Merge branch 'bytecode-spec'
This commit is contained in:
commit
a0ecbfd334
14
AGENTS.md
14
AGENTS.md
@ -6,3 +6,17 @@
|
|||||||
- If you need a wrapper for delegated properties, check for `getValue` explicitly and return a concrete `Statement` object when missing; avoid `onNotFoundResult` lambdas.
|
- If you need a wrapper for delegated properties, check for `getValue` explicitly and return a concrete `Statement` object when missing; avoid `onNotFoundResult` lambdas.
|
||||||
- If wasmJs browser tests hang, first run `:lynglib:wasmJsNodeTest` and look for wasm compilation errors; hangs usually mean module instantiation failed.
|
- If wasmJs browser tests hang, first run `:lynglib:wasmJsNodeTest` and look for wasm compilation errors; hangs usually mean module instantiation failed.
|
||||||
- Do not increase test timeouts to mask wasm generation errors; fix the invalid IR instead.
|
- Do not increase test timeouts to mask wasm generation errors; fix the invalid IR instead.
|
||||||
|
|
||||||
|
## Type inference notes (notes/new_lyng_type_system_spec.md)
|
||||||
|
- Nullability is Kotlin-style: `T` non-null, `T?` nullable, `!!` asserts non-null.
|
||||||
|
- `void` is a singleton of class `Void` (syntax sugar for return type).
|
||||||
|
- Object members are always allowed even on unknown types; non-Object members require explicit casts. Remove `inspect` from Object and use `toInspectString()` instead.
|
||||||
|
- Type expression checks: `x is T` is value instance check; `T1 is T2` is type-subset; `A in T` means `A` is subset of `T`; `==` is structural type equality.
|
||||||
|
- Type aliases: `type Name = TypeExpr` (generic allowed) expand to their underlying type expressions; no nominal distinctness.
|
||||||
|
- Do not reintroduce bytecode fallback opcodes (e.g., `GET_NAME`, `EVAL_*`, `CALL_FALLBACK`) or runtime name-resolution fallbacks; all symbol resolution must stay compile-time only.
|
||||||
|
|
||||||
|
## Bytecode frame-first migration plan
|
||||||
|
- Treat frame slots as the only storage for locals/temps by default; avoid pre-creating scope slot mappings for compiled functions.
|
||||||
|
- Create closure references only when a capture is detected; use a direct frame+slot reference (foreign slot ref) instead of scope slots.
|
||||||
|
- Keep Scope as a lazy reflection facade: resolve name -> slot only on demand for Kotlin interop (no eager name mapping on every call).
|
||||||
|
- Avoid PUSH_SCOPE/POP_SCOPE in bytecode for loops/functions unless dynamic name access or Kotlin reflection is requested.
|
||||||
|
|||||||
39
CHANGELOG.md
39
CHANGELOG.md
@ -1,6 +1,31 @@
|
|||||||
## Changelog
|
## 1.5.0-SNAPSHOT
|
||||||
|
|
||||||
### Unreleased
|
### Language Features
|
||||||
|
- Added `return` statement with local and non-local exit support (`return@label`).
|
||||||
|
- Support for `abstract` classes, methods, and variables.
|
||||||
|
- Introduced `interface` as a synonym for `abstract class`.
|
||||||
|
- Multiple Inheritance (MI) completed and enabled by default (C3 MRO).
|
||||||
|
- Class properties with custom accessors (`get`, `set`).
|
||||||
|
- Restricted setter visibility (`private set`, `protected set`).
|
||||||
|
- Late-initialized `val` fields in classes with `Unset` protection.
|
||||||
|
- Named arguments (`name: value`) and named splats (`...Map`).
|
||||||
|
- Assign-if-null operator `?=`.
|
||||||
|
- Refined `protected` visibility rules and `closed` modifier.
|
||||||
|
- Transient attribute `@Transient` for serialization and equality.
|
||||||
|
- Unified Delegation model for `val`, `var`, and `fun`.
|
||||||
|
- Singleton objects (`object`) and object expressions.
|
||||||
|
|
||||||
|
### Standard Library
|
||||||
|
- Added `with(self, block)` for scoped execution.
|
||||||
|
- Added `clamp()` function and extension.
|
||||||
|
- Improved `Exception` and `StackTraceEntry` reporting.
|
||||||
|
|
||||||
|
### Tooling and IDE
|
||||||
|
- **CLI**: Added `fmt` as a first-class subcommand for code formatting.
|
||||||
|
- **IDEA Plugin**: Lightweight autocompletion (experimental), improved docs, and Grazie integration.
|
||||||
|
- **Highlighters**: Updated TextMate bundle and website highlighters for new syntax.
|
||||||
|
|
||||||
|
### Detailed Changes:
|
||||||
|
|
||||||
- Language: Refined `protected` visibility rules
|
- 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).
|
- 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).
|
||||||
@ -109,16 +134,6 @@
|
|||||||
|
|
||||||
- Documentation updated (docs/OOP.md and tutorial quick-start) to reflect MI with active C3 MRO.
|
- Documentation updated (docs/OOP.md and tutorial quick-start) to reflect MI with active C3 MRO.
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Existing single-inheritance code continues to work; resolution reduces to the single base.
|
|
||||||
- If code previously relied on non-deterministic parent set iteration, C3 MRO provides a predictable order; disambiguate explicitly if needed using `this@Type`/casts.
|
|
||||||
|
|
||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
## Unreleased
|
|
||||||
|
|
||||||
- CLI: Added `fmt` as a first-class Clikt subcommand.
|
- CLI: Added `fmt` as a first-class Clikt subcommand.
|
||||||
- Default behavior: formats files to stdout (no in-place edits by default).
|
- Default behavior: formats files to stdout (no in-place edits by default).
|
||||||
- Options:
|
- Options:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Lyng Language AI Specification (V1.3)
|
# Lyng Language AI Specification (V1.5.0-SNAPSHOT)
|
||||||
|
|
||||||
High-density specification for LLMs. Reference this for all Lyng code generation.
|
High-density specification for LLMs. Reference this for all Lyng code generation.
|
||||||
|
|
||||||
@ -19,6 +19,7 @@ High-density specification for LLMs. Reference this for all Lyng code generation
|
|||||||
- **Equality**: `==` (equals), `!=` (not equals), `===` (ref identity), `!==` (ref not identity).
|
- **Equality**: `==` (equals), `!=` (not equals), `===` (ref identity), `!==` (ref not identity).
|
||||||
- **Comparison**: `<`, `>`, `<=`, `>=`, `<=>` (shuttle/spaceship, returns -1, 0, 1).
|
- **Comparison**: `<`, `>`, `<=`, `>=`, `<=>` (shuttle/spaceship, returns -1, 0, 1).
|
||||||
- **Destructuring**: `val [a, b, rest...] = list`. Supports nested `[a, [b, c]]` and splats.
|
- **Destructuring**: `val [a, b, rest...] = list`. Supports nested `[a, [b, c]]` and splats.
|
||||||
|
- **Compile-Time Resolution Only**: All names/members must resolve at compile time. No runtime name lookup or fallback opcodes.
|
||||||
|
|
||||||
## 2. Object-Oriented Programming (OOP)
|
## 2. Object-Oriented Programming (OOP)
|
||||||
- **Multiple Inheritance**: Supported with **C3 MRO** (Python-style). Diamond-safe.
|
- **Multiple Inheritance**: Supported with **C3 MRO** (Python-style). Diamond-safe.
|
||||||
@ -37,6 +38,15 @@ High-density specification for LLMs. Reference this for all Lyng code generation
|
|||||||
- **Disambiguation**: `this@Base.member()` or `(obj as Base).member()`. `as` returns a qualified view.
|
- **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.
|
- **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.
|
- **Extensions**: `fun Class.ext()` or `val Class.ext get = ...`. Scope-isolated.
|
||||||
|
- **Member Access**: Object members (`toString`, `toInspectString`, `let`, `also`, `apply`, `run`) are allowed on unknown types; all other members require a statically known receiver type or explicit cast.
|
||||||
|
|
||||||
|
## 2.1 Type System (2026)
|
||||||
|
- **Root Type**: Everything is an `Object` (root of the hierarchy).
|
||||||
|
- **Nullability**: Non-null by default (`T`), nullable with `T?`, `!!` asserts non-null.
|
||||||
|
- **Untyped params**: `fun foo(x)` -> `x: Object`, `fun foo(x?)` -> `x: Object?`.
|
||||||
|
- **Untyped vars**: `var x` is `Unset` until first assignment locks the type.
|
||||||
|
- **Inference**: List/map literals infer union element types; empty list is `List<Object>`, empty map is `{:}`.
|
||||||
|
- **Generics**: Bounds with `T: A & B` or `T: A | B`; variance uses `out`/`in`.
|
||||||
|
|
||||||
## 3. Delegation (`by`)
|
## 3. Delegation (`by`)
|
||||||
Unified model for `val`, `var`, and `fun`.
|
Unified model for `val`, `var`, and `fun`.
|
||||||
@ -63,11 +73,11 @@ Delegate Methods:
|
|||||||
- **Collections**: `List` ( `[a, b]` ), `Map` ( `Map(k => v)` ), `Set` ( `Set(a, b)` ). `MapEntry` ( `k => v` ).
|
- **Collections**: `List` ( `[a, b]` ), `Map` ( `Map(k => v)` ), `Set` ( `Set(a, b)` ). `MapEntry` ( `k => v` ).
|
||||||
|
|
||||||
## 5. Patterns & Shorthands
|
## 5. Patterns & Shorthands
|
||||||
- **Map Literals**: `{ key: value, identifier: }` (identifier shorthand `x:` is `x: x`). Empty map is `Map()`.
|
- **Map Literals**: `{ key: value, identifier: }` (identifier shorthand `x:` is `x: x`). Empty map is `{:}`.
|
||||||
- **Named Arguments**: `fun(y: 10, x: 5)`. Shorthand: `Point(x:, y:)`.
|
- **Named Arguments**: `fun(y: 10, x: 5)`. Shorthand: `Point(x:, y:)`.
|
||||||
- **Varargs & Splats**: `fun f(args...)`, `f(...otherList)`.
|
- **Varargs & Splats**: `fun f(args...)`, `f(...otherList)`.
|
||||||
- **Labels**: `loop@ for(x in list) { if(x == 0) break@loop }`.
|
- **Labels**: `loop@ for(x in list) { if(x == 0) break@loop }`.
|
||||||
- **Dynamic**: `val d = dynamic { get { name -> ... } }` allows `d.anyName`.
|
- **Dynamic**: `val d = dynamic { get { name -> ... } }` allows `d.anyName` via explicit dynamic handler (not implicit fallback).
|
||||||
|
|
||||||
## 6. Operators & Methods to Overload
|
## 6. Operators & Methods to Overload
|
||||||
| Op | Method | Op | Method |
|
| Op | Method | Op | Method |
|
||||||
|
|||||||
17
README.md
17
README.md
@ -25,6 +25,16 @@ Point(x:, y:).dist() //< 5
|
|||||||
fun swapEnds(first, args..., last, f) {
|
fun swapEnds(first, args..., last, f) {
|
||||||
f( last, ...args, first)
|
f( last, ...args, first)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class A {
|
||||||
|
class B(x?)
|
||||||
|
object Inner { val foo = "bar" }
|
||||||
|
enum E* { One, Two }
|
||||||
|
}
|
||||||
|
val ab = A.B()
|
||||||
|
assertEquals(ab.x, null)
|
||||||
|
assertEquals(A.Inner.foo, "bar")
|
||||||
|
assertEquals(A.One, A.E.One)
|
||||||
```
|
```
|
||||||
|
|
||||||
- extremely simple Kotlin integration on any platform (JVM, JS, WasmJS, Lunux, MacOS, iOS, Windows)
|
- extremely simple Kotlin integration on any platform (JVM, JS, WasmJS, Lunux, MacOS, iOS, Windows)
|
||||||
@ -38,6 +48,7 @@ fun swapEnds(first, args..., last, f) {
|
|||||||
|
|
||||||
- [Language home](https://lynglang.com)
|
- [Language home](https://lynglang.com)
|
||||||
- [introduction and tutorial](docs/tutorial.md) - start here please
|
- [introduction and tutorial](docs/tutorial.md) - start here please
|
||||||
|
- [What's New in 1.5](docs/whats_new_1_5.md)
|
||||||
- [Testing and Assertions](docs/Testing.md)
|
- [Testing and Assertions](docs/Testing.md)
|
||||||
- [Filesystem and Processes (lyngio)](docs/lyngio.md)
|
- [Filesystem and Processes (lyngio)](docs/lyngio.md)
|
||||||
- [Return Statement](docs/return_statement.md)
|
- [Return Statement](docs/return_statement.md)
|
||||||
@ -53,7 +64,7 @@ fun swapEnds(first, args..., last, f) {
|
|||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
// update to current please:
|
// update to current please:
|
||||||
val lyngVersion = "0.6.1-SNAPSHOT"
|
val lyngVersion = "1.5.0-SNAPSHOT"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
// ...
|
// ...
|
||||||
@ -166,7 +177,7 @@ Designed to add scripting to kotlin multiplatform application in easy and effici
|
|||||||
|
|
||||||
# Language Roadmap
|
# Language Roadmap
|
||||||
|
|
||||||
We are now at **v1.0**: basic optimization performed, battery included: standard library is 90% here, initial
|
We are now at **v1.5.0-SNAPSHOT** (stable development cycle): basic optimization performed, battery included: standard library is 90% here, initial
|
||||||
support in HTML, popular editors, and IDEA; tools to syntax highlight and format code are ready. It was released closed to schedule.
|
support in HTML, popular editors, and IDEA; tools to syntax highlight and format code are ready. It was released closed to schedule.
|
||||||
|
|
||||||
Ready features:
|
Ready features:
|
||||||
@ -206,7 +217,7 @@ Ready features:
|
|||||||
|
|
||||||
All of this is documented in the [language site](https://lynglang.com) and locally [docs/language.md](docs/tutorial.md). the current nightly builds published on the site and in the private maven repository.
|
All of this is documented in the [language site](https://lynglang.com) and locally [docs/language.md](docs/tutorial.md). the current nightly builds published on the site and in the private maven repository.
|
||||||
|
|
||||||
## plan: towards v1.5 Enhancing
|
## plan: towards v2.0 Next Generation
|
||||||
|
|
||||||
- [x] site with integrated interpreter to give a try
|
- [x] site with integrated interpreter to give a try
|
||||||
- [x] kotlin part public API good docs, integration focused
|
- [x] kotlin part public API good docs, integration focused
|
||||||
|
|||||||
7
bytecode_migration_plan.md
Normal file
7
bytecode_migration_plan.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Bytecode Migration Plan (Archived)
|
||||||
|
|
||||||
|
Status: completed.
|
||||||
|
|
||||||
|
Historical reference:
|
||||||
|
- `notes/archive/bytecode_migration_plan.md` (full plan)
|
||||||
|
- `notes/archive/bytecode_migration_plan_completed.md` (summary)
|
||||||
280
docs/BytecodeSpec.md
Normal file
280
docs/BytecodeSpec.md
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
# Lyng Bytecode VM Spec v0 (Draft)
|
||||||
|
|
||||||
|
This document describes a register-like (3-address) bytecode for Lyng with
|
||||||
|
dynamic slot width (8/16/32-bit slot IDs), a slot-tail argument model, and
|
||||||
|
typed lanes for Obj/Int/Real/Bool. The VM is intended to run as a suspendable
|
||||||
|
interpreter and fall back to the existing AST execution when needed.
|
||||||
|
|
||||||
|
## 1) Frame & Slot Model
|
||||||
|
|
||||||
|
### Frame metadata
|
||||||
|
- localCount: number of local slots for this function (fixed at compile time).
|
||||||
|
- argCount: number of arguments passed at call time.
|
||||||
|
- scopeSlotNames: optional debug names for scope slots (locals/params), aligned to slot mapping.
|
||||||
|
- argBase = localCount.
|
||||||
|
|
||||||
|
### Slot layout
|
||||||
|
slots[0 .. localCount-1] locals
|
||||||
|
slots[localCount .. localCount+argCount-1] arguments
|
||||||
|
|
||||||
|
### Typed lanes
|
||||||
|
- slotType[]: UNKNOWN/OBJ/INT/REAL/BOOL
|
||||||
|
- objSlots[], intSlots[], realSlots[], boolSlots[]
|
||||||
|
- A slot is a logical index; active lane is selected by slotType.
|
||||||
|
|
||||||
|
### Parameter access
|
||||||
|
- param i => slot localCount + i
|
||||||
|
- variadic extra => slot localCount + declaredParamCount + k
|
||||||
|
|
||||||
|
### Debug metadata (optional)
|
||||||
|
- scopeSlotNames: array sized scopeSlotCount, each entry nullable.
|
||||||
|
- Intended for disassembly/debug tooling; VM semantics do not depend on it.
|
||||||
|
|
||||||
|
### Constant pool extras
|
||||||
|
- SlotPlan: map of name -> slot index, used by PUSH_SCOPE to pre-allocate and map loop locals.
|
||||||
|
- CallArgsPlan: ordered argument specs (name/splat) + tailBlock flag, used when argCount has the plan flag set.
|
||||||
|
|
||||||
|
## 2) Slot ID Width
|
||||||
|
|
||||||
|
Per frame, select:
|
||||||
|
- 8-bit if localCount + argCount < 256
|
||||||
|
- 16-bit if < 65536
|
||||||
|
- 32-bit otherwise
|
||||||
|
|
||||||
|
The decoder uses a dedicated loop per width. All slot operands are expanded to
|
||||||
|
Int internally.
|
||||||
|
|
||||||
|
## 3) CALL Semantics (Model A)
|
||||||
|
|
||||||
|
Instruction:
|
||||||
|
CALL_DIRECT fnId, argBase, argCount, dst
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
- Allocate a callee frame sized localCount + argCount.
|
||||||
|
- Copy caller slots [argBase .. argBase+argCount-1] into callee slots
|
||||||
|
[localCount .. localCount+argCount-1].
|
||||||
|
- Callee returns via RET slot or RET_VOID.
|
||||||
|
- Caller stores return value to dst.
|
||||||
|
|
||||||
|
Other calls:
|
||||||
|
- CALL_VIRTUAL recvSlot, methodId, argBase, argCount, dst
|
||||||
|
- CALL_FALLBACK stmtId, argBase, argCount, dst
|
||||||
|
- CALL_SLOT calleeSlot, argBase, argCount, dst
|
||||||
|
|
||||||
|
## 4) Binary Encoding Layout
|
||||||
|
|
||||||
|
All instructions are:
|
||||||
|
[opcode:U8] [operands...]
|
||||||
|
|
||||||
|
Operand widths:
|
||||||
|
- slotId: S = 1/2/4 bytes (per frame slot width)
|
||||||
|
- constId: K = 2 bytes (U16), extend to 4 if needed
|
||||||
|
- ip: I = 2 bytes (U16) or 4 bytes (U32) per function size
|
||||||
|
- fnId/methodId/stmtId: F/M/T = 2 bytes (U16) unless extended
|
||||||
|
- argCount: C = 2 bytes (U16), extend to 4 if needed
|
||||||
|
|
||||||
|
Endianness: little-endian for multi-byte operands.
|
||||||
|
|
||||||
|
Common operand patterns:
|
||||||
|
- S: one slot
|
||||||
|
- SS: two slots
|
||||||
|
- SSS: three slots
|
||||||
|
- K S: constId + dst slot
|
||||||
|
- S I: slot + jump target
|
||||||
|
- I: jump target
|
||||||
|
- F S C S: fnId, argBase slot, argCount, dst slot
|
||||||
|
|
||||||
|
Arg count flag:
|
||||||
|
- If high bit of C is set (0x8000), the low 15 bits encode a CallArgsPlan constId.
|
||||||
|
- When not set, C is the raw positional count and tailBlockMode=false.
|
||||||
|
|
||||||
|
## 5) Opcode Table
|
||||||
|
|
||||||
|
Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
|
||||||
|
|
||||||
|
### Data movement
|
||||||
|
- NOP
|
||||||
|
- MOVE_OBJ S -> S
|
||||||
|
- MOVE_INT S -> S
|
||||||
|
- MOVE_REAL S -> S
|
||||||
|
- MOVE_BOOL S -> S
|
||||||
|
- BOX_OBJ S -> S
|
||||||
|
- CONST_OBJ K -> S
|
||||||
|
- CONST_INT K -> S
|
||||||
|
- CONST_REAL K -> S
|
||||||
|
- CONST_BOOL K -> S
|
||||||
|
- CONST_NULL -> S
|
||||||
|
|
||||||
|
### Numeric conversions
|
||||||
|
- INT_TO_REAL S -> S
|
||||||
|
- REAL_TO_INT S -> S
|
||||||
|
- BOOL_TO_INT S -> S
|
||||||
|
- INT_TO_BOOL S -> S
|
||||||
|
|
||||||
|
### Arithmetic: INT
|
||||||
|
- ADD_INT S, S -> S
|
||||||
|
- SUB_INT S, S -> S
|
||||||
|
- MUL_INT S, S -> S
|
||||||
|
- DIV_INT S, S -> S
|
||||||
|
- MOD_INT S, S -> S
|
||||||
|
- NEG_INT S -> S
|
||||||
|
- INC_INT S
|
||||||
|
- DEC_INT S
|
||||||
|
|
||||||
|
### Arithmetic: REAL
|
||||||
|
- ADD_REAL S, S -> S
|
||||||
|
- SUB_REAL S, S -> S
|
||||||
|
- MUL_REAL S, S -> S
|
||||||
|
- DIV_REAL S, S -> S
|
||||||
|
- NEG_REAL S -> S
|
||||||
|
|
||||||
|
### Arithmetic: OBJ
|
||||||
|
- ADD_OBJ S, S -> S
|
||||||
|
- SUB_OBJ S, S -> S
|
||||||
|
- MUL_OBJ S, S -> S
|
||||||
|
- DIV_OBJ S, S -> S
|
||||||
|
- MOD_OBJ S, S -> S
|
||||||
|
|
||||||
|
### Bitwise: INT
|
||||||
|
- AND_INT S, S -> S
|
||||||
|
- OR_INT S, S -> S
|
||||||
|
- XOR_INT S, S -> S
|
||||||
|
- SHL_INT S, S -> S
|
||||||
|
- SHR_INT S, S -> S
|
||||||
|
- USHR_INT S, S -> S
|
||||||
|
- INV_INT S -> S
|
||||||
|
|
||||||
|
### Comparisons (typed)
|
||||||
|
- CMP_EQ_INT S, S -> S
|
||||||
|
- CMP_NEQ_INT S, S -> S
|
||||||
|
- CMP_LT_INT S, S -> S
|
||||||
|
- CMP_LTE_INT S, S -> S
|
||||||
|
- CMP_GT_INT S, S -> S
|
||||||
|
- CMP_GTE_INT S, S -> S
|
||||||
|
- CMP_EQ_REAL S, S -> S
|
||||||
|
- CMP_NEQ_REAL S, S -> S
|
||||||
|
- CMP_LT_REAL S, S -> S
|
||||||
|
- CMP_LTE_REAL S, S -> S
|
||||||
|
- CMP_GT_REAL S, S -> S
|
||||||
|
- CMP_GTE_REAL S, S -> S
|
||||||
|
- CMP_EQ_BOOL S, S -> S
|
||||||
|
- CMP_NEQ_BOOL S, S -> S
|
||||||
|
|
||||||
|
### Mixed numeric comparisons
|
||||||
|
- CMP_EQ_INT_REAL S, S -> S
|
||||||
|
- CMP_EQ_REAL_INT S, S -> S
|
||||||
|
- CMP_LT_INT_REAL S, S -> S
|
||||||
|
- CMP_LT_REAL_INT S, S -> S
|
||||||
|
- CMP_LTE_INT_REAL S, S -> S
|
||||||
|
- CMP_LTE_REAL_INT S, S -> S
|
||||||
|
- CMP_GT_INT_REAL S, S -> S
|
||||||
|
- CMP_GT_REAL_INT S, S -> S
|
||||||
|
- CMP_GTE_INT_REAL S, S -> S
|
||||||
|
- CMP_GTE_REAL_INT S, S -> S
|
||||||
|
- CMP_NEQ_INT_REAL S, S -> S
|
||||||
|
- CMP_NEQ_REAL_INT S, S -> S
|
||||||
|
- CMP_EQ_OBJ S, S -> S
|
||||||
|
- CMP_NEQ_OBJ S, S -> S
|
||||||
|
- CMP_REF_EQ_OBJ S, S -> S
|
||||||
|
- CMP_REF_NEQ_OBJ S, S -> S
|
||||||
|
- CMP_LT_OBJ S, S -> S
|
||||||
|
- CMP_LTE_OBJ S, S -> S
|
||||||
|
- CMP_GT_OBJ S, S -> S
|
||||||
|
- CMP_GTE_OBJ S, S -> S
|
||||||
|
|
||||||
|
### Boolean ops
|
||||||
|
- NOT_BOOL S -> S
|
||||||
|
- AND_BOOL S, S -> S
|
||||||
|
- OR_BOOL S, S -> S
|
||||||
|
|
||||||
|
### Control flow
|
||||||
|
- JMP I
|
||||||
|
- JMP_IF_TRUE S, I
|
||||||
|
- JMP_IF_FALSE S, I
|
||||||
|
- RET S
|
||||||
|
- RET_VOID
|
||||||
|
- PUSH_SCOPE K
|
||||||
|
- POP_SCOPE
|
||||||
|
|
||||||
|
### Scope setup
|
||||||
|
- PUSH_SCOPE uses const `SlotPlan` (name -> slot index) to create a child scope and apply slot mapping.
|
||||||
|
- POP_SCOPE restores the parent scope.
|
||||||
|
|
||||||
|
### Calls
|
||||||
|
- CALL_DIRECT F, S, C, S
|
||||||
|
- CALL_VIRTUAL S, M, S, C, S
|
||||||
|
- CALL_FALLBACK T, S, C, S
|
||||||
|
- CALL_SLOT S, S, C, S
|
||||||
|
|
||||||
|
### Object access (optional, later)
|
||||||
|
- GET_FIELD S, M -> S
|
||||||
|
- SET_FIELD S, M, S
|
||||||
|
- GET_INDEX S, S -> S
|
||||||
|
- SET_INDEX S, S, S
|
||||||
|
|
||||||
|
### Fallback
|
||||||
|
- EVAL_FALLBACK T -> S
|
||||||
|
|
||||||
|
## 6) Const Pool Encoding (v0)
|
||||||
|
|
||||||
|
Each const entry is encoded as:
|
||||||
|
[tag:U8] [payload...]
|
||||||
|
|
||||||
|
Tags:
|
||||||
|
- 0x00: NULL
|
||||||
|
- 0x01: BOOL (payload: U8 0/1)
|
||||||
|
- 0x02: INT (payload: S64, little-endian)
|
||||||
|
- 0x03: REAL (payload: F64, IEEE-754, little-endian)
|
||||||
|
- 0x04: STRING (payload: U32 length + UTF-8 bytes)
|
||||||
|
- 0x05: OBJ_REF (payload: U32 index into external Obj table)
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- OBJ_REF is reserved for embedding prebuilt Obj handles if needed.
|
||||||
|
- Strings use UTF-8; length is bytes, not chars.
|
||||||
|
|
||||||
|
## 7) Function Header (binary container)
|
||||||
|
|
||||||
|
Suggested layout for a bytecode function blob:
|
||||||
|
- magic: U32 ("LYBC")
|
||||||
|
- version: U16 (0x0001)
|
||||||
|
- slotWidth: U8 (1,2,4)
|
||||||
|
- ipWidth: U8 (2,4)
|
||||||
|
- constIdWidth: U8 (2,4)
|
||||||
|
- localCount: U32
|
||||||
|
- codeSize: U32 (bytes)
|
||||||
|
- constCount: U32
|
||||||
|
- constPool: [const entries...]
|
||||||
|
- code: [bytecode...]
|
||||||
|
|
||||||
|
Const pool entries use the encoding described in section 6.
|
||||||
|
|
||||||
|
## 8) Sample Bytecode (illustrative)
|
||||||
|
|
||||||
|
Example Lyng:
|
||||||
|
val x = 2
|
||||||
|
val y = 3
|
||||||
|
val z = x + y
|
||||||
|
|
||||||
|
Assume:
|
||||||
|
- localCount = 3 (x,y,z)
|
||||||
|
- argCount = 0
|
||||||
|
- slot width = 1 byte
|
||||||
|
- const pool: [INT 2, INT 3]
|
||||||
|
|
||||||
|
Bytecode:
|
||||||
|
CONST_INT k0 -> s0
|
||||||
|
CONST_INT k1 -> s1
|
||||||
|
ADD_INT s0, s1 -> s2
|
||||||
|
RET_VOID
|
||||||
|
|
||||||
|
Encoded (opcode values symbolic):
|
||||||
|
[OP_CONST_INT][k0][s0]
|
||||||
|
[OP_CONST_INT][k1][s1]
|
||||||
|
[OP_ADD_INT][s0][s1][s2]
|
||||||
|
[OP_RET_VOID]
|
||||||
|
|
||||||
|
## 9) Notes
|
||||||
|
|
||||||
|
- Mixed-mode is allowed: compiler can emit FALLBACK ops for unsupported nodes.
|
||||||
|
- The VM must be suspendable; on suspension, store ip + minimal operand state.
|
||||||
|
- Source mapping uses a separate ip->Pos table, not part of core bytecode.
|
||||||
42
docs/OOP.md
42
docs/OOP.md
@ -113,6 +113,48 @@ val handler = object {
|
|||||||
- **Serialization**: Anonymous objects are **not serializable**. Attempting to encode an anonymous object via `Lynon` will throw a `SerializationException`. This is because their class definition is transient and cannot be safely restored in a different session or process.
|
- **Serialization**: Anonymous objects are **not serializable**. Attempting to encode an anonymous object via `Lynon` will throw a `SerializationException`. This is because their class definition is transient and cannot be safely restored in a different session or process.
|
||||||
- **Type Identity**: Every object expression creates a unique anonymous class. Two identical object expressions will result in two different classes with distinct type identities.
|
- **Type Identity**: Every object expression creates a unique anonymous class. Two identical object expressions will result in two different classes with distinct type identities.
|
||||||
|
|
||||||
|
## Nested Declarations
|
||||||
|
|
||||||
|
Lyng allows classes, objects, enums, and type aliases to be declared inside another class. These declarations live in the **class namespace** (not the instance), so they do not capture an outer instance and are accessed with a qualifier.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class A {
|
||||||
|
class B(x?)
|
||||||
|
object Inner { val foo = "bar" }
|
||||||
|
type Alias = B
|
||||||
|
enum E { One, Two }
|
||||||
|
}
|
||||||
|
|
||||||
|
val ab = A.B()
|
||||||
|
assertEquals(ab.x, null)
|
||||||
|
assertEquals(A.Inner.foo, "bar")
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- **Qualified access**: use `Outer.Inner` for nested classes/objects/enums/aliases. Inside `Outer` you can refer to them by unqualified name unless shadowed.
|
||||||
|
- **No inner semantics**: nested declarations do not capture an instance of the outer class. They are resolved at compile time.
|
||||||
|
- **Visibility**: `private` restricts a nested declaration to the declaring class body (not visible from outside or subclasses).
|
||||||
|
- **Reflection name**: a nested class reports `Outer.Inner` (e.g., `A.B::class.name` is `"A.B"`).
|
||||||
|
- **Type aliases**: behave as aliases of the qualified nested type and are expanded by the type system.
|
||||||
|
|
||||||
|
### Lifted Enum Entries
|
||||||
|
|
||||||
|
Enums can optionally lift their entries into the surrounding class namespace using `*`:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class A {
|
||||||
|
enum E* { One, Two }
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(A.One, A.E.One)
|
||||||
|
assertEquals(A.Two, A.E.Two)
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- `E*` exposes entries in `A` as if they were direct members (`A.One`).
|
||||||
|
- If a name would conflict with an existing class member, compilation fails (no implicit fallback).
|
||||||
|
- Without `*`, use the normal `A.E.One` form.
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
Properties allow you to define member accessors that look like fields but execute code when read or written. Unlike regular fields, properties in Lyng do **not** have automatic backing fields; they are pure accessors.
|
Properties allow you to define member accessors that look like fields but execute code when read or written. Unlike regular fields, properties in Lyng do **not** have automatic backing fields; they are pure accessors.
|
||||||
|
|||||||
@ -45,10 +45,11 @@ are equal or within another, taking into account the end-inclusiveness:
|
|||||||
assert( (1..<3) in (1..3) )
|
assert( (1..<3) in (1..3) )
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
## Finite Ranges are iterable
|
## Ranges are iterable
|
||||||
|
|
||||||
So given a range with both ends, you can assume it is [Iterable]. This automatically let
|
Finite ranges are [Iterable] and can be used in loops and list conversions.
|
||||||
use finite ranges in loops and convert it to lists:
|
Open-ended ranges are iterable only with an explicit `step`, and open-start
|
||||||
|
ranges are never iterable.
|
||||||
|
|
||||||
assert( [-2, -1, 0, 1] == (-2..1).toList() )
|
assert( [-2, -1, 0, 1] == (-2..1).toList() )
|
||||||
>>> void
|
>>> void
|
||||||
@ -62,6 +63,8 @@ In spite of this you can use ranges in for loops:
|
|||||||
>>> 3
|
>>> 3
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
The loop variable is read-only inside the loop body (behaves like a `val`).
|
||||||
|
|
||||||
but
|
but
|
||||||
|
|
||||||
for( i in 1..<3 )
|
for( i in 1..<3 )
|
||||||
@ -70,6 +73,26 @@ but
|
|||||||
>>> 2
|
>>> 2
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
### Stepped ranges
|
||||||
|
|
||||||
|
Use `step` to change the iteration increment. The range bounds still define membership,
|
||||||
|
so iteration ends when the next value is no longer in the range.
|
||||||
|
|
||||||
|
assert( [1,3,5] == (1..5 step 2).toList() )
|
||||||
|
assert( [1,3] == (1..<5 step 2).toList() )
|
||||||
|
assert( ['a','c','e'] == ('a'..'e' step 2).toList() )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Real ranges require an explicit step:
|
||||||
|
|
||||||
|
assert( [0,0.25,0.5,0.75,1.0] == (0.0..1.0 step 0.25).toList() )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Open-ended ranges require an explicit step to iterate:
|
||||||
|
|
||||||
|
(0.. step 1).take(3).toList()
|
||||||
|
>>> [0,1,2]
|
||||||
|
|
||||||
## Character ranges
|
## Character ranges
|
||||||
|
|
||||||
You can use Char as both ends of the closed range:
|
You can use Char as both ends of the closed range:
|
||||||
@ -98,6 +121,7 @@ Exclusive end char ranges are supported too:
|
|||||||
| isEndInclusive | true for '..' | Bool |
|
| isEndInclusive | true for '..' | Bool |
|
||||||
| isOpen | at any end | Bool |
|
| isOpen | at any end | Bool |
|
||||||
| isIntRange | both start and end are Int | Bool |
|
| isIntRange | both start and end are Int | Bool |
|
||||||
|
| step | explicit iteration step | Any? |
|
||||||
| start | | Any? |
|
| start | | Any? |
|
||||||
| end | | Any? |
|
| end | | Any? |
|
||||||
| size | for finite ranges, see above | Long |
|
| size | for finite ranges, see above | Long |
|
||||||
|
|||||||
@ -105,6 +105,7 @@ arguments list in almost arbitrary ways. For example:
|
|||||||
var result = ""
|
var result = ""
|
||||||
for( a in args ) result += a
|
for( a in args ) result += a
|
||||||
}
|
}
|
||||||
|
// loop variables are read-only inside the loop body
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"4231",
|
"4231",
|
||||||
|
|||||||
@ -182,6 +182,77 @@ instance.value = 42
|
|||||||
println(instance.value) // -> 42
|
println(instance.value) // -> 42
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 6.5) Preferred: bind Kotlin implementations to declared Lyng classes
|
||||||
|
|
||||||
|
For extensions and libraries, the **preferred** workflow is Lyng‑first: declare the class and its members in Lyng, then bind the Kotlin implementations using the bridge.
|
||||||
|
|
||||||
|
This keeps Lyng semantics (visibility, overrides, type checks) in Lyng, while Kotlin supplies the behavior.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
// Lyng side (in a module)
|
||||||
|
class Counter {
|
||||||
|
extern var value: Int
|
||||||
|
extern fun inc(by: Int): Int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: members must be marked `extern` so the compiler emits the ABI slots that Kotlin bindings attach to. This applies to functions and properties bound via `addFun` / `addVal` / `addVar`.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Kotlin side (binding)
|
||||||
|
val moduleScope = Script.newScope() // or an existing module scope
|
||||||
|
moduleScope.eval("class Counter { extern var value: Int; extern fun inc(by: Int): Int }")
|
||||||
|
|
||||||
|
moduleScope.bind("Counter") {
|
||||||
|
addVar(
|
||||||
|
name = "value",
|
||||||
|
get = { _, self -> self.readField(this, "value").value },
|
||||||
|
set = { _, self, v -> self.writeField(this, "value", v) }
|
||||||
|
)
|
||||||
|
addFun("inc") { _, self, args ->
|
||||||
|
val by = args.requiredArg<ObjInt>(0).value
|
||||||
|
val current = self.readField(this, "value").value as ObjInt
|
||||||
|
val next = ObjInt(current.value + by)
|
||||||
|
self.writeField(this, "value", next)
|
||||||
|
next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Binding must happen **before** the first instance is created.
|
||||||
|
- Use [LyngClassBridge] to bind by name/module, or by an already resolved `ObjClass`.
|
||||||
|
- Use `ObjInstance.data` / `ObjClass.classData` to attach Kotlin‑side state when needed.
|
||||||
|
|
||||||
|
### 6.6) Preferred: Kotlin reflection bridge for call‑by‑name
|
||||||
|
|
||||||
|
For Kotlin code that needs dynamic access to Lyng variables, functions, or members, use the bridge resolver.
|
||||||
|
It provides explicit, cached handles and predictable lookup rules.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.eval("""
|
||||||
|
val x = 40
|
||||||
|
fun add(a, b) = a + b
|
||||||
|
class Box { var value = 1 }
|
||||||
|
""")
|
||||||
|
|
||||||
|
val resolver = scope.resolver()
|
||||||
|
|
||||||
|
// Read a top‑level value
|
||||||
|
val x = resolver.resolveVal("x").get(scope)
|
||||||
|
|
||||||
|
// Call a function by name (cached inside the resolver)
|
||||||
|
val sum = (resolver as BridgeCallByName).callByName(scope, "add", Arguments(ObjInt(1), ObjInt(2)))
|
||||||
|
|
||||||
|
// Member access
|
||||||
|
val box = scope.eval("Box()")
|
||||||
|
val valueHandle = resolver.resolveMemberVar(box, "value")
|
||||||
|
valueHandle.set(scope, ObjInt(10))
|
||||||
|
val value = valueHandle.get(scope)
|
||||||
|
```
|
||||||
|
|
||||||
### 7) Read variable values back in Kotlin
|
### 7) Read variable values back in Kotlin
|
||||||
|
|
||||||
The simplest approach: evaluate an expression that yields the value and convert it.
|
The simplest approach: evaluate an expression that yields the value and convert it.
|
||||||
|
|||||||
128
docs/generics.md
Normal file
128
docs/generics.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Generics and type expressions
|
||||||
|
|
||||||
|
This document covers generics, bounds, unions/intersections, and the rules for type expressions in Lyng.
|
||||||
|
|
||||||
|
# Generic parameters
|
||||||
|
|
||||||
|
Declare type parameters with `<...>` on functions and classes:
|
||||||
|
|
||||||
|
fun id<T>(x: T): T = x
|
||||||
|
class Box<T>(val value: T)
|
||||||
|
|
||||||
|
Type arguments are usually inferred at call sites:
|
||||||
|
|
||||||
|
val b = Box(10) // Box<Int>
|
||||||
|
val s = id("ok") // T is String
|
||||||
|
|
||||||
|
# Bounds
|
||||||
|
|
||||||
|
Use `:` to set bounds. Bounds may be unions (`|`) or intersections (`&`):
|
||||||
|
|
||||||
|
fun sum<T: Int | Real>(x: T, y: T) = x + y
|
||||||
|
class Named<T: Iterable & Comparable>(val data: T)
|
||||||
|
|
||||||
|
Bounds are checked at compile time. For union bounds, the argument must fit at least one option. For intersection bounds, it must fit all options.
|
||||||
|
|
||||||
|
# Variance
|
||||||
|
|
||||||
|
Generic types are invariant by default. You can specify declaration-site variance:
|
||||||
|
|
||||||
|
class Source<out T>(val value: T)
|
||||||
|
class Sink<in T> { fun accept(x: T) { ... } }
|
||||||
|
|
||||||
|
`out` makes the type covariant (produced), `in` makes it contravariant (consumed).
|
||||||
|
|
||||||
|
# Type aliases
|
||||||
|
|
||||||
|
Type aliases name type expressions (including unions/intersections):
|
||||||
|
|
||||||
|
type Num = Int | Real
|
||||||
|
type AB = A & B
|
||||||
|
|
||||||
|
Aliases can be generic and can use bounds and defaults:
|
||||||
|
|
||||||
|
type Maybe<T> = T?
|
||||||
|
type IntList<T: Int> = List<T>
|
||||||
|
|
||||||
|
Aliases expand to their underlying type expressions. They can be used anywhere a type expression is expected.
|
||||||
|
|
||||||
|
# Inference rules
|
||||||
|
|
||||||
|
- Literals set obvious types (`1` is `Int`, `1.0` is `Real`, etc.).
|
||||||
|
- Empty list literals default to `List<Object>` unless constrained by context.
|
||||||
|
- Non-empty list literals infer element type as a union of element types.
|
||||||
|
- Map literals infer key and value types; named keys are `String`.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
val a = [1, 2, 3] // List<Int>
|
||||||
|
val b = [1, "two", true] // List<Int | String | Bool>
|
||||||
|
val c: List<Int> = [] // List<Int>
|
||||||
|
|
||||||
|
val m1 = { "a": 1, "b": 2 } // Map<String, Int>
|
||||||
|
val m2 = { "a": 1, "b": "x" } // Map<String, Int | String>
|
||||||
|
val m3 = { ...m1, "c": true } // Map<String, Int | Bool>
|
||||||
|
|
||||||
|
Map spreads carry key/value types when possible.
|
||||||
|
|
||||||
|
Spreads propagate element type when possible:
|
||||||
|
|
||||||
|
val base = [1, 2]
|
||||||
|
val mix = [...base, 3] // List<Int>
|
||||||
|
|
||||||
|
# Type expressions
|
||||||
|
|
||||||
|
Type expressions include simple types, generics, unions, and intersections:
|
||||||
|
|
||||||
|
Int
|
||||||
|
List<String>
|
||||||
|
Int | String
|
||||||
|
Iterable & Comparable
|
||||||
|
|
||||||
|
These type expressions can appear in casts and `is` checks.
|
||||||
|
|
||||||
|
# `is`, `in`, and `==` with type expressions
|
||||||
|
|
||||||
|
There are two categories of `is` checks:
|
||||||
|
|
||||||
|
1) Value checks: `x is T`
|
||||||
|
- `x` is a value, `T` is a type expression.
|
||||||
|
- This is a runtime instance check.
|
||||||
|
|
||||||
|
2) Type checks: `T1 is T2`
|
||||||
|
- both sides are type expressions (class objects or unions/intersections).
|
||||||
|
- This is a *type-subset* check: every value of `T1` must fit in `T2`.
|
||||||
|
|
||||||
|
Exact type expression equality uses `==` and is structural (union/intersection order does not matter).
|
||||||
|
|
||||||
|
Includes checks use `in` with type expressions:
|
||||||
|
|
||||||
|
A in T
|
||||||
|
|
||||||
|
This means `A` is a subset of `T` (the same relation as `A is T`).
|
||||||
|
|
||||||
|
Examples (T = A | B):
|
||||||
|
|
||||||
|
T == A // false
|
||||||
|
T is A // false
|
||||||
|
A in T // true
|
||||||
|
B in T // true
|
||||||
|
T is A | B // true
|
||||||
|
|
||||||
|
# Practical examples
|
||||||
|
|
||||||
|
fun acceptInts<T: Int>(xs: List<T>) { }
|
||||||
|
acceptInts([1, 2, 3])
|
||||||
|
// acceptInts([1, "a"]) -> compile-time error
|
||||||
|
|
||||||
|
fun f<T>(list: List<T>) {
|
||||||
|
assert( T is Int | String | Bool )
|
||||||
|
assert( !(T is Int) )
|
||||||
|
assert( Int in T )
|
||||||
|
}
|
||||||
|
f([1, "two", true])
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
- `T` is reified as a type expression when needed (e.g., union/intersection). When it is a single class, `T` is that class object.
|
||||||
|
- Type expression checks are compile-time where possible; runtime checks only happen for `is` on values and explicit casts.
|
||||||
@ -4,15 +4,21 @@
|
|||||||
test the Lyng way. It is not meant to be effective.
|
test the Lyng way. It is not meant to be effective.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fun naiveCountHappyNumbers() {
|
fun naiveCountHappyNumbers(): Int {
|
||||||
var count = 0
|
var count = 0
|
||||||
for( n1 in 0..9 )
|
for( n1 in 0..9 ) {
|
||||||
for( n2 in 0..9 )
|
for( n2 in 0..9 ) {
|
||||||
for( n3 in 0..9 )
|
for( n3 in 0..9 ) {
|
||||||
for( n4 in 0..9 )
|
for( n4 in 0..9 ) {
|
||||||
for( n5 in 0..9 )
|
for( n5 in 0..9 ) {
|
||||||
for( n6 in 0..9 )
|
for( n6 in 0..9 ) {
|
||||||
if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
|
if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,4 +34,3 @@ for( r in 1..900 ) {
|
|||||||
assert( found == 55252 )
|
assert( found == 55252 )
|
||||||
delay(0.05)
|
delay(0.05)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
# Scopes and Closures: resolution and safety
|
# Scopes and Closures: resolution and safety
|
||||||
|
|
||||||
|
Attention to AI: name lookup is ibsolete and must not be used with bytecode compiler
|
||||||
|
|
||||||
This page documents how name resolution works with `ClosureScope`, how to avoid recursion pitfalls, and how to safely capture and execute callbacks that need access to outer locals.
|
This page documents how name resolution works with `ClosureScope`, how to avoid recursion pitfalls, and how to safely capture and execute callbacks that need access to outer locals.
|
||||||
|
|
||||||
## Why this matters
|
## Why this matters
|
||||||
|
|||||||
168
docs/tutorial.md
168
docs/tutorial.md
@ -10,6 +10,7 @@ __Other documents to read__ maybe after this one:
|
|||||||
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
||||||
- [math in Lyng](math.md), [the `when` statement](when.md), [return statement](return_statement.md)
|
- [math in Lyng](math.md), [the `when` statement](when.md), [return statement](return_statement.md)
|
||||||
- [Testing and Assertions](Testing.md)
|
- [Testing and Assertions](Testing.md)
|
||||||
|
- [Generics and type expressions](generics.md)
|
||||||
- [time](time.md) and [parallelism](parallelism.md)
|
- [time](time.md) and [parallelism](parallelism.md)
|
||||||
- [parallelism] - multithreaded code, coroutines, etc.
|
- [parallelism] - multithreaded code, coroutines, etc.
|
||||||
- Some class
|
- Some class
|
||||||
@ -106,6 +107,23 @@ Singleton objects are declared using the `object` keyword. They define a class a
|
|||||||
|
|
||||||
Logger.log("Hello singleton!")
|
Logger.log("Hello singleton!")
|
||||||
|
|
||||||
|
## Nested Declarations (short)
|
||||||
|
|
||||||
|
Classes, objects, and enums can be declared inside another class. They live in the class namespace (no outer instance capture), so you access them with a qualifier:
|
||||||
|
|
||||||
|
class A {
|
||||||
|
class B(x?)
|
||||||
|
object Inner { val foo = "bar" }
|
||||||
|
enum E* { One, Two }
|
||||||
|
}
|
||||||
|
|
||||||
|
val ab = A.B()
|
||||||
|
assertEquals(ab.x, null)
|
||||||
|
assertEquals(A.Inner.foo, "bar")
|
||||||
|
assertEquals(A.One, A.E.One)
|
||||||
|
|
||||||
|
See [OOP notes](OOP.md#nested-declarations) for rules, visibility, and enum lifting details.
|
||||||
|
|
||||||
## Delegation (briefly)
|
## Delegation (briefly)
|
||||||
|
|
||||||
You can delegate properties and functions to other objects using the `by` keyword. This is perfect for patterns like `lazy` initialization.
|
You can delegate properties and functions to other objects using the `by` keyword. This is perfect for patterns like `lazy` initialization.
|
||||||
@ -224,6 +242,9 @@ This also prevents chain assignments so use parentheses:
|
|||||||
|
|
||||||
## Nullability
|
## Nullability
|
||||||
|
|
||||||
|
Nullability is part of the type. `String` is non-null, `String?` is nullable. Use `!!` to assert non-null and throw
|
||||||
|
`NullReferenceException` if the value is `null`.
|
||||||
|
|
||||||
When the value is `null`, it might throws `NullReferenceException`, the name is somewhat a tradition. To avoid it
|
When the value is `null`, it might throws `NullReferenceException`, the name is somewhat a tradition. To avoid it
|
||||||
one can check it against null or use _null coalescing_. The null coalescing means, if the operand (left) is null,
|
one can check it against null or use _null coalescing_. The null coalescing means, if the operand (left) is null,
|
||||||
the operation won't be performed and the result will be null. Here is the difference:
|
the operation won't be performed and the result will be null. Here is the difference:
|
||||||
@ -242,6 +263,9 @@ the operation won't be performed and the result will be null. Here is the differ
|
|||||||
assert( ref?() == null )
|
assert( ref?() == null )
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
Note: `?.` is still a typed operation. The receiver must have a compile-time type that declares the member; if the
|
||||||
|
receiver is `Object`, cast it first or declare a more specific type.
|
||||||
|
|
||||||
There is also "elvis operator", null-coalesce infix operator '?:' that returns rvalue if lvalue is `null`:
|
There is also "elvis operator", null-coalesce infix operator '?:' that returns rvalue if lvalue is `null`:
|
||||||
|
|
||||||
null ?: "nothing"
|
null ?: "nothing"
|
||||||
@ -425,8 +449,6 @@ Almost the same, using `val`:
|
|||||||
val foo = 1
|
val foo = 1
|
||||||
foo += 1 // this will throw exception
|
foo += 1 // this will throw exception
|
||||||
|
|
||||||
# Constants
|
|
||||||
|
|
||||||
Same as in kotlin:
|
Same as in kotlin:
|
||||||
|
|
||||||
val HalfPi = π / 2
|
val HalfPi = π / 2
|
||||||
@ -434,6 +456,144 @@ Same as in kotlin:
|
|||||||
Note using greek characters in identifiers! All letters allowed, but remember who might try to read your script, most
|
Note using greek characters in identifiers! All letters allowed, but remember who might try to read your script, most
|
||||||
likely will know some English, the rest is the pure uncertainty.
|
likely will know some English, the rest is the pure uncertainty.
|
||||||
|
|
||||||
|
# Types and inference
|
||||||
|
|
||||||
|
Lyng uses Kotlin-style static types with inference. You can always write explicit types, but in most places the compiler
|
||||||
|
can infer them from literals, defaults, and flow analysis.
|
||||||
|
|
||||||
|
## Type annotations
|
||||||
|
|
||||||
|
Use `:` to specify a type:
|
||||||
|
|
||||||
|
var x: Int = 10
|
||||||
|
val label: String = "count"
|
||||||
|
fun clamp(x: Int, min: Int, max: Int): Int { ... }
|
||||||
|
|
||||||
|
`Object` is the top type. If you omit a type and there is no default value, the parameter is `Object` by default:
|
||||||
|
|
||||||
|
fun show(x) { println(x) } // x is Object
|
||||||
|
|
||||||
|
For nullable types, add `?`:
|
||||||
|
|
||||||
|
fun showMaybe(x: Object?) { ... }
|
||||||
|
fun parseInt(s: String?): Int? { ... }
|
||||||
|
|
||||||
|
There is also a nullable shorthand for untyped parameters and constructor args: `x?` means `x: Object?`.
|
||||||
|
It cannot be combined with an explicit type annotation.
|
||||||
|
|
||||||
|
class A(x?) { ... } // x: Object?
|
||||||
|
fun f(x?) { x == null } // x: Object?
|
||||||
|
|
||||||
|
Type aliases name type expressions and can be generic:
|
||||||
|
|
||||||
|
type Num = Int | Real
|
||||||
|
type Maybe<T> = T?
|
||||||
|
|
||||||
|
Aliases expand to their underlying type expressions. See `docs/generics.md` for details.
|
||||||
|
|
||||||
|
`void` is a singleton value of the class `Void`. `Void` can be used as an explicit return type:
|
||||||
|
|
||||||
|
fun log(msg): Void { println(msg); void }
|
||||||
|
|
||||||
|
`Null` is the class of `null`. It is a singleton type and mostly useful for type inference results.
|
||||||
|
|
||||||
|
## Type inference
|
||||||
|
|
||||||
|
The compiler infers types from:
|
||||||
|
|
||||||
|
- literals: `1` is `Int`, `1.0` is `Real`, `"s"` is `String`, `'c'` is `Char`
|
||||||
|
- defaults: `fun f(x=1, name="n")` infers `x: Int`, `name: String`
|
||||||
|
- assignments: `val x = call()` uses the return type of `call`
|
||||||
|
- returns and branches: the result type of a block is the last expression, and if any branch is nullable,
|
||||||
|
the inferred type becomes nullable
|
||||||
|
- numeric ops: `Int` and `Real` stay `Int` when both sides are `Int`, and promote to `Real` on mixed arithmetic
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
fun inc(x=0) = x + 1 // (Int)->Int
|
||||||
|
fun maybe(flag) { if(flag) 1 else null } // ()->Int?
|
||||||
|
|
||||||
|
Untyped locals are allowed, but their type is fixed on the first assignment:
|
||||||
|
|
||||||
|
var x
|
||||||
|
x = 1 // x becomes Int
|
||||||
|
x = "one" // compile-time error
|
||||||
|
|
||||||
|
var y = null // y is Object?
|
||||||
|
val z = null // z is Null
|
||||||
|
|
||||||
|
Empty list/map literals default to `List<Object>` and `Map<Object,Object>` until a more specific type is known:
|
||||||
|
|
||||||
|
val xs = [] // List<Object>
|
||||||
|
val ys: List<Int> = [] // List<Int>
|
||||||
|
|
||||||
|
Map literals infer key/value types from entries; named keys are `String`. See `docs/generics.md` for details.
|
||||||
|
|
||||||
|
## Flow analysis
|
||||||
|
|
||||||
|
Lyng uses flow analysis to narrow types inside branches:
|
||||||
|
|
||||||
|
fun len(x: String?): Int {
|
||||||
|
if( x == null ) return 0
|
||||||
|
// x is String (non-null) in this branch
|
||||||
|
return x.length
|
||||||
|
}
|
||||||
|
|
||||||
|
`is` checks and `when` branches also narrow types:
|
||||||
|
|
||||||
|
fun kind(x: Object) {
|
||||||
|
if( x is Int ) return "int"
|
||||||
|
if( x is String ) return "string"
|
||||||
|
return "other"
|
||||||
|
}
|
||||||
|
|
||||||
|
Narrowing is local to the branch; after the branch, the original type is restored.
|
||||||
|
|
||||||
|
## Casts and unknown types
|
||||||
|
|
||||||
|
Use `as` for explicit casts. The compiler inserts casts only when it can be valid and necessary. If a cast fails at
|
||||||
|
runtime, it throws `ClassCastException`. If the value is nullable, `as T` implies a non-null assertion.
|
||||||
|
|
||||||
|
Member access is resolved at compile time. Only members of `Object` are available on unknown types; non-Object members
|
||||||
|
require an explicit cast:
|
||||||
|
|
||||||
|
fun f(x) { // x is Object
|
||||||
|
x.toString() // ok (Object member)
|
||||||
|
x.size() // compile-time error
|
||||||
|
(x as List).size() // ok
|
||||||
|
}
|
||||||
|
|
||||||
|
This avoids runtime name-resolution fallbacks; all symbols must be known at compile time.
|
||||||
|
|
||||||
|
## Generics and bounds
|
||||||
|
|
||||||
|
Generic parameters are declared with `<...>`:
|
||||||
|
|
||||||
|
fun id<T>(x: T): T = x
|
||||||
|
class Box<T>(val value: T)
|
||||||
|
|
||||||
|
Bounds use `:` and can combine with `&` (intersection) and `|` (union):
|
||||||
|
|
||||||
|
fun sum<T: Int | Real>(x: T, y: T) = x + y
|
||||||
|
class Named<T: Iterable & Comparable>(val data: T)
|
||||||
|
|
||||||
|
Type arguments are usually inferred from call sites:
|
||||||
|
|
||||||
|
val b = Box(10) // Box<Int>
|
||||||
|
val s = id("ok") // T is String
|
||||||
|
|
||||||
|
See [Generics and type expressions](generics.md) for bounds, unions/intersections, and type-checking rules.
|
||||||
|
|
||||||
|
## Variance
|
||||||
|
|
||||||
|
Generic types are invariant by default, so `List<Int>` is not a `List<Object>`.
|
||||||
|
Use declaration-site variance when you need it:
|
||||||
|
|
||||||
|
class Source<out T>(val value: T)
|
||||||
|
class Sink<in T> { fun accept(x: T) { ... } }
|
||||||
|
|
||||||
|
`out` makes the type covariant (only produced), `in` makes it contravariant (only consumed).
|
||||||
|
|
||||||
# Defining functions
|
# Defining functions
|
||||||
|
|
||||||
fun check(amount) {
|
fun check(amount) {
|
||||||
@ -579,6 +739,7 @@ See also: [Testing and Assertions](Testing.md)
|
|||||||
var result = []
|
var result = []
|
||||||
for( x in iterable ) result += transform(x)
|
for( x in iterable ) result += transform(x)
|
||||||
}
|
}
|
||||||
|
// loop variables are read-only inside the loop body
|
||||||
assert( [11, 21, 31] == mapValues( [1,2,3], { it*10+1 }))
|
assert( [11, 21, 31] == mapValues( [1,2,3], { it*10+1 }))
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
@ -1330,7 +1491,7 @@ than enum arrays, until `Lynon.encodeTyped` will be implemented.
|
|||||||
var result = null // here we will store the result
|
var result = null // here we will store the result
|
||||||
>>> null
|
>>> null
|
||||||
|
|
||||||
# Integral data types
|
# Built-in types
|
||||||
|
|
||||||
| type | description | literal samples |
|
| type | description | literal samples |
|
||||||
|--------|---------------------------------|---------------------|
|
|--------|---------------------------------|---------------------|
|
||||||
@ -1340,6 +1501,7 @@ than enum arrays, until `Lynon.encodeTyped` will be implemented.
|
|||||||
| Char | single unicode character | `'S'`, `'\n'` |
|
| Char | single unicode character | `'S'`, `'\n'` |
|
||||||
| String | unicode string, no limits | "hello" (see below) |
|
| String | unicode string, no limits | "hello" (see below) |
|
||||||
| List | mutable list | [1, "two", 3] |
|
| List | mutable list | [1, "two", 3] |
|
||||||
|
| Object | top type for all values | |
|
||||||
| Void | no value could exist, singleton | void |
|
| Void | no value could exist, singleton | void |
|
||||||
| Null | missing value, singleton | null |
|
| Null | missing value, singleton | null |
|
||||||
| Fn | callable type | |
|
| Fn | callable type | |
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
# What's New in Lyng
|
# What's New in Lyng
|
||||||
|
|
||||||
This document highlights the latest additions and improvements to the Lyng language and its ecosystem.
|
This document highlights the latest additions and improvements to the Lyng language and its ecosystem.
|
||||||
|
For a programmer-focused migration summary, see `docs/whats_new_1_5.md`.
|
||||||
|
|
||||||
## Language Features
|
## Language Features
|
||||||
|
|
||||||
@ -101,13 +102,31 @@ Singleton objects are declared using the `object` keyword. They provide a conven
|
|||||||
|
|
||||||
```lyng
|
```lyng
|
||||||
object Config {
|
object Config {
|
||||||
val version = "1.2.3"
|
val version = "1.5.0-SNAPSHOT"
|
||||||
fun show() = println("Config version: " + version)
|
fun show() = println("Config version: " + version)
|
||||||
}
|
}
|
||||||
|
|
||||||
Config.show()
|
Config.show()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Nested Declarations and Lifted Enums
|
||||||
|
You can now declare classes, objects, enums, and type aliases inside another class. These nested declarations live in the class namespace (no outer instance capture) and are accessed with a qualifier.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class A {
|
||||||
|
class B(x?)
|
||||||
|
object Inner { val foo = "bar" }
|
||||||
|
enum E* { One, Two }
|
||||||
|
}
|
||||||
|
|
||||||
|
val ab = A.B()
|
||||||
|
assertEquals(ab.x, null)
|
||||||
|
assertEquals(A.Inner.foo, "bar")
|
||||||
|
assertEquals(A.One, A.E.One)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `*` on `enum E*` lifts entries into the enclosing class namespace (compile-time error on ambiguity).
|
||||||
|
|
||||||
### Object Expressions
|
### Object Expressions
|
||||||
You can now create anonymous objects that inherit from classes or interfaces using the `object : Base { ... }` syntax. These expressions capture their lexical scope and support multiple inheritance.
|
You can now create anonymous objects that inherit from classes or interfaces using the `object : Base { ... }` syntax. These expressions capture their lexical scope and support multiple inheritance.
|
||||||
|
|
||||||
@ -224,3 +243,11 @@ You can enable it in **Settings | Lyng Formatter | Enable Lyng autocompletion**.
|
|||||||
|
|
||||||
### Kotlin API: Exception Handling
|
### Kotlin API: Exception Handling
|
||||||
The `Obj.getLyngExceptionMessageWithStackTrace()` extension method has been added to simplify retrieving detailed error information from Lyng exception objects in Kotlin. Additionally, `getLyngExceptionMessage()` and `raiseAsExecutionError()` now accept an optional `Scope`, making it easier to use them when a scope is not immediately available.
|
The `Obj.getLyngExceptionMessageWithStackTrace()` extension method has been added to simplify retrieving detailed error information from Lyng exception objects in Kotlin. Additionally, `getLyngExceptionMessage()` and `raiseAsExecutionError()` now accept an optional `Scope`, making it easier to use them when a scope is not immediately available.
|
||||||
|
|
||||||
|
### Kotlin API: Bridge Reflection and Class Binding (Preferred Extensions)
|
||||||
|
Lyng now provides a public Kotlin reflection bridge and a Lyng‑first class binding workflow. This is the **preferred** way to write Kotlin extensions and library integrations:
|
||||||
|
|
||||||
|
- **Bridge resolver**: explicit handles for values, vars, and callables with predictable lookup rules.
|
||||||
|
- **Class bridge binding**: declare classes/members in Lyng (marked `extern`) and bind the implementations in Kotlin before the first instance is created.
|
||||||
|
|
||||||
|
See **Embedding Lyng** for full samples and usage details.
|
||||||
|
|||||||
133
docs/whats_new_1_3.md
Normal file
133
docs/whats_new_1_3.md
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# What's New in Lyng 1.3 (vs 1.2.* / master)
|
||||||
|
|
||||||
|
This is a programmer-focused summary of what changed since the 1.2.* line on `master`. It highlights new language and type-system features, runtime/IDE improvements, and how to migrate code safely.
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
|
||||||
|
- Generics are now a first-class part of the type system, with bounds, variance, unions, and intersections.
|
||||||
|
- Type aliases and type-expression checks (`T1 is T2`, `A in T`) enable richer static modeling.
|
||||||
|
- Nested declarations inside classes, plus lifted enum entries via `enum E*`.
|
||||||
|
- Stepped ranges (`step`) including iterable open-ended and real ranges.
|
||||||
|
- Runtime and compiler speedups: more bytecode coverage, direct slot access, call-site caching.
|
||||||
|
|
||||||
|
## Language and type system
|
||||||
|
|
||||||
|
### Generics, bounds, and variance
|
||||||
|
|
||||||
|
You can declare generic functions/classes with `<...>`, restrict them with bounds, and control variance.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun id<T>(x: T): T = x
|
||||||
|
class Box<out T>(val value: T)
|
||||||
|
|
||||||
|
fun sum<T: Int | Real>(x: T, y: T) = x + y
|
||||||
|
class Named<T: Iterable & Comparable>(val data: T)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type aliases and type expressions
|
||||||
|
|
||||||
|
Type aliases can name any type expression, including unions and intersections.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
type Num = Int | Real
|
||||||
|
type Maybe<T> = T?
|
||||||
|
```
|
||||||
|
|
||||||
|
Type expressions can be checked directly:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun f<T>(xs: List<T>) {
|
||||||
|
assert( T is Int | String | Bool ) // type-subset check
|
||||||
|
assert( Int in T ) // same relation as `Int is T`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Value checks remain `x is T`. Type expression equality uses `==` and is structural.
|
||||||
|
|
||||||
|
### Nullable shorthand for parameters
|
||||||
|
|
||||||
|
Untyped parameters and constructor args can use `x?` as shorthand for `x: Object?`:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class A(x?) { ... }
|
||||||
|
fun f(x?) { x == null }
|
||||||
|
```
|
||||||
|
|
||||||
|
### List/map literal inference
|
||||||
|
|
||||||
|
The compiler now infers element and key/value types from literals and spreads. Mixed element types produce unions.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
val a = [1, 2, 3] // List<Int>
|
||||||
|
val b = [1, "two", true] // List<Int | String | Bool>
|
||||||
|
val m = { "a": 1, "b": "x" } // Map<String, Int | String>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile-time member access only
|
||||||
|
|
||||||
|
Member access is resolved at compile time. On unknown types, only `Object` members are visible; other members require an explicit cast.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun f(x) { // x: Object
|
||||||
|
x.toString() // ok
|
||||||
|
x.size() // compile-time error
|
||||||
|
(x as List).size()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This removes runtime name-resolution fallbacks and makes errors deterministic.
|
||||||
|
|
||||||
|
### Nested declarations and lifted enums
|
||||||
|
|
||||||
|
Classes, objects, enums, and type aliases can be declared inside another class and accessed by qualifier. Enums can lift entries into the outer namespace with `*`.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class A {
|
||||||
|
class B(x?)
|
||||||
|
object Inner { val foo = "bar" }
|
||||||
|
type Alias = B
|
||||||
|
enum E* { One, Two }
|
||||||
|
}
|
||||||
|
|
||||||
|
val b = A.B()
|
||||||
|
assertEquals(A.One, A.E.One)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stepped ranges
|
||||||
|
|
||||||
|
Ranges now support `step`, and open-ended/real ranges are iterable only with an explicit step.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
(1..5 step 2).toList() // [1,3,5]
|
||||||
|
(0.0..1.0 step 0.25).toList() // [0,0.25,0.5,0.75,1.0]
|
||||||
|
(0.. step 1).take(3).toList() // [0,1,2]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tooling and performance
|
||||||
|
|
||||||
|
- Bytecode compiler/VM coverage expanded (loops, expressions, calls), improving execution speed and consistency.
|
||||||
|
- Direct frame-slot access and scoped slot addressing reduce lookup overhead, including in closures.
|
||||||
|
- Call-site caching and numeric fast paths reduce hot-loop overhead.
|
||||||
|
- IDE tooling updated for the new type system and nested declarations; MiniAst-based completion work continues.
|
||||||
|
|
||||||
|
## Migration guide (from 1.2.*)
|
||||||
|
|
||||||
|
1) Replace dynamic member access on unknown types
|
||||||
|
- If you relied on runtime name resolution, add explicit casts or annotate types so the compiler can resolve members.
|
||||||
|
|
||||||
|
2) Adopt new type-system constructs where helpful
|
||||||
|
- Consider `type` aliases for complex unions/intersections.
|
||||||
|
- Prefer generic signatures over `Object` when the API is parametric.
|
||||||
|
|
||||||
|
3) Update range iteration where needed
|
||||||
|
- Use `step` for open-ended or real ranges you want to iterate.
|
||||||
|
|
||||||
|
4) Nullable shorthand is optional
|
||||||
|
- If you used untyped nullable params, you can keep `x` (Object) or switch to `x?` (Object?) for clarity.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `docs/generics.md`
|
||||||
|
- `docs/Range.md`
|
||||||
|
- `docs/OOP.md`
|
||||||
|
- `docs/BytecodeSpec.md`
|
||||||
140
docs/whats_new_1_5.md
Normal file
140
docs/whats_new_1_5.md
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# What's New in Lyng 1.5 (vs 1.3.* / master)
|
||||||
|
|
||||||
|
This document summarizes the significant changes and new features introduced in the 1.5 development cycle.
|
||||||
|
|
||||||
|
## Principal changes
|
||||||
|
|
||||||
|
### JIT compiler and compile-time types and symbols.
|
||||||
|
|
||||||
|
This major improvement gives the following big advantages:
|
||||||
|
|
||||||
|
- **Blazing Fast execution**: several times faster! (three to six times speedup in different scenarios).
|
||||||
|
- **Better IDE support**: autocompletion, early error detection, types check.
|
||||||
|
- **Error safety**: all symbols and types are checked at bound at compile-time. Many errors are detected earlier. Also, no risk that external or caller code would shadow some internally used symbols (especially in closures and inheritance).
|
||||||
|
|
||||||
|
In particular, it means no slow and flaky runtime lookups. Once compiled, code guarantees that it will always call the symbol known at compile-time; runtime name lookup though does not guarantee it and can be source of hard to trace bugs.
|
||||||
|
|
||||||
|
### New stable API to create Kotlin extensions
|
||||||
|
|
||||||
|
The API is fixed and will be kept with further Lyng core changes. It is now the recommended way to write Lyng extensions in Kotlin. It is much simpler and more elegant than the internal one. See [Kotlin Bridge Binding](../notes/kotlin_bridge_binding.md).
|
||||||
|
|
||||||
|
### Smart types system
|
||||||
|
|
||||||
|
- **Deep inference**: The compiler analyzes types of symbols along the execution path and in many cases eliminates unnecessary casts or type specifications.
|
||||||
|
- **Union and intersection types**: `A & B`, `A | B`.
|
||||||
|
- **Generics**: Generic types are first-class citizens with support for [bounds and variance](generics.md). No type erasure: in a generic function you can, for example, check `A in T`, where T is the generic type.
|
||||||
|
- **Inner classes and enums**: Full support for nested declarations, including [Enums with lifting](OOP.md#lifted-enum-entries).
|
||||||
|
|
||||||
|
## Other highlights
|
||||||
|
|
||||||
|
- **The `return` Statement**: Added support for local and non-local returns using labels.
|
||||||
|
- **Abstract Classes and Interfaces**: Full support for `abstract` members and the `interface` keyword.
|
||||||
|
- **Singleton Objects**: Define singletons using the `object` keyword or use anonymous object expressions.
|
||||||
|
- **Multiple Inheritance**: Enhanced multi-inheritance with predictable [C3 MRO resolution](OOP.md#multiple-inheritance-and-mro).
|
||||||
|
- **Unified Delegation**: Powerful delegation model for `val`, `var`, and `fun` members. See [Delegation](delegation.md).
|
||||||
|
- **Class Properties with Accessors**: Define `val` and `var` properties with custom `get()` and `set()`.
|
||||||
|
- **Restricted Setter Visibility**: Use `private set` and `protected set` on fields and properties.
|
||||||
|
- **Late-initialized `val`**: Support for `val` fields that are initialized in `init` blocks or class bodies.
|
||||||
|
- **Transient Members**: Use `@Transient` to exclude members from serialization and equality checks.
|
||||||
|
- **Named Arguments and Splats**: Improved call-site readability with `name: value` and map-based splats.
|
||||||
|
- **Refined Visibility**: Improved `protected` access and `closed` modifier for better encapsulation.
|
||||||
|
|
||||||
|
## Language Features
|
||||||
|
|
||||||
|
### The `return` Statement
|
||||||
|
You can now exit from the innermost enclosing callable (function or lambda) using `return`. Lyng also supports non-local returns to outer scopes using labels. See [Return Statement](return_statement.md).
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun findFirst<T>(list: Iterable<T>, predicate: (T)->Bool): T? {
|
||||||
|
list.forEach {
|
||||||
|
if (predicate(it)) return@findFirst it
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Abstract Classes and Interfaces
|
||||||
|
Lyng now supports the `abstract` modifier for classes and their members. `interface` is introduced as a synonym for `abstract class`, allowing for rich multi-inheritance patterns.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
interface Shape {
|
||||||
|
abstract val area: Real
|
||||||
|
fun describe() = "Area: %g"(area)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Circle(val radius: Real) : Shape {
|
||||||
|
override val area get = Math.PI * radius * radius
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Class Properties with Accessors
|
||||||
|
Properties can now have custom getters and setters. They do not have automatic backing fields, making them perfect for computed values or delegation.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class Rectangle(var width: Real, var height: Real) {
|
||||||
|
val area: Real get() = width * height
|
||||||
|
|
||||||
|
var squareSize: Real
|
||||||
|
get() = area
|
||||||
|
set(v) {
|
||||||
|
width = sqrt(v)
|
||||||
|
height = width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Singleton Objects
|
||||||
|
Declare singletons or anonymous objects easily.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
object Database {
|
||||||
|
val connection = "connected"
|
||||||
|
}
|
||||||
|
|
||||||
|
val runner = object : Runnable {
|
||||||
|
override fun run() = println("Running!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Named Arguments and Named Splats
|
||||||
|
Improve call-site clarity by specifying argument names. You can also expand a Map into named arguments using the splat operator.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun configure(timeout: Int, retry: Int = 3) { ... }
|
||||||
|
|
||||||
|
configure(timeout: 5000, retry: 5)
|
||||||
|
val options = Map("timeout": 1000, "retry": 1)
|
||||||
|
configure(...options)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modern Operators
|
||||||
|
The `?=` operator allows for concise "assign if null" logic.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
var cache: Map? = null
|
||||||
|
cache ?= Map("status": "ok") // Only assigns if cache is null
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tooling and IDE
|
||||||
|
|
||||||
|
- **IDEA Plugin**: Significant improvements to autocompletion, documentation tooltips, and natural language support (Grazie integration).
|
||||||
|
- **CLI**: The `lyng fmt` command is now a first-class tool for formatting code with various options like `--check` and `--in-place`.
|
||||||
|
- **Performance**: Ongoing optimizations in the bytecode VM and compiler for faster execution and smaller footprint.
|
||||||
|
|
||||||
|
## Standard Library
|
||||||
|
- **`with(self, block)`**: Scoped execution with a dedicated `self`.
|
||||||
|
- **`clamp(value, min, max)`**: Easily restrict values to a range.
|
||||||
|
|
||||||
|
## Migration Guide (from 1.3.*)
|
||||||
|
|
||||||
|
1. **Check Visibility**: Refined `protected` and `private` rules may catch previously undetected invalid accesses.
|
||||||
|
2. **Override Keyword**: Ensure all members that override ancestor declarations are marked with the `override` keyword (now mandatory).
|
||||||
|
3. **Return in Shorthand**: Remember that `return` is forbidden in `=` shorthand functions; use block syntax if you need early exit.
|
||||||
|
4. **Empty Map Literals**: Use `Map()` or `{:}` for empty maps, as `{}` is now strictly a block/lambda.
|
||||||
|
|
||||||
|
## References
|
||||||
|
- [Object Oriented Programming](OOP.md)
|
||||||
|
- [Generics](generics.md)
|
||||||
|
- [Return Statement](return_statement.md)
|
||||||
|
- [Delegation](delegation.md)
|
||||||
|
- [Tutorial](tutorial.md)
|
||||||
@ -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");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -16,7 +16,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
#Gradle
|
#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.caching=true
|
||||||
org.gradle.configuration-cache=true
|
org.gradle.configuration-cache=true
|
||||||
#Kotlin
|
#Kotlin
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import kotlinx.coroutines.launch
|
|||||||
import net.sergeych.lyng.ExecutionError
|
import net.sergeych.lyng.ExecutionError
|
||||||
import net.sergeych.lyng.Script
|
import net.sergeych.lyng.Script
|
||||||
import net.sergeych.lyng.Source
|
import net.sergeych.lyng.Source
|
||||||
|
import net.sergeych.lyng.requireScope
|
||||||
import net.sergeych.lyng.idea.LyngIcons
|
import net.sergeych.lyng.idea.LyngIcons
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
import net.sergeych.lyng.obj.getLyngExceptionMessageWithStackTrace
|
import net.sergeych.lyng.obj.getLyngExceptionMessageWithStackTrace
|
||||||
@ -81,7 +82,7 @@ class RunLyngScriptAction : AnAction(LyngIcons.FILE) {
|
|||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
for ((i, arg) in args.list.withIndex()) {
|
for ((i, arg) in args.list.withIndex()) {
|
||||||
if (i > 0) sb.append(" ")
|
if (i > 0) sb.append(" ")
|
||||||
sb.append(arg.toString(this).value)
|
sb.append(arg.toString(requireScope()).value)
|
||||||
}
|
}
|
||||||
console.print(sb.toString(), ConsoleViewContentType.NORMAL_OUTPUT)
|
console.print(sb.toString(), ConsoleViewContentType.NORMAL_OUTPUT)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
@ -90,7 +91,7 @@ class RunLyngScriptAction : AnAction(LyngIcons.FILE) {
|
|||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
for ((i, arg) in args.list.withIndex()) {
|
for ((i, arg) in args.list.withIndex()) {
|
||||||
if (i > 0) sb.append(" ")
|
if (i > 0) sb.append(" ")
|
||||||
sb.append(arg.toString(this).value)
|
sb.append(arg.toString(requireScope()).value)
|
||||||
}
|
}
|
||||||
console.print(sb.toString() + "\n", ConsoleViewContentType.NORMAL_OUTPUT)
|
console.print(sb.toString() + "\n", ConsoleViewContentType.NORMAL_OUTPUT)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
|
|||||||
@ -25,15 +25,13 @@ import com.intellij.openapi.progress.ProgressManager
|
|||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import net.sergeych.lyng.Source
|
|
||||||
import net.sergeych.lyng.binding.Binder
|
|
||||||
import net.sergeych.lyng.binding.SymbolKind
|
|
||||||
import net.sergeych.lyng.highlight.HighlightKind
|
import net.sergeych.lyng.highlight.HighlightKind
|
||||||
import net.sergeych.lyng.highlight.SimpleLyngHighlighter
|
|
||||||
import net.sergeych.lyng.highlight.offsetOf
|
import net.sergeych.lyng.highlight.offsetOf
|
||||||
import net.sergeych.lyng.idea.highlight.LyngHighlighterColors
|
import net.sergeych.lyng.idea.highlight.LyngHighlighterColors
|
||||||
import net.sergeych.lyng.idea.util.LyngAstManager
|
import net.sergeych.lyng.idea.util.LyngAstManager
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.tools.LyngDiagnosticSeverity
|
||||||
|
import net.sergeych.lyng.tools.LyngLanguageTools
|
||||||
|
import net.sergeych.lyng.tools.LyngSemanticKind
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExternalAnnotator that runs Lyng MiniAst on the document text in background
|
* ExternalAnnotator that runs Lyng MiniAst on the document text in background
|
||||||
@ -43,8 +41,8 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
data class Input(val text: String, val modStamp: Long, val previousSpans: List<Span>?, val file: PsiFile)
|
data class Input(val text: String, val modStamp: Long, val previousSpans: List<Span>?, val file: PsiFile)
|
||||||
|
|
||||||
data class Span(val start: Int, val end: Int, val key: com.intellij.openapi.editor.colors.TextAttributesKey)
|
data class Span(val start: Int, val end: Int, val key: com.intellij.openapi.editor.colors.TextAttributesKey)
|
||||||
data class Error(val start: Int, val end: Int, val message: String)
|
data class Diag(val start: Int, val end: Int, val message: String, val severity: HighlightSeverity)
|
||||||
data class Result(val modStamp: Long, val spans: List<Span>, val error: Error? = null)
|
data class Result(val modStamp: Long, val spans: List<Span>, val diagnostics: List<Diag> = emptyList())
|
||||||
|
|
||||||
override fun collectInformation(file: PsiFile): Input? {
|
override fun collectInformation(file: PsiFile): Input? {
|
||||||
val doc: Document = file.viewProvider.document ?: return null
|
val doc: Document = file.viewProvider.document ?: return null
|
||||||
@ -59,224 +57,46 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
if (collectedInfo == null) return null
|
if (collectedInfo == null) return null
|
||||||
ProgressManager.checkCanceled()
|
ProgressManager.checkCanceled()
|
||||||
val text = collectedInfo.text
|
val text = collectedInfo.text
|
||||||
val tokens = try { SimpleLyngHighlighter().highlight(text) } catch (_: Throwable) { emptyList() }
|
val analysis = LyngAstManager.getAnalysis(collectedInfo.file)
|
||||||
|
|
||||||
// Use LyngAstManager to get the (potentially merged) Mini-AST
|
|
||||||
val mini = LyngAstManager.getMiniAst(collectedInfo.file)
|
|
||||||
?: return Result(collectedInfo.modStamp, collectedInfo.previousSpans ?: emptyList())
|
?: return Result(collectedInfo.modStamp, collectedInfo.previousSpans ?: emptyList())
|
||||||
|
val mini = analysis.mini
|
||||||
|
|
||||||
ProgressManager.checkCanceled()
|
ProgressManager.checkCanceled()
|
||||||
val source = Source(collectedInfo.file.name, text)
|
|
||||||
|
|
||||||
val out = ArrayList<Span>(256)
|
val out = ArrayList<Span>(256)
|
||||||
|
val diags = ArrayList<Diag>()
|
||||||
fun isFollowedByParenOrBlock(rangeEnd: Int): Boolean {
|
|
||||||
var i = rangeEnd
|
|
||||||
while (i < text.length) {
|
|
||||||
val ch = text[i]
|
|
||||||
if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { i++; continue }
|
|
||||||
return ch == '(' || ch == '{'
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putRange(start: Int, end: Int, key: com.intellij.openapi.editor.colors.TextAttributesKey) {
|
fun putRange(start: Int, end: Int, key: com.intellij.openapi.editor.colors.TextAttributesKey) {
|
||||||
if (start in 0..end && end <= text.length && start < end) out += Span(start, end, key)
|
if (start in 0..end && end <= text.length && start < end) out += Span(start, end, key)
|
||||||
}
|
}
|
||||||
fun putName(startPos: net.sergeych.lyng.Pos, name: String, key: com.intellij.openapi.editor.colors.TextAttributesKey) {
|
|
||||||
val s = source.offsetOf(startPos)
|
fun keyForKind(kind: LyngSemanticKind): com.intellij.openapi.editor.colors.TextAttributesKey? = when (kind) {
|
||||||
putRange(s, (s + name.length).coerceAtMost(text.length), key)
|
LyngSemanticKind.Function -> LyngHighlighterColors.FUNCTION
|
||||||
}
|
LyngSemanticKind.Class, LyngSemanticKind.Enum, LyngSemanticKind.TypeAlias -> LyngHighlighterColors.TYPE
|
||||||
fun putMiniRange(r: MiniRange, key: com.intellij.openapi.editor.colors.TextAttributesKey) {
|
LyngSemanticKind.Value -> LyngHighlighterColors.VALUE
|
||||||
val s = source.offsetOf(r.start)
|
LyngSemanticKind.Variable -> LyngHighlighterColors.VARIABLE
|
||||||
val e = source.offsetOf(r.end)
|
LyngSemanticKind.Parameter -> LyngHighlighterColors.PARAMETER
|
||||||
putRange(s, e, key)
|
LyngSemanticKind.TypeRef -> LyngHighlighterColors.TYPE
|
||||||
|
LyngSemanticKind.EnumConstant -> LyngHighlighterColors.ENUM_CONSTANT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declarations
|
// Semantic highlights from shared tooling
|
||||||
mini.declarations.forEach { d ->
|
LyngLanguageTools.semanticHighlights(analysis).forEach { span ->
|
||||||
if (d.nameStart.source != source) return@forEach
|
keyForKind(span.kind)?.let { putRange(span.range.start, span.range.endExclusive, it) }
|
||||||
when (d) {
|
|
||||||
is MiniFunDecl -> putName(d.nameStart, d.name, LyngHighlighterColors.FUNCTION_DECLARATION)
|
|
||||||
is MiniClassDecl -> putName(d.nameStart, d.name, LyngHighlighterColors.TYPE)
|
|
||||||
is MiniValDecl -> putName(
|
|
||||||
d.nameStart,
|
|
||||||
d.name,
|
|
||||||
if (d.mutable) LyngHighlighterColors.VARIABLE else LyngHighlighterColors.VALUE
|
|
||||||
)
|
|
||||||
is MiniEnumDecl -> putName(d.nameStart, d.name, LyngHighlighterColors.TYPE)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imports: each segment as namespace/path
|
// Imports: each segment as namespace/path
|
||||||
mini.imports.forEach { imp ->
|
mini?.imports?.forEach { imp ->
|
||||||
if (imp.range.start.source != source) return@forEach
|
imp.segments.forEach { seg ->
|
||||||
imp.segments.forEach { seg -> putMiniRange(seg.range, LyngHighlighterColors.NAMESPACE) }
|
val start = analysis.source.offsetOf(seg.range.start)
|
||||||
}
|
val end = analysis.source.offsetOf(seg.range.end)
|
||||||
|
putRange(start, end, LyngHighlighterColors.NAMESPACE)
|
||||||
// Parameters
|
|
||||||
fun addParams(params: List<MiniParam>) {
|
|
||||||
params.forEach { p ->
|
|
||||||
if (p.nameStart.source == source)
|
|
||||||
putName(p.nameStart, p.name, LyngHighlighterColors.PARAMETER)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mini.declarations.forEach { d ->
|
|
||||||
when (d) {
|
|
||||||
is MiniFunDecl -> addParams(d.params)
|
|
||||||
is MiniClassDecl -> d.members.filterIsInstance<MiniMemberFunDecl>().forEach { addParams(it.params) }
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type name segments (including generics base & args)
|
|
||||||
fun addTypeSegments(t: MiniTypeRef?) {
|
|
||||||
when (t) {
|
|
||||||
is MiniTypeName -> t.segments.forEach { seg ->
|
|
||||||
if (seg.range.start.source != source) return@forEach
|
|
||||||
val s = source.offsetOf(seg.range.start)
|
|
||||||
putRange(s, (s + seg.name.length).coerceAtMost(text.length), LyngHighlighterColors.TYPE)
|
|
||||||
}
|
|
||||||
is MiniGenericType -> {
|
|
||||||
addTypeSegments(t.base)
|
|
||||||
t.args.forEach { addTypeSegments(it) }
|
|
||||||
}
|
|
||||||
is MiniFunctionType -> {
|
|
||||||
t.receiver?.let { addTypeSegments(it) }
|
|
||||||
t.params.forEach { addTypeSegments(it) }
|
|
||||||
addTypeSegments(t.returnType)
|
|
||||||
}
|
|
||||||
is MiniTypeVar -> { /* name is in range; could be highlighted as TYPE as well */
|
|
||||||
if (t.range.start.source == source)
|
|
||||||
putMiniRange(t.range, LyngHighlighterColors.TYPE)
|
|
||||||
}
|
|
||||||
null -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun addDeclTypeSegments(d: MiniDecl) {
|
|
||||||
if (d.nameStart.source != source) return
|
|
||||||
when (d) {
|
|
||||||
is MiniFunDecl -> {
|
|
||||||
addTypeSegments(d.returnType)
|
|
||||||
d.params.forEach { addTypeSegments(it.type) }
|
|
||||||
addTypeSegments(d.receiver)
|
|
||||||
}
|
|
||||||
is MiniValDecl -> {
|
|
||||||
addTypeSegments(d.type)
|
|
||||||
addTypeSegments(d.receiver)
|
|
||||||
}
|
|
||||||
is MiniClassDecl -> {
|
|
||||||
d.ctorFields.forEach { addTypeSegments(it.type) }
|
|
||||||
d.classFields.forEach { addTypeSegments(it.type) }
|
|
||||||
for (m in d.members) {
|
|
||||||
when (m) {
|
|
||||||
is MiniMemberFunDecl -> {
|
|
||||||
addTypeSegments(m.returnType)
|
|
||||||
m.params.forEach { addTypeSegments(it.type) }
|
|
||||||
}
|
|
||||||
is MiniMemberValDecl -> {
|
|
||||||
addTypeSegments(m.type)
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is MiniEnumDecl -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mini.declarations.forEach { d -> addDeclTypeSegments(d) }
|
|
||||||
|
|
||||||
ProgressManager.checkCanceled()
|
|
||||||
|
|
||||||
// Semantic usages via Binder (best-effort)
|
|
||||||
try {
|
|
||||||
val binding = Binder.bind(text, mini)
|
|
||||||
|
|
||||||
// Map declaration ranges to avoid duplicating them as usages
|
|
||||||
val declKeys = HashSet<Pair<Int, Int>>(binding.symbols.size * 2)
|
|
||||||
binding.symbols.forEach { sym -> declKeys += (sym.declStart to sym.declEnd) }
|
|
||||||
|
|
||||||
fun keyForKind(k: SymbolKind) = when (k) {
|
|
||||||
SymbolKind.Function -> LyngHighlighterColors.FUNCTION
|
|
||||||
SymbolKind.Class, SymbolKind.Enum -> LyngHighlighterColors.TYPE
|
|
||||||
SymbolKind.Parameter -> LyngHighlighterColors.PARAMETER
|
|
||||||
SymbolKind.Value -> LyngHighlighterColors.VALUE
|
|
||||||
SymbolKind.Variable -> LyngHighlighterColors.VARIABLE
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track covered ranges to not override later heuristics
|
|
||||||
val covered = HashSet<Pair<Int, Int>>()
|
|
||||||
|
|
||||||
binding.references.forEach { ref ->
|
|
||||||
val key = ref.start to ref.end
|
|
||||||
if (!declKeys.contains(key)) {
|
|
||||||
val sym = binding.symbols.firstOrNull { it.id == ref.symbolId }
|
|
||||||
if (sym != null) {
|
|
||||||
val color = keyForKind(sym.kind)
|
|
||||||
putRange(ref.start, ref.end, color)
|
|
||||||
covered += key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Heuristics on top of binder: function call-sites and simple name-based roles
|
|
||||||
ProgressManager.checkCanceled()
|
|
||||||
|
|
||||||
// Build simple name -> role map for top-level vals/vars and parameters
|
|
||||||
val nameRole = HashMap<String, com.intellij.openapi.editor.colors.TextAttributesKey>(8)
|
|
||||||
mini.declarations.forEach { d ->
|
|
||||||
when (d) {
|
|
||||||
is MiniValDecl -> nameRole[d.name] =
|
|
||||||
if (d.mutable) LyngHighlighterColors.VARIABLE else LyngHighlighterColors.VALUE
|
|
||||||
|
|
||||||
is MiniFunDecl -> d.params.forEach { p -> nameRole[p.name] = LyngHighlighterColors.PARAMETER }
|
|
||||||
is MiniClassDecl -> {
|
|
||||||
d.members.forEach { m ->
|
|
||||||
if (m is MiniMemberFunDecl) {
|
|
||||||
m.params.forEach { p -> nameRole[p.name] = LyngHighlighterColors.PARAMETER }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens.forEach { s ->
|
|
||||||
if (s.kind == HighlightKind.Identifier) {
|
|
||||||
val start = s.range.start
|
|
||||||
val end = s.range.endExclusive
|
|
||||||
val key = start to end
|
|
||||||
if (key !in covered && key !in declKeys) {
|
|
||||||
// Call-site detection first so it wins over var/param role
|
|
||||||
if (isFollowedByParenOrBlock(end)) {
|
|
||||||
putRange(start, end, LyngHighlighterColors.FUNCTION)
|
|
||||||
covered += key
|
|
||||||
} else {
|
|
||||||
// Simple role by known names
|
|
||||||
val ident = try {
|
|
||||||
text.substring(start, end)
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
if (ident != null) {
|
|
||||||
val roleKey = nameRole[ident]
|
|
||||||
if (roleKey != null) {
|
|
||||||
putRange(start, end, roleKey)
|
|
||||||
covered += key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
// Must rethrow cancellation; otherwise ignore binder failures (best-effort)
|
|
||||||
if (e is com.intellij.openapi.progress.ProcessCanceledException) throw e
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add annotation/label coloring using token highlighter
|
// Add annotation/label coloring using token highlighter
|
||||||
run {
|
run {
|
||||||
tokens.forEach { s ->
|
analysis.lexicalHighlights.forEach { s ->
|
||||||
if (s.kind == HighlightKind.Label) {
|
if (s.kind == HighlightKind.Label) {
|
||||||
val start = s.range.start
|
val start = s.range.start
|
||||||
val end = s.range.endExclusive
|
val end = s.range.endExclusive
|
||||||
@ -302,7 +122,7 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
text.substring(wStart, wEnd)
|
text.substring(wStart, wEnd)
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
if (prevWord in setOf("return", "break", "continue") || isFollowedByParenOrBlock(end)) {
|
if (prevWord in setOf("return", "break", "continue")) {
|
||||||
putRange(start, end, LyngHighlighterColors.LABEL)
|
putRange(start, end, LyngHighlighterColors.LABEL)
|
||||||
} else {
|
} else {
|
||||||
putRange(start, end, LyngHighlighterColors.ANNOTATION)
|
putRange(start, end, LyngHighlighterColors.ANNOTATION)
|
||||||
@ -315,17 +135,13 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens.forEach { s ->
|
analysis.diagnostics.forEach { d ->
|
||||||
if (s.kind == HighlightKind.EnumConstant) {
|
val range = d.range ?: return@forEach
|
||||||
val start = s.range.start
|
val severity = if (d.severity == LyngDiagnosticSeverity.Warning) HighlightSeverity.WARNING else HighlightSeverity.ERROR
|
||||||
val end = s.range.endExclusive
|
diags += Diag(range.start, range.endExclusive, d.message, severity)
|
||||||
if (start in 0..end && end <= text.length && start < end) {
|
|
||||||
putRange(start, end, LyngHighlighterColors.ENUM_CONSTANT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result(collectedInfo.modStamp, out, null)
|
return Result(collectedInfo.modStamp, out, diags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -346,13 +162,12 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show syntax error if present
|
// Show errors and warnings
|
||||||
val err = result.error
|
result.diagnostics.forEach { d ->
|
||||||
if (err != null) {
|
val start = d.start.coerceIn(0, (doc?.textLength ?: 0))
|
||||||
val start = err.start.coerceIn(0, (doc?.textLength ?: 0))
|
val end = d.end.coerceIn(start, (doc?.textLength ?: start))
|
||||||
val end = err.end.coerceIn(start, (doc?.textLength ?: start))
|
|
||||||
if (end > start) {
|
if (end > start) {
|
||||||
holder.newAnnotation(HighlightSeverity.ERROR, err.message)
|
holder.newAnnotation(d.severity, d.message)
|
||||||
.range(TextRange(start, end))
|
.range(TextRange(start, end))
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
@ -373,30 +188,5 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Make the error highlight a bit wider than a single character so it is easier to see and click.
|
|
||||||
* Strategy:
|
|
||||||
* - If the offset points inside an identifier-like token (letters/digits/underscore), expand to the full token.
|
|
||||||
* - Otherwise select a small range starting at the offset with a minimum width, but not crossing the line end.
|
|
||||||
*/
|
|
||||||
private fun expandErrorRange(text: String, rawStart: Int): Pair<Int, Int> {
|
|
||||||
if (text.isEmpty()) return 0 to 0
|
|
||||||
val len = text.length
|
|
||||||
val start = rawStart.coerceIn(0, len)
|
|
||||||
fun isWord(ch: Char) = ch == '_' || ch.isLetterOrDigit()
|
|
||||||
|
|
||||||
if (start < len && isWord(text[start])) {
|
|
||||||
var s = start
|
|
||||||
var e = start
|
|
||||||
while (s > 0 && isWord(text[s - 1])) s--
|
|
||||||
while (e < len && isWord(text[e])) e++
|
|
||||||
return s to e
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not inside a word: select a short, visible range up to EOL
|
|
||||||
val lineEnd = text.indexOf('\n', start).let { if (it == -1) len else it }
|
|
||||||
val minWidth = 4
|
|
||||||
val end = (start + minWidth).coerceAtMost(lineEnd).coerceAtLeast((start + 1).coerceAtMost(lineEnd))
|
|
||||||
return start to end
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,9 +96,10 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
log.info("[LYNG_DEBUG] Completion: caret=$caret prefix='${prefix}' memberDotPos=${memberDotPos} file='${file.name}'")
|
log.info("[LYNG_DEBUG] Completion: caret=$caret prefix='${prefix}' memberDotPos=${memberDotPos} file='${file.name}'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build MiniAst (cached) for both global and member contexts to enable local class/val inference
|
// Build analysis (cached) for both global and member contexts to enable local class/val inference
|
||||||
val mini = LyngAstManager.getMiniAst(file)
|
val analysis = LyngAstManager.getAnalysis(file)
|
||||||
val binding = LyngAstManager.getBinding(file)
|
val mini = analysis?.mini
|
||||||
|
val binding = analysis?.binding
|
||||||
|
|
||||||
// Delegate computation to the shared engine to keep behavior in sync with tests
|
// Delegate computation to the shared engine to keep behavior in sync with tests
|
||||||
val engineItems = try {
|
val engineItems = try {
|
||||||
@ -121,6 +122,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
fromText.forEach { add(it) }
|
fromText.forEach { add(it) }
|
||||||
add("lyng.stdlib")
|
add("lyng.stdlib")
|
||||||
}.toList()
|
}.toList()
|
||||||
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, memberDotPos, imported, binding)
|
||||||
|
|
||||||
// Try inferring return/receiver class around the dot
|
// Try inferring return/receiver class around the dot
|
||||||
val inferred =
|
val inferred =
|
||||||
@ -135,7 +137,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
|
|
||||||
if (inferred != null) {
|
if (inferred != null) {
|
||||||
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback inferred receiver/return class='$inferred' — offering its members")
|
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback inferred receiver/return class='$inferred' — offering its members")
|
||||||
offerMembers(emit, imported, inferred, sourceText = text, mini = mini)
|
offerMembers(emit, imported, inferred, staticOnly = staticOnly, sourceText = text, mini = mini)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback could not infer class; keeping list empty (no globals after dot)")
|
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback could not infer class; keeping list empty (no globals after dot)")
|
||||||
@ -160,6 +162,8 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
.withIcon(AllIcons.Nodes.Class)
|
.withIcon(AllIcons.Nodes.Class)
|
||||||
Kind.Enum -> LookupElementBuilder.create(ci.name)
|
Kind.Enum -> LookupElementBuilder.create(ci.name)
|
||||||
.withIcon(AllIcons.Nodes.Enum)
|
.withIcon(AllIcons.Nodes.Enum)
|
||||||
|
Kind.TypeAlias -> LookupElementBuilder.create(ci.name)
|
||||||
|
.withIcon(AllIcons.Nodes.Class)
|
||||||
Kind.Value -> LookupElementBuilder.create(ci.name)
|
Kind.Value -> LookupElementBuilder.create(ci.name)
|
||||||
.withIcon(AllIcons.Nodes.Variable)
|
.withIcon(AllIcons.Nodes.Variable)
|
||||||
.let { b -> if (!ci.typeText.isNullOrBlank()) b.withTypeText(ci.typeText, true) else b }
|
.let { b -> if (!ci.typeText.isNullOrBlank()) b.withTypeText(ci.typeText, true) else b }
|
||||||
@ -292,6 +296,9 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
}
|
}
|
||||||
is MiniEnumDecl -> LookupElementBuilder.create(name)
|
is MiniEnumDecl -> LookupElementBuilder.create(name)
|
||||||
.withIcon(AllIcons.Nodes.Enum)
|
.withIcon(AllIcons.Nodes.Enum)
|
||||||
|
is MiniTypeAliasDecl -> LookupElementBuilder.create(name)
|
||||||
|
.withIcon(AllIcons.Nodes.Class)
|
||||||
|
.withTypeText(typeOf(d.target), true)
|
||||||
}
|
}
|
||||||
emit(builder)
|
emit(builder)
|
||||||
}
|
}
|
||||||
@ -369,6 +376,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
when (m) {
|
when (m) {
|
||||||
is MiniMemberFunDecl -> if (!m.isStatic) continue
|
is MiniMemberFunDecl -> if (!m.isStatic) continue
|
||||||
is MiniMemberValDecl -> if (!m.isStatic) continue
|
is MiniMemberValDecl -> if (!m.isStatic) continue
|
||||||
|
is MiniMemberTypeAliasDecl -> if (!m.isStatic) continue
|
||||||
is MiniInitDecl -> continue
|
is MiniInitDecl -> continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -458,6 +466,16 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
emit(builder)
|
emit(builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is MiniMemberTypeAliasDecl -> {
|
||||||
|
val builder = LookupElementBuilder.create(name)
|
||||||
|
.withIcon(AllIcons.Nodes.Class)
|
||||||
|
.withTypeText(typeOf(rep.target), true)
|
||||||
|
if (groupPriority != 0.0) {
|
||||||
|
emit(PrioritizedLookupElement.withPriority(builder, groupPriority))
|
||||||
|
} else {
|
||||||
|
emit(builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
is MiniInitDecl -> {}
|
is MiniInitDecl -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,8 @@ import net.sergeych.lyng.idea.LyngLanguage
|
|||||||
import net.sergeych.lyng.idea.util.LyngAstManager
|
import net.sergeych.lyng.idea.util.LyngAstManager
|
||||||
import net.sergeych.lyng.idea.util.TextCtx
|
import net.sergeych.lyng.idea.util.TextCtx
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
|
import net.sergeych.lyng.tools.LyngLanguageTools
|
||||||
|
import net.sergeych.lyng.tools.LyngSymbolInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quick Docs backed by MiniAst: when caret is on an identifier that corresponds
|
* Quick Docs backed by MiniAst: when caret is on an identifier that corresponds
|
||||||
@ -66,9 +68,15 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
if (DEBUG_LOG) log.info("[LYNG_DEBUG] QuickDoc: ident='$ident' at ${idRange.startOffset}..${idRange.endOffset} in ${file.name}")
|
if (DEBUG_LOG) log.info("[LYNG_DEBUG] QuickDoc: ident='$ident' at ${idRange.startOffset}..${idRange.endOffset} in ${file.name}")
|
||||||
|
|
||||||
// 1. Get merged mini-AST from Manager (handles local + .lyng.d merged declarations)
|
// 1. Get merged mini-AST from Manager (handles local + .lyng.d merged declarations)
|
||||||
val mini = LyngAstManager.getMiniAst(file) ?: return null
|
val analysis = LyngAstManager.getAnalysis(file) ?: return null
|
||||||
|
val mini = analysis.mini ?: return null
|
||||||
val miniSource = mini.range.start.source
|
val miniSource = mini.range.start.source
|
||||||
val imported = DocLookupUtils.canonicalImportedModules(mini, text)
|
val imported = analysis.importedModules.ifEmpty { DocLookupUtils.canonicalImportedModules(mini, text) }
|
||||||
|
|
||||||
|
// Single-source quick doc lookup
|
||||||
|
LyngLanguageTools.docAt(analysis, offset)?.let { info ->
|
||||||
|
renderDocFromInfo(info)?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
// Try resolve to: function param at position, function/class/val declaration at position
|
// Try resolve to: function param at position, function/class/val declaration at position
|
||||||
// 1) Use unified declaration detection
|
// 1) Use unified declaration detection
|
||||||
@ -91,6 +99,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
return when (m) {
|
return when (m) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(d.name, m)
|
is MiniMemberFunDecl -> renderMemberFunDoc(d.name, m)
|
||||||
is MiniMemberValDecl -> renderMemberValDoc(d.name, m)
|
is MiniMemberValDecl -> renderMemberValDoc(d.name, m)
|
||||||
|
is MiniMemberTypeAliasDecl -> renderMemberTypeAliasDoc(d.name, m)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,6 +206,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
return when (m) {
|
return when (m) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(cls.name, m)
|
is MiniMemberFunDecl -> renderMemberFunDoc(cls.name, m)
|
||||||
is MiniMemberValDecl -> renderMemberValDoc(cls.name, m)
|
is MiniMemberValDecl -> renderMemberValDoc(cls.name, m)
|
||||||
|
is MiniMemberTypeAliasDecl -> renderMemberTypeAliasDoc(cls.name, m)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,16 +317,19 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
}
|
}
|
||||||
if (DEBUG_LOG) log.info("[LYNG_DEBUG] QuickDoc: memberCtx dotPos=${dotPos} chBeforeDot='${if (dotPos > 0) text[dotPos - 1] else ' '}' classGuess=${className} imports=${importedModules}")
|
if (DEBUG_LOG) log.info("[LYNG_DEBUG] QuickDoc: memberCtx dotPos=${dotPos} chBeforeDot='${if (dotPos > 0) text[dotPos - 1] else ' '}' classGuess=${className} imports=${importedModules}")
|
||||||
if (className != null) {
|
if (className != null) {
|
||||||
DocLookupUtils.resolveMemberWithInheritance(importedModules, className, ident, mini)?.let { (owner, member) ->
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, dotPos, importedModules, analysis.binding)
|
||||||
|
DocLookupUtils.resolveMemberWithInheritance(importedModules, className, ident, mini, staticOnly = staticOnly)?.let { (owner, member) ->
|
||||||
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] QuickDoc: literal/call '$ident' resolved to $owner.${member.name}")
|
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] QuickDoc: literal/call '$ident' resolved to $owner.${member.name}")
|
||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||||
|
is MiniMemberTypeAliasDecl -> renderMemberTypeAliasDoc(owner, member)
|
||||||
is MiniInitDecl -> null
|
is MiniInitDecl -> null
|
||||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
|
is MiniTypeAliasDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("[LYNG_DEBUG] QuickDoc: resolve failed for ${className}.${ident}")
|
log.info("[LYNG_DEBUG] QuickDoc: resolve failed for ${className}.${ident}")
|
||||||
@ -354,6 +367,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
// And classes/enums
|
// And classes/enums
|
||||||
docs.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it, text, mini, imported) }
|
docs.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it, text, mini, imported) }
|
||||||
docs.filterIsInstance<MiniEnumDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it, text, mini, imported) }
|
docs.filterIsInstance<MiniEnumDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it, text, mini, imported) }
|
||||||
|
docs.filterIsInstance<MiniTypeAliasDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it, text, mini, imported) }
|
||||||
}
|
}
|
||||||
// Defensive fallback: if nothing found and it's a well-known stdlib function, render minimal inline docs
|
// Defensive fallback: if nothing found and it's a well-known stdlib function, render minimal inline docs
|
||||||
if (ident == "println" || ident == "print") {
|
if (ident == "println" || ident == "print") {
|
||||||
@ -367,16 +381,20 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
val lhs = previousWordBefore(text, idRange.startOffset)
|
val lhs = previousWordBefore(text, idRange.startOffset)
|
||||||
if (lhs != null && hasDotBetween(text, lhs.endOffset, idRange.startOffset)) {
|
if (lhs != null && hasDotBetween(text, lhs.endOffset, idRange.startOffset)) {
|
||||||
val className = text.substring(lhs.startOffset, lhs.endOffset)
|
val className = text.substring(lhs.startOffset, lhs.endOffset)
|
||||||
DocLookupUtils.resolveMemberWithInheritance(importedModules, className, ident, mini)?.let { (owner, member) ->
|
val dotPos = findDotLeft(text, idRange.startOffset)
|
||||||
|
val staticOnly = dotPos?.let { DocLookupUtils.isStaticReceiver(mini, text, it, importedModules, analysis.binding) } ?: false
|
||||||
|
DocLookupUtils.resolveMemberWithInheritance(importedModules, className, ident, mini, staticOnly = staticOnly)?.let { (owner, member) ->
|
||||||
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Inheritance resolved $className.$ident to $owner.${member.name}")
|
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Inheritance resolved $className.$ident to $owner.${member.name}")
|
||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||||
|
is MiniMemberTypeAliasDecl -> renderMemberTypeAliasDoc(owner, member)
|
||||||
is MiniInitDecl -> null
|
is MiniInitDecl -> null
|
||||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
|
is MiniTypeAliasDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -390,16 +408,19 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
else -> DocLookupUtils.guessClassFromCallBefore(text, dotPos, importedModules, mini)
|
else -> DocLookupUtils.guessClassFromCallBefore(text, dotPos, importedModules, mini)
|
||||||
}
|
}
|
||||||
if (guessed != null) {
|
if (guessed != null) {
|
||||||
DocLookupUtils.resolveMemberWithInheritance(importedModules, guessed, ident, mini)?.let { (owner, member) ->
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, dotPos, importedModules, analysis.binding)
|
||||||
|
DocLookupUtils.resolveMemberWithInheritance(importedModules, guessed, ident, mini, staticOnly = staticOnly)?.let { (owner, member) ->
|
||||||
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Heuristic '$guessed.$ident' resolved via inheritance to $owner.${member.name}")
|
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Heuristic '$guessed.$ident' resolved via inheritance to $owner.${member.name}")
|
||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||||
|
is MiniMemberTypeAliasDecl -> renderMemberTypeAliasDoc(owner, member)
|
||||||
is MiniInitDecl -> null
|
is MiniInitDecl -> null
|
||||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
|
is MiniTypeAliasDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -407,16 +428,19 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
run {
|
run {
|
||||||
val candidates = listOf("String", "Iterable", "Iterator", "List", "Collection", "Array", "Dict", "Regex")
|
val candidates = listOf("String", "Iterable", "Iterator", "List", "Collection", "Array", "Dict", "Regex")
|
||||||
for (c in candidates) {
|
for (c in candidates) {
|
||||||
DocLookupUtils.resolveMemberWithInheritance(importedModules, c, ident, mini)?.let { (owner, member) ->
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, dotPos, importedModules, analysis.binding)
|
||||||
|
DocLookupUtils.resolveMemberWithInheritance(importedModules, c, ident, mini, staticOnly = staticOnly)?.let { (owner, member) ->
|
||||||
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Candidate '$c.$ident' resolved via inheritance to $owner.${member.name}")
|
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Candidate '$c.$ident' resolved via inheritance to $owner.${member.name}")
|
||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||||
|
is MiniMemberTypeAliasDecl -> renderMemberTypeAliasDoc(owner, member)
|
||||||
is MiniInitDecl -> null
|
is MiniInitDecl -> null
|
||||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
|
is MiniTypeAliasDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,6 +455,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
return when (m) {
|
return when (m) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc("String", m)
|
is MiniMemberFunDecl -> renderMemberFunDoc("String", m)
|
||||||
is MiniMemberValDecl -> renderMemberValDoc("String", m)
|
is MiniMemberValDecl -> renderMemberValDoc("String", m)
|
||||||
|
is MiniMemberTypeAliasDecl -> renderMemberTypeAliasDoc("String", m)
|
||||||
is MiniInitDecl -> null
|
is MiniInitDecl -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -441,11 +466,13 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||||
|
is MiniMemberTypeAliasDecl -> renderMemberTypeAliasDoc(owner, member)
|
||||||
is MiniInitDecl -> null
|
is MiniInitDecl -> null
|
||||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
|
is MiniTypeAliasDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -512,6 +539,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
is MiniFunDecl -> "function ${d.name}${signatureOf(d)}"
|
is MiniFunDecl -> "function ${d.name}${signatureOf(d)}"
|
||||||
is MiniClassDecl -> "class ${d.name}"
|
is MiniClassDecl -> "class ${d.name}"
|
||||||
is MiniEnumDecl -> "enum ${d.name} { ${d.entries.joinToString(", ")} }"
|
is MiniEnumDecl -> "enum ${d.name} { ${d.entries.joinToString(", ")} }"
|
||||||
|
is MiniTypeAliasDecl -> "type ${d.name}${typeAliasSuffix(d)}"
|
||||||
is MiniValDecl -> {
|
is MiniValDecl -> {
|
||||||
val t = d.type ?: DocLookupUtils.inferTypeRefForVal(d, text, imported, mini)
|
val t = d.type ?: DocLookupUtils.inferTypeRefForVal(d, text, imported, mini)
|
||||||
val typeStr = if (t == null) ": Object?" else typeOf(t)
|
val typeStr = if (t == null) ": Object?" else typeOf(t)
|
||||||
@ -524,6 +552,24 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderDocFromInfo(info: LyngSymbolInfo): String? {
|
||||||
|
val kind = when (info.target.kind) {
|
||||||
|
net.sergeych.lyng.binding.SymbolKind.Function -> "function"
|
||||||
|
net.sergeych.lyng.binding.SymbolKind.Class -> "class"
|
||||||
|
net.sergeych.lyng.binding.SymbolKind.Enum -> "enum"
|
||||||
|
net.sergeych.lyng.binding.SymbolKind.TypeAlias -> "type"
|
||||||
|
net.sergeych.lyng.binding.SymbolKind.Value -> "val"
|
||||||
|
net.sergeych.lyng.binding.SymbolKind.Variable -> "var"
|
||||||
|
net.sergeych.lyng.binding.SymbolKind.Parameter -> "parameter"
|
||||||
|
}
|
||||||
|
val title = info.signature ?: "$kind ${info.target.name}"
|
||||||
|
if (title.isBlank() && info.doc == null) return null
|
||||||
|
val sb = StringBuilder()
|
||||||
|
sb.append(renderTitle(title))
|
||||||
|
sb.append(renderDocBody(info.doc))
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderParamDoc(fn: MiniFunDecl, p: MiniParam): String {
|
private fun renderParamDoc(fn: MiniFunDecl, p: MiniParam): String {
|
||||||
val title = "parameter ${p.name}${typeOf(p.type)} in ${fn.name}${signatureOf(fn)}"
|
val title = "parameter ${p.name}${typeOf(p.type)} in ${fn.name}${signatureOf(fn)}"
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
@ -565,6 +611,25 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderMemberTypeAliasDoc(className: String, m: MiniMemberTypeAliasDecl): String {
|
||||||
|
val tp = if (m.typeParams.isEmpty()) "" else "<" + m.typeParams.joinToString(", ") + ">"
|
||||||
|
val body = typeOf(m.target)
|
||||||
|
val rhs = if (body.isBlank()) "" else " = ${body.removePrefix(": ")}"
|
||||||
|
val staticStr = if (m.isStatic) "static " else ""
|
||||||
|
val title = "${staticStr}type $className.${m.name}$tp$rhs"
|
||||||
|
val sb = StringBuilder()
|
||||||
|
sb.append(renderTitle(title))
|
||||||
|
sb.append(renderDocBody(m.doc))
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun typeAliasSuffix(d: MiniTypeAliasDecl): String {
|
||||||
|
val tp = if (d.typeParams.isEmpty()) "" else "<" + d.typeParams.joinToString(", ") + ">"
|
||||||
|
val body = typeOf(d.target)
|
||||||
|
val rhs = if (body.isBlank()) "" else " = ${body.removePrefix(": ")}"
|
||||||
|
return "$tp$rhs"
|
||||||
|
}
|
||||||
|
|
||||||
private fun typeOf(t: MiniTypeRef?): String {
|
private fun typeOf(t: MiniTypeRef?): String {
|
||||||
val s = DocLookupUtils.typeOf(t)
|
val s = DocLookupUtils.typeOf(t)
|
||||||
return if (s.isEmpty()) (if (t == null) ": Object?" else "") else ": $s"
|
return if (s.isEmpty()) (if (t == null) ": Object?" else "") else ": $s"
|
||||||
|
|||||||
@ -36,7 +36,7 @@ class LyngLexer : LexerBase() {
|
|||||||
"abstract", "closed", "override", "static", "extern", "open", "private", "protected",
|
"abstract", "closed", "override", "static", "extern", "open", "private", "protected",
|
||||||
"if", "else", "for", "while", "return", "true", "false", "null",
|
"if", "else", "for", "while", "return", "true", "false", "null",
|
||||||
"when", "in", "is", "break", "continue", "try", "catch", "finally",
|
"when", "in", "is", "break", "continue", "try", "catch", "finally",
|
||||||
"get", "set", "object", "enum", "init", "by", "property", "constructor"
|
"get", "set", "object", "enum", "init", "by", "step", "property", "constructor"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun start(buffer: CharSequence, startOffset: Int, endOffset: Int, initialState: Int) {
|
override fun start(buffer: CharSequence, startOffset: Int, endOffset: Int, initialState: Int) {
|
||||||
|
|||||||
@ -36,9 +36,10 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
val name = element.text ?: ""
|
val name = element.text ?: ""
|
||||||
val results = mutableListOf<ResolveResult>()
|
val results = mutableListOf<ResolveResult>()
|
||||||
|
|
||||||
val mini = LyngAstManager.getMiniAst(file) ?: return emptyArray()
|
val analysis = LyngAstManager.getAnalysis(file) ?: return emptyArray()
|
||||||
val binding = LyngAstManager.getBinding(file)
|
val mini = analysis.mini ?: return emptyArray()
|
||||||
val imported = DocLookupUtils.canonicalImportedModules(mini, text).toSet()
|
val binding = analysis.binding
|
||||||
|
val imported = analysis.importedModules.toSet()
|
||||||
val currentPackage = getPackageName(file)
|
val currentPackage = getPackageName(file)
|
||||||
val allowedPackages = if (currentPackage != null) imported + currentPackage else imported
|
val allowedPackages = if (currentPackage != null) imported + currentPackage else imported
|
||||||
|
|
||||||
@ -47,9 +48,10 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
if (dotPos != null) {
|
if (dotPos != null) {
|
||||||
val receiverClass = DocLookupUtils.guessReceiverClassViaMini(mini, text, dotPos, imported.toList(), binding)
|
val receiverClass = DocLookupUtils.guessReceiverClassViaMini(mini, text, dotPos, imported.toList(), binding)
|
||||||
?: DocLookupUtils.guessReceiverClass(text, dotPos, imported.toList(), mini)
|
?: DocLookupUtils.guessReceiverClass(text, dotPos, imported.toList(), mini)
|
||||||
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, dotPos, imported.toList(), binding)
|
||||||
|
|
||||||
if (receiverClass != null) {
|
if (receiverClass != null) {
|
||||||
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported.toList(), receiverClass, name, mini)
|
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported.toList(), receiverClass, name, mini, staticOnly = staticOnly)
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
val owner = resolved.first
|
val owner = resolved.first
|
||||||
val member = resolved.second
|
val member = resolved.second
|
||||||
@ -64,11 +66,13 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
val kind = when(member) {
|
val kind = when(member) {
|
||||||
is MiniMemberFunDecl -> "Function"
|
is MiniMemberFunDecl -> "Function"
|
||||||
is MiniMemberValDecl -> if (member.mutable) "Variable" else "Value"
|
is MiniMemberValDecl -> if (member.mutable) "Variable" else "Value"
|
||||||
|
is MiniMemberTypeAliasDecl -> "TypeAlias"
|
||||||
is MiniInitDecl -> "Initializer"
|
is MiniInitDecl -> "Initializer"
|
||||||
is MiniFunDecl -> "Function"
|
is MiniFunDecl -> "Function"
|
||||||
is MiniValDecl -> if (member.mutable) "Variable" else "Value"
|
is MiniValDecl -> if (member.mutable) "Variable" else "Value"
|
||||||
is MiniClassDecl -> "Class"
|
is MiniClassDecl -> "Class"
|
||||||
is MiniEnumDecl -> "Enum"
|
is MiniEnumDecl -> "Enum"
|
||||||
|
is MiniTypeAliasDecl -> "TypeAlias"
|
||||||
}
|
}
|
||||||
results.add(PsiElementResolveResult(LyngDeclarationElement(it, member.name, kind)))
|
results.add(PsiElementResolveResult(LyngDeclarationElement(it, member.name, kind)))
|
||||||
}
|
}
|
||||||
@ -199,6 +203,7 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
is net.sergeych.lyng.miniast.MiniClassDecl -> "Class"
|
is net.sergeych.lyng.miniast.MiniClassDecl -> "Class"
|
||||||
is net.sergeych.lyng.miniast.MiniEnumDecl -> "Enum"
|
is net.sergeych.lyng.miniast.MiniEnumDecl -> "Enum"
|
||||||
is net.sergeych.lyng.miniast.MiniValDecl -> if (d.mutable) "Variable" else "Value"
|
is net.sergeych.lyng.miniast.MiniValDecl -> if (d.mutable) "Variable" else "Value"
|
||||||
|
is net.sergeych.lyng.miniast.MiniTypeAliasDecl -> "TypeAlias"
|
||||||
}
|
}
|
||||||
addIfMatch(d.name, d.nameStart, dKind)
|
addIfMatch(d.name, d.nameStart, dKind)
|
||||||
}
|
}
|
||||||
@ -214,6 +219,7 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
val mKind = when(m) {
|
val mKind = when(m) {
|
||||||
is net.sergeych.lyng.miniast.MiniMemberFunDecl -> "Function"
|
is net.sergeych.lyng.miniast.MiniMemberFunDecl -> "Function"
|
||||||
is net.sergeych.lyng.miniast.MiniMemberValDecl -> if (m.mutable) "Variable" else "Value"
|
is net.sergeych.lyng.miniast.MiniMemberValDecl -> if (m.mutable) "Variable" else "Value"
|
||||||
|
is net.sergeych.lyng.miniast.MiniMemberTypeAliasDecl -> "TypeAlias"
|
||||||
is net.sergeych.lyng.miniast.MiniInitDecl -> "Initializer"
|
is net.sergeych.lyng.miniast.MiniInitDecl -> "Initializer"
|
||||||
}
|
}
|
||||||
addIfMatch(m.name, m.nameStart, mKind)
|
addIfMatch(m.name, m.nameStart, mKind)
|
||||||
|
|||||||
@ -22,55 +22,22 @@ import com.intellij.openapi.util.Key
|
|||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import com.intellij.psi.PsiManager
|
import com.intellij.psi.PsiManager
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.sergeych.lyng.Compiler
|
|
||||||
import net.sergeych.lyng.Source
|
|
||||||
import net.sergeych.lyng.binding.Binder
|
|
||||||
import net.sergeych.lyng.binding.BindingSnapshot
|
import net.sergeych.lyng.binding.BindingSnapshot
|
||||||
import net.sergeych.lyng.miniast.MiniAstBuilder
|
import net.sergeych.lyng.miniast.DocLookupUtils
|
||||||
import net.sergeych.lyng.miniast.MiniScript
|
import net.sergeych.lyng.miniast.MiniScript
|
||||||
|
import net.sergeych.lyng.tools.IdeLenientImportProvider
|
||||||
|
import net.sergeych.lyng.tools.LyngAnalysisRequest
|
||||||
|
import net.sergeych.lyng.tools.LyngAnalysisResult
|
||||||
|
import net.sergeych.lyng.tools.LyngLanguageTools
|
||||||
|
|
||||||
object LyngAstManager {
|
object LyngAstManager {
|
||||||
private val MINI_KEY = Key.create<MiniScript>("lyng.mini.cache")
|
private val MINI_KEY = Key.create<MiniScript>("lyng.mini.cache")
|
||||||
private val BINDING_KEY = Key.create<BindingSnapshot>("lyng.binding.cache")
|
private val BINDING_KEY = Key.create<BindingSnapshot>("lyng.binding.cache")
|
||||||
private val STAMP_KEY = Key.create<Long>("lyng.mini.cache.stamp")
|
private val STAMP_KEY = Key.create<Long>("lyng.mini.cache.stamp")
|
||||||
|
private val ANALYSIS_KEY = Key.create<LyngAnalysisResult>("lyng.analysis.cache")
|
||||||
|
|
||||||
fun getMiniAst(file: PsiFile): MiniScript? = runReadAction {
|
fun getMiniAst(file: PsiFile): MiniScript? = runReadAction {
|
||||||
val vFile = file.virtualFile ?: return@runReadAction null
|
getAnalysis(file)?.mini
|
||||||
val combinedStamp = getCombinedStamp(file)
|
|
||||||
|
|
||||||
val prevStamp = file.getUserData(STAMP_KEY)
|
|
||||||
val cached = file.getUserData(MINI_KEY)
|
|
||||||
if (cached != null && prevStamp != null && prevStamp == combinedStamp) return@runReadAction cached
|
|
||||||
|
|
||||||
val text = file.viewProvider.contents.toString()
|
|
||||||
val sink = MiniAstBuilder()
|
|
||||||
val built = try {
|
|
||||||
val provider = IdeLenientImportProvider.create()
|
|
||||||
val src = Source(file.name, text)
|
|
||||||
runBlocking { Compiler.compileWithMini(src, provider, sink) }
|
|
||||||
val script = sink.build()
|
|
||||||
if (script != null && !file.name.endsWith(".lyng.d")) {
|
|
||||||
val dFiles = collectDeclarationFiles(file)
|
|
||||||
for (df in dFiles) {
|
|
||||||
val scriptD = getMiniAst(df)
|
|
||||||
if (scriptD != null) {
|
|
||||||
script.declarations.addAll(scriptD.declarations)
|
|
||||||
script.imports.addAll(scriptD.imports)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
script
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
sink.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (built != null) {
|
|
||||||
file.putUserData(MINI_KEY, built)
|
|
||||||
file.putUserData(STAMP_KEY, combinedStamp)
|
|
||||||
// Invalidate binding too
|
|
||||||
file.putUserData(BINDING_KEY, null)
|
|
||||||
}
|
|
||||||
built
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCombinedStamp(file: PsiFile): Long = runReadAction {
|
fun getCombinedStamp(file: PsiFile): Long = runReadAction {
|
||||||
@ -102,32 +69,53 @@ object LyngAstManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getBinding(file: PsiFile): BindingSnapshot? = runReadAction {
|
fun getBinding(file: PsiFile): BindingSnapshot? = runReadAction {
|
||||||
|
getAnalysis(file)?.binding
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAnalysis(file: PsiFile): LyngAnalysisResult? = runReadAction {
|
||||||
val vFile = file.virtualFile ?: return@runReadAction null
|
val vFile = file.virtualFile ?: return@runReadAction null
|
||||||
var combinedStamp = file.viewProvider.modificationStamp
|
val combinedStamp = getCombinedStamp(file)
|
||||||
|
|
||||||
val dFiles = if (!file.name.endsWith(".lyng.d")) collectDeclarationFiles(file) else emptyList()
|
|
||||||
for (df in dFiles) {
|
|
||||||
combinedStamp += df.viewProvider.modificationStamp
|
|
||||||
}
|
|
||||||
|
|
||||||
val prevStamp = file.getUserData(STAMP_KEY)
|
val prevStamp = file.getUserData(STAMP_KEY)
|
||||||
val cached = file.getUserData(BINDING_KEY)
|
val cached = file.getUserData(ANALYSIS_KEY)
|
||||||
|
|
||||||
if (cached != null && prevStamp != null && prevStamp == combinedStamp) return@runReadAction cached
|
if (cached != null && prevStamp != null && prevStamp == combinedStamp) return@runReadAction cached
|
||||||
|
|
||||||
val mini = getMiniAst(file) ?: return@runReadAction null
|
|
||||||
val text = file.viewProvider.contents.toString()
|
val text = file.viewProvider.contents.toString()
|
||||||
val binding = try {
|
val built = try {
|
||||||
Binder.bind(text, mini)
|
val provider = IdeLenientImportProvider.create()
|
||||||
|
runBlocking {
|
||||||
|
LyngLanguageTools.analyze(
|
||||||
|
LyngAnalysisRequest(text = text, fileName = file.name, importProvider = provider)
|
||||||
|
)
|
||||||
|
}
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (binding != null) {
|
if (built != null) {
|
||||||
file.putUserData(BINDING_KEY, binding)
|
val merged = built.mini
|
||||||
// stamp is already set by getMiniAst or we set it here if getMiniAst was cached
|
if (merged != null && !file.name.endsWith(".lyng.d")) {
|
||||||
|
val dFiles = collectDeclarationFiles(file)
|
||||||
|
for (df in dFiles) {
|
||||||
|
val dAnalysis = getAnalysis(df)
|
||||||
|
val dMini = dAnalysis?.mini ?: continue
|
||||||
|
merged.declarations.addAll(dMini.declarations)
|
||||||
|
merged.imports.addAll(dMini.imports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val finalAnalysis = if (merged != null) {
|
||||||
|
built.copy(
|
||||||
|
mini = merged,
|
||||||
|
importedModules = DocLookupUtils.canonicalImportedModules(merged, text)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
built
|
||||||
|
}
|
||||||
|
file.putUserData(ANALYSIS_KEY, finalAnalysis)
|
||||||
|
file.putUserData(MINI_KEY, finalAnalysis.mini)
|
||||||
|
file.putUserData(BINDING_KEY, finalAnalysis.binding)
|
||||||
file.putUserData(STAMP_KEY, combinedStamp)
|
file.putUserData(STAMP_KEY, combinedStamp)
|
||||||
|
return@runReadAction finalAnalysis
|
||||||
}
|
}
|
||||||
binding
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import com.github.ajalt.clikt.parameters.arguments.optional
|
|||||||
import com.github.ajalt.clikt.parameters.options.flag
|
import com.github.ajalt.clikt.parameters.options.flag
|
||||||
import com.github.ajalt.clikt.parameters.options.option
|
import com.github.ajalt.clikt.parameters.options.option
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import net.sergeych.lyng.Compiler
|
||||||
import net.sergeych.lyng.LyngVersion
|
import net.sergeych.lyng.LyngVersion
|
||||||
import net.sergeych.lyng.Script
|
import net.sergeych.lyng.Script
|
||||||
import net.sergeych.lyng.ScriptError
|
import net.sergeych.lyng.ScriptError
|
||||||
@ -167,7 +168,7 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
|
|
||||||
override fun help(context: Context): String =
|
override fun help(context: Context): String =
|
||||||
"""
|
"""
|
||||||
The Lyng script language interpreter, language version is $LyngVersion.
|
The Lyng script language runtime, language version is $LyngVersion.
|
||||||
|
|
||||||
Please refer form more information to the project site:
|
Please refer form more information to the project site:
|
||||||
https://gitea.sergeych.net/SergeychWorks/lyng
|
https://gitea.sergeych.net/SergeychWorks/lyng
|
||||||
@ -198,7 +199,12 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
launcher {
|
launcher {
|
||||||
// there is no script name, it is a first argument instead:
|
// there is no script name, it is a first argument instead:
|
||||||
processErrors {
|
processErrors {
|
||||||
baseScope.eval(execute!!)
|
val script = Compiler.compileWithResolution(
|
||||||
|
Source("<eval>", execute!!),
|
||||||
|
baseScope.currentImportProvider,
|
||||||
|
seedScope = baseScope
|
||||||
|
)
|
||||||
|
script.execute(baseScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,7 +242,13 @@ suspend fun executeFile(fileName: String) {
|
|||||||
text = text.substring(pos + 1)
|
text = text.substring(pos + 1)
|
||||||
}
|
}
|
||||||
processErrors {
|
processErrors {
|
||||||
baseScopeDefer.await().eval(Source(fileName, text))
|
val scope = baseScopeDefer.await()
|
||||||
|
val script = Compiler.compileWithResolution(
|
||||||
|
Source(fileName, text),
|
||||||
|
scope.currentImportProvider,
|
||||||
|
seedScope = scope
|
||||||
|
)
|
||||||
|
script.execute(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -56,7 +56,7 @@ class FsIntegrationJvmTest {
|
|||||||
"""
|
"""
|
||||||
import lyng.io.fs
|
import lyng.io.fs
|
||||||
// list current folder files
|
// list current folder files
|
||||||
println( Path(".").list().toList() )
|
println( Path(".").list() )
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,8 @@ package net.sergeych.lyng.io.fs
|
|||||||
|
|
||||||
import net.sergeych.lyng.ModuleScope
|
import net.sergeych.lyng.ModuleScope
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.ScopeFacade
|
||||||
|
import net.sergeych.lyng.requireScope
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
@ -437,7 +439,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
|
|||||||
moduleName = module.packageName
|
moduleName = module.packageName
|
||||||
) {
|
) {
|
||||||
fsGuard {
|
fsGuard {
|
||||||
val chunkIt = thisObj.invokeInstanceMethod(this, "readUtf8Chunks")
|
val chunkIt = thisObj.invokeInstanceMethod(requireScope(), "readUtf8Chunks")
|
||||||
ObjFsLinesIterator(chunkIt)
|
ObjFsLinesIterator(chunkIt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -463,7 +465,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
|
|||||||
|
|
||||||
// --- Helper classes and utilities ---
|
// --- Helper classes and utilities ---
|
||||||
|
|
||||||
private fun parsePathArg(scope: Scope, self: ObjPath, arg: Obj): LyngPath {
|
private fun parsePathArg(scope: ScopeFacade, self: ObjPath, arg: Obj): LyngPath {
|
||||||
return when (arg) {
|
return when (arg) {
|
||||||
is ObjString -> arg.value.toPath()
|
is ObjString -> arg.value.toPath()
|
||||||
is ObjPath -> arg.path
|
is ObjPath -> arg.path
|
||||||
@ -472,11 +474,11 @@ private fun parsePathArg(scope: Scope, self: ObjPath, arg: Obj): LyngPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Map Fs access denials to Lyng runtime exceptions for script-friendly errors
|
// Map Fs access denials to Lyng runtime exceptions for script-friendly errors
|
||||||
private suspend inline fun Scope.fsGuard(crossinline block: suspend () -> Obj): Obj {
|
private suspend inline fun ScopeFacade.fsGuard(crossinline block: suspend () -> Obj): Obj {
|
||||||
return try {
|
return try {
|
||||||
block()
|
block()
|
||||||
} catch (e: AccessDeniedException) {
|
} catch (e: AccessDeniedException) {
|
||||||
raiseError(ObjIllegalOperationException(this, e.reasonDetail ?: "access denied"))
|
raiseError(ObjIllegalOperationException(requireScope(), e.reasonDetail ?: "access denied"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -668,16 +670,17 @@ class ObjFsLinesIterator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun ensureBufferFilled(scope: Scope) {
|
private suspend fun ensureBufferFilled(scope: ScopeFacade) {
|
||||||
if (buffer.contains('\n') || exhausted) return
|
if (buffer.contains('\n') || exhausted) return
|
||||||
|
val actualScope = scope.requireScope()
|
||||||
// Pull next chunk from the underlying iterator
|
// Pull next chunk from the underlying iterator
|
||||||
val it = chunksIterator.invokeInstanceMethod(scope, "iterator")
|
val it = chunksIterator.invokeInstanceMethod(actualScope, "iterator")
|
||||||
val hasNext = it.invokeInstanceMethod(scope, "hasNext").toBool()
|
val hasNext = it.invokeInstanceMethod(actualScope, "hasNext").toBool()
|
||||||
if (!hasNext) {
|
if (!hasNext) {
|
||||||
exhausted = true
|
exhausted = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val next = it.invokeInstanceMethod(scope, "next")
|
val next = it.invokeInstanceMethod(actualScope, "next")
|
||||||
buffer += next.toString()
|
buffer += next.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,10 +20,11 @@ package net.sergeych.lyng.io.process
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import net.sergeych.lyng.ModuleScope
|
import net.sergeych.lyng.ModuleScope
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.ScopeFacade
|
||||||
|
import net.sergeych.lyng.requireScope
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
import net.sergeych.lyng.statement
|
|
||||||
import net.sergeych.lyngio.process.*
|
import net.sergeych.lyngio.process.*
|
||||||
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
|
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
|
||||||
import net.sergeych.lyngio.process.security.ProcessAccessPolicy
|
import net.sergeych.lyngio.process.security.ProcessAccessPolicy
|
||||||
@ -205,20 +206,21 @@ class ObjRunningProcess(
|
|||||||
override fun toString(): String = "RunningProcess($process)"
|
override fun toString(): String = "RunningProcess($process)"
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend inline fun Scope.processGuard(crossinline block: suspend () -> Obj): Obj {
|
private suspend inline fun ScopeFacade.processGuard(crossinline block: suspend () -> Obj): Obj {
|
||||||
return try {
|
return try {
|
||||||
block()
|
block()
|
||||||
} catch (e: ProcessAccessDeniedException) {
|
} catch (e: ProcessAccessDeniedException) {
|
||||||
raiseError(ObjIllegalOperationException(this, e.reasonDetail ?: "process access denied"))
|
raiseError(ObjIllegalOperationException(requireScope(), e.reasonDetail ?: "process access denied"))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
raiseError(ObjIllegalOperationException(this, e.message ?: "process error"))
|
raiseError(ObjIllegalOperationException(requireScope(), e.message ?: "process error"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
|
private fun Flow<String>.toLyngFlow(flowScope: ScopeFacade): ObjFlow {
|
||||||
val producer = statement {
|
val producer = net.sergeych.lyng.obj.ObjExternCallable.fromBridge {
|
||||||
val builder = (this as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
|
val scope = requireScope()
|
||||||
?: this.thisObj as? ObjFlowBuilder
|
val builder = (scope as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
|
||||||
|
?: scope.thisObj as? ObjFlowBuilder
|
||||||
|
|
||||||
this@toLyngFlow.collect {
|
this@toLyngFlow.collect {
|
||||||
try {
|
try {
|
||||||
@ -230,5 +232,5 @@ private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
|
|||||||
}
|
}
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
return ObjFlow(producer, flowScope)
|
return ObjFlow(producer, flowScope.requireScope())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "1.2.1"
|
version = "1.5.0-SNAPSHOT"
|
||||||
|
|
||||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ package net.sergeych.lyng
|
|||||||
import net.sergeych.lyng.miniast.MiniTypeRef
|
import net.sergeych.lyng.miniast.MiniTypeRef
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjList
|
import net.sergeych.lyng.obj.ObjList
|
||||||
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,30 +62,59 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasComplex) {
|
if (!hasComplex) {
|
||||||
if (arguments.list.size != params.size)
|
if (arguments.list.size > params.size)
|
||||||
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
||||||
|
if (arguments.list.size < params.size) {
|
||||||
|
for (i in arguments.list.size until params.size) {
|
||||||
|
val a = params[i]
|
||||||
|
if (!a.type.isNullable) {
|
||||||
|
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (i in params.indices) {
|
for (i in params.indices) {
|
||||||
val a = params[i]
|
val a = params[i]
|
||||||
val value = arguments.list[i]
|
val value = if (i < arguments.list.size) arguments.list[i] else ObjNull
|
||||||
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable,
|
val recordType = if (declaringClass != null && a.accessType != null) {
|
||||||
|
ObjRecord.Type.ConstructorField
|
||||||
|
} else {
|
||||||
|
ObjRecord.Type.Argument
|
||||||
|
}
|
||||||
|
scope.addItem(
|
||||||
|
a.name,
|
||||||
|
(a.accessType ?: defaultAccessType).isMutable,
|
||||||
value.byValueCopy(),
|
value.byValueCopy(),
|
||||||
a.visibility ?: defaultVisibility,
|
a.visibility ?: defaultVisibility,
|
||||||
recordType = ObjRecord.Type.Argument,
|
recordType = recordType,
|
||||||
declaringClass = declaringClass,
|
declaringClass = declaringClass,
|
||||||
isTransient = a.isTransient)
|
isTransient = a.isTransient
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assign(a: Item, value: Obj) {
|
fun assign(a: Item, value: Obj) {
|
||||||
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable,
|
val recordType = if (declaringClass != null && a.accessType != null) {
|
||||||
|
ObjRecord.Type.ConstructorField
|
||||||
|
} else {
|
||||||
|
ObjRecord.Type.Argument
|
||||||
|
}
|
||||||
|
scope.addItem(
|
||||||
|
a.name,
|
||||||
|
(a.accessType ?: defaultAccessType).isMutable,
|
||||||
value.byValueCopy(),
|
value.byValueCopy(),
|
||||||
a.visibility ?: defaultVisibility,
|
a.visibility ?: defaultVisibility,
|
||||||
recordType = ObjRecord.Type.Argument,
|
recordType = recordType,
|
||||||
declaringClass = declaringClass,
|
declaringClass = declaringClass,
|
||||||
isTransient = a.isTransient)
|
isTransient = a.isTransient
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun missingValue(a: Item, error: String): Obj {
|
||||||
|
return a.defaultValue?.callOn(scope)
|
||||||
|
?: if (a.type.isNullable) ObjNull else scope.raiseIllegalArgument(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare positional args and parameter count, handle tail-block binding
|
// Prepare positional args and parameter count, handle tail-block binding
|
||||||
@ -165,8 +195,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
assign(a, namedValues[i]!!)
|
assign(a, namedValues[i]!!)
|
||||||
} else {
|
} else {
|
||||||
val value = if (hp < callArgs.size) callArgs[hp++]
|
val value = if (hp < callArgs.size) callArgs[hp++]
|
||||||
else a.defaultValue?.execute(scope)
|
else missingValue(a, "too few arguments for the call (missing ${a.name})")
|
||||||
?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})")
|
|
||||||
assign(a, value)
|
assign(a, value)
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
@ -186,8 +215,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
assign(a, namedValues[i]!!)
|
assign(a, namedValues[i]!!)
|
||||||
} else {
|
} else {
|
||||||
val value = if (tp >= headPosBound) callArgs[tp--]
|
val value = if (tp >= headPosBound) callArgs[tp--]
|
||||||
else a.defaultValue?.execute(scope)
|
else missingValue(a, "too few arguments for the call")
|
||||||
?: scope.raiseIllegalArgument("too few arguments for the call")
|
|
||||||
assign(a, value)
|
assign(a, value)
|
||||||
}
|
}
|
||||||
i--
|
i--
|
||||||
@ -222,10 +250,194 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign arguments directly into frame slots using [paramSlotPlan] without creating scope locals.
|
||||||
|
* Default expressions must resolve through frame slots (no scope mirroring).
|
||||||
|
*/
|
||||||
|
suspend fun assignToFrame(
|
||||||
|
scope: Scope,
|
||||||
|
arguments: Arguments = scope.args,
|
||||||
|
paramSlotPlan: Map<String, Int>,
|
||||||
|
frame: FrameAccess,
|
||||||
|
slotOffset: Int = 0
|
||||||
|
) {
|
||||||
|
fun slotFor(name: String): Int {
|
||||||
|
val full = paramSlotPlan[name] ?: scope.raiseIllegalState("parameter slot for '$name' is missing")
|
||||||
|
val slot = full - slotOffset
|
||||||
|
if (slot < 0) scope.raiseIllegalState("parameter slot for '$name' is out of range")
|
||||||
|
return slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFrameValue(slot: Int, value: Obj) {
|
||||||
|
when (value) {
|
||||||
|
is net.sergeych.lyng.obj.ObjInt -> frame.setInt(slot, value.value)
|
||||||
|
is net.sergeych.lyng.obj.ObjReal -> frame.setReal(slot, value.value)
|
||||||
|
is net.sergeych.lyng.obj.ObjBool -> frame.setBool(slot, value.value)
|
||||||
|
else -> frame.setObj(slot, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assign(a: Item, value: Obj) {
|
||||||
|
val slot = slotFor(a.name)
|
||||||
|
setFrameValue(slot, value.byValueCopy())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun missingValue(a: Item, error: String): Obj {
|
||||||
|
return a.defaultValue?.callOn(scope)
|
||||||
|
?: if (a.type.isNullable) ObjNull else scope.raiseIllegalArgument(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path for simple positional-only calls with no ellipsis and no defaults
|
||||||
|
if (arguments.named.isEmpty() && !arguments.tailBlockMode) {
|
||||||
|
var hasComplex = false
|
||||||
|
for (p in params) {
|
||||||
|
if (p.isEllipsis || p.defaultValue != null) {
|
||||||
|
hasComplex = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasComplex) {
|
||||||
|
if (arguments.list.size > params.size)
|
||||||
|
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
||||||
|
if (arguments.list.size < params.size) {
|
||||||
|
for (i in arguments.list.size until params.size) {
|
||||||
|
val a = params[i]
|
||||||
|
if (!a.type.isNullable) {
|
||||||
|
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in params.indices) {
|
||||||
|
val a = params[i]
|
||||||
|
val value = if (i < arguments.list.size) arguments.list[i] else ObjNull
|
||||||
|
assign(a, value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare positional args and parameter count, handle tail-block binding
|
||||||
|
val callArgs: List<Obj>
|
||||||
|
val paramsSize: Int
|
||||||
|
if (arguments.tailBlockMode) {
|
||||||
|
val lastParam = params.last()
|
||||||
|
if (arguments.named.containsKey(lastParam.name))
|
||||||
|
scope.raiseIllegalArgument("trailing block cannot be used when the last parameter is already assigned by a named argument")
|
||||||
|
paramsSize = params.size - 1
|
||||||
|
assign(lastParam, arguments.list.last())
|
||||||
|
callArgs = arguments.list.dropLast(1)
|
||||||
|
} else {
|
||||||
|
paramsSize = params.size
|
||||||
|
callArgs = arguments.list
|
||||||
|
}
|
||||||
|
|
||||||
|
val coveredByPositional = BooleanArray(paramsSize)
|
||||||
|
run {
|
||||||
|
var headRequired = 0
|
||||||
|
var tailRequired = 0
|
||||||
|
val ellipsisIdx = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||||
|
if (ellipsisIdx >= 0) {
|
||||||
|
for (i in 0 until ellipsisIdx) if (!params[i].isEllipsis && params[i].defaultValue == null) headRequired++
|
||||||
|
for (i in paramsSize - 1 downTo ellipsisIdx + 1) if (params[i].defaultValue == null) tailRequired++
|
||||||
|
} else {
|
||||||
|
for (i in 0 until paramsSize) if (params[i].defaultValue == null) headRequired++
|
||||||
|
}
|
||||||
|
val P = callArgs.size
|
||||||
|
if (ellipsisIdx < 0) {
|
||||||
|
val k = minOf(P, paramsSize)
|
||||||
|
for (i in 0 until k) coveredByPositional[i] = true
|
||||||
|
} else {
|
||||||
|
val headTake = minOf(P, headRequired)
|
||||||
|
for (i in 0 until headTake) coveredByPositional[i] = true
|
||||||
|
val remaining = P - headTake
|
||||||
|
val tailTake = minOf(remaining, tailRequired)
|
||||||
|
var j = paramsSize - 1
|
||||||
|
var taken = 0
|
||||||
|
while (j > ellipsisIdx && taken < tailTake) {
|
||||||
|
coveredByPositional[j] = true
|
||||||
|
j--
|
||||||
|
taken++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val assignedByName = BooleanArray(paramsSize)
|
||||||
|
val namedValues = arrayOfNulls<Obj>(paramsSize)
|
||||||
|
if (arguments.named.isNotEmpty()) {
|
||||||
|
for ((k, v) in arguments.named) {
|
||||||
|
val idx = params.subList(0, paramsSize).indexOfFirst { it.name == k }
|
||||||
|
if (idx < 0) scope.raiseIllegalArgument("unknown parameter '$k'")
|
||||||
|
if (params[idx].isEllipsis) scope.raiseIllegalArgument("ellipsis (variadic) parameter cannot be assigned by name: '$k'")
|
||||||
|
if (coveredByPositional[idx]) scope.raiseIllegalArgument("argument '$k' is already set by positional argument")
|
||||||
|
if (assignedByName[idx]) scope.raiseIllegalArgument("argument '$k' is already set")
|
||||||
|
assignedByName[idx] = true
|
||||||
|
namedValues[idx] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun processHead(index: Int, headPos: Int): Pair<Int, Int> {
|
||||||
|
var i = index
|
||||||
|
var hp = headPos
|
||||||
|
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++]
|
||||||
|
else missingValue(a, "too few arguments for the call (missing ${a.name})")
|
||||||
|
assign(a, value)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i to hp
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun processTail(startExclusive: Int, tailStart: Int, headPosBound: Int): Int {
|
||||||
|
var i = paramsSize - 1
|
||||||
|
var tp = tailStart
|
||||||
|
while (i > startExclusive) {
|
||||||
|
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--]
|
||||||
|
else missingValue(a, "too few arguments for the call")
|
||||||
|
assign(a, value)
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
return tp
|
||||||
|
}
|
||||||
|
|
||||||
|
fun processEllipsis(index: Int, headPos: Int, tailPos: Int) {
|
||||||
|
val a = params[index]
|
||||||
|
val from = headPos
|
||||||
|
val to = tailPos
|
||||||
|
val l = if (from > to) ObjList()
|
||||||
|
else ObjList(callArgs.subList(from, to + 1).toMutableList())
|
||||||
|
assign(a, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||||
|
|
||||||
|
if (ellipsisIndex >= 0) {
|
||||||
|
val (_, headConsumedTo) = processHead(0, 0)
|
||||||
|
val tailConsumedFrom = processTail(ellipsisIndex, callArgs.size - 1, headConsumedTo)
|
||||||
|
processEllipsis(ellipsisIndex, headConsumedTo, tailConsumedFrom)
|
||||||
|
} else {
|
||||||
|
val (_, headConsumedTo) = processHead(0, 0)
|
||||||
|
if (headConsumedTo != callArgs.size)
|
||||||
|
scope.raiseIllegalArgument("too many arguments for the call")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single argument declaration descriptor.
|
* Single argument declaration descriptor.
|
||||||
*
|
*
|
||||||
* @param defaultValue default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc.
|
* @param defaultValue default value, callable evaluated at call site.
|
||||||
* If not null, could be executed on __caller context__ only.
|
* If not null, could be executed on __caller context__ only.
|
||||||
*/
|
*/
|
||||||
data class Item(
|
data class Item(
|
||||||
@ -238,7 +450,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
* Default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc.
|
* Default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc.
|
||||||
* So it is a [Statement] that must be executed on __caller context__.
|
* So it is a [Statement] that must be executed on __caller context__.
|
||||||
*/
|
*/
|
||||||
val defaultValue: Statement? = null,
|
val defaultValue: Obj? = null,
|
||||||
val accessType: AccessType? = null,
|
val accessType: AccessType? = null,
|
||||||
val visibility: Visibility? = null,
|
val visibility: Visibility? = null,
|
||||||
val isTransient: Boolean = false,
|
val isTransient: Boolean = false,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ package net.sergeych.lyng
|
|||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
|
|
||||||
data class ParsedArgument(
|
data class ParsedArgument(
|
||||||
val value: Statement,
|
val value: Obj,
|
||||||
val pos: Pos,
|
val pos: Pos,
|
||||||
val isSplat: Boolean = false,
|
val isSplat: Boolean = false,
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
@ -40,115 +40,115 @@ data class ParsedArgument(
|
|||||||
if (!hasSplatOrNamed && count == this.size) {
|
if (!hasSplatOrNamed && count == this.size) {
|
||||||
val quick = when (count) {
|
val quick = when (count) {
|
||||||
0 -> Arguments.EMPTY
|
0 -> Arguments.EMPTY
|
||||||
1 -> Arguments(listOf(this.elementAt(0).value.execute(scope)), tailBlockMode)
|
1 -> Arguments(listOf(this.elementAt(0).value.callOn(scope)), tailBlockMode)
|
||||||
2 -> {
|
2 -> {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1), tailBlockMode)
|
Arguments(listOf(a0, a1), tailBlockMode)
|
||||||
}
|
}
|
||||||
3 -> {
|
3 -> {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2), tailBlockMode)
|
Arguments(listOf(a0, a1, a2), tailBlockMode)
|
||||||
}
|
}
|
||||||
4 -> {
|
4 -> {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
val a3 = this.elementAt(3).value.execute(scope)
|
val a3 = this.elementAt(3).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3), tailBlockMode)
|
||||||
}
|
}
|
||||||
5 -> {
|
5 -> {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
val a3 = this.elementAt(3).value.execute(scope)
|
val a3 = this.elementAt(3).value.callOn(scope)
|
||||||
val a4 = this.elementAt(4).value.execute(scope)
|
val a4 = this.elementAt(4).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3, a4), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3, a4), tailBlockMode)
|
||||||
}
|
}
|
||||||
6 -> {
|
6 -> {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
val a3 = this.elementAt(3).value.execute(scope)
|
val a3 = this.elementAt(3).value.callOn(scope)
|
||||||
val a4 = this.elementAt(4).value.execute(scope)
|
val a4 = this.elementAt(4).value.callOn(scope)
|
||||||
val a5 = this.elementAt(5).value.execute(scope)
|
val a5 = this.elementAt(5).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3, a4, a5), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3, a4, a5), tailBlockMode)
|
||||||
}
|
}
|
||||||
7 -> {
|
7 -> {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
val a3 = this.elementAt(3).value.execute(scope)
|
val a3 = this.elementAt(3).value.callOn(scope)
|
||||||
val a4 = this.elementAt(4).value.execute(scope)
|
val a4 = this.elementAt(4).value.callOn(scope)
|
||||||
val a5 = this.elementAt(5).value.execute(scope)
|
val a5 = this.elementAt(5).value.callOn(scope)
|
||||||
val a6 = this.elementAt(6).value.execute(scope)
|
val a6 = this.elementAt(6).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6), tailBlockMode)
|
||||||
}
|
}
|
||||||
8 -> {
|
8 -> {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
val a3 = this.elementAt(3).value.execute(scope)
|
val a3 = this.elementAt(3).value.callOn(scope)
|
||||||
val a4 = this.elementAt(4).value.execute(scope)
|
val a4 = this.elementAt(4).value.callOn(scope)
|
||||||
val a5 = this.elementAt(5).value.execute(scope)
|
val a5 = this.elementAt(5).value.callOn(scope)
|
||||||
val a6 = this.elementAt(6).value.execute(scope)
|
val a6 = this.elementAt(6).value.callOn(scope)
|
||||||
val a7 = this.elementAt(7).value.execute(scope)
|
val a7 = this.elementAt(7).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7), tailBlockMode)
|
||||||
}
|
}
|
||||||
9 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
9 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
val a3 = this.elementAt(3).value.execute(scope)
|
val a3 = this.elementAt(3).value.callOn(scope)
|
||||||
val a4 = this.elementAt(4).value.execute(scope)
|
val a4 = this.elementAt(4).value.callOn(scope)
|
||||||
val a5 = this.elementAt(5).value.execute(scope)
|
val a5 = this.elementAt(5).value.callOn(scope)
|
||||||
val a6 = this.elementAt(6).value.execute(scope)
|
val a6 = this.elementAt(6).value.callOn(scope)
|
||||||
val a7 = this.elementAt(7).value.execute(scope)
|
val a7 = this.elementAt(7).value.callOn(scope)
|
||||||
val a8 = this.elementAt(8).value.execute(scope)
|
val a8 = this.elementAt(8).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8), tailBlockMode)
|
||||||
} else null
|
} else null
|
||||||
10 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
10 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
val a3 = this.elementAt(3).value.execute(scope)
|
val a3 = this.elementAt(3).value.callOn(scope)
|
||||||
val a4 = this.elementAt(4).value.execute(scope)
|
val a4 = this.elementAt(4).value.callOn(scope)
|
||||||
val a5 = this.elementAt(5).value.execute(scope)
|
val a5 = this.elementAt(5).value.callOn(scope)
|
||||||
val a6 = this.elementAt(6).value.execute(scope)
|
val a6 = this.elementAt(6).value.callOn(scope)
|
||||||
val a7 = this.elementAt(7).value.execute(scope)
|
val a7 = this.elementAt(7).value.callOn(scope)
|
||||||
val a8 = this.elementAt(8).value.execute(scope)
|
val a8 = this.elementAt(8).value.callOn(scope)
|
||||||
val a9 = this.elementAt(9).value.execute(scope)
|
val a9 = this.elementAt(9).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9), tailBlockMode)
|
||||||
} else null
|
} else null
|
||||||
11 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
11 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
val a3 = this.elementAt(3).value.execute(scope)
|
val a3 = this.elementAt(3).value.callOn(scope)
|
||||||
val a4 = this.elementAt(4).value.execute(scope)
|
val a4 = this.elementAt(4).value.callOn(scope)
|
||||||
val a5 = this.elementAt(5).value.execute(scope)
|
val a5 = this.elementAt(5).value.callOn(scope)
|
||||||
val a6 = this.elementAt(6).value.execute(scope)
|
val a6 = this.elementAt(6).value.callOn(scope)
|
||||||
val a7 = this.elementAt(7).value.execute(scope)
|
val a7 = this.elementAt(7).value.callOn(scope)
|
||||||
val a8 = this.elementAt(8).value.execute(scope)
|
val a8 = this.elementAt(8).value.callOn(scope)
|
||||||
val a9 = this.elementAt(9).value.execute(scope)
|
val a9 = this.elementAt(9).value.callOn(scope)
|
||||||
val a10 = this.elementAt(10).value.execute(scope)
|
val a10 = this.elementAt(10).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10), tailBlockMode)
|
||||||
} else null
|
} else null
|
||||||
12 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
12 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
||||||
val a0 = this.elementAt(0).value.execute(scope)
|
val a0 = this.elementAt(0).value.callOn(scope)
|
||||||
val a1 = this.elementAt(1).value.execute(scope)
|
val a1 = this.elementAt(1).value.callOn(scope)
|
||||||
val a2 = this.elementAt(2).value.execute(scope)
|
val a2 = this.elementAt(2).value.callOn(scope)
|
||||||
val a3 = this.elementAt(3).value.execute(scope)
|
val a3 = this.elementAt(3).value.callOn(scope)
|
||||||
val a4 = this.elementAt(4).value.execute(scope)
|
val a4 = this.elementAt(4).value.callOn(scope)
|
||||||
val a5 = this.elementAt(5).value.execute(scope)
|
val a5 = this.elementAt(5).value.callOn(scope)
|
||||||
val a6 = this.elementAt(6).value.execute(scope)
|
val a6 = this.elementAt(6).value.callOn(scope)
|
||||||
val a7 = this.elementAt(7).value.execute(scope)
|
val a7 = this.elementAt(7).value.callOn(scope)
|
||||||
val a8 = this.elementAt(8).value.execute(scope)
|
val a8 = this.elementAt(8).value.callOn(scope)
|
||||||
val a9 = this.elementAt(9).value.execute(scope)
|
val a9 = this.elementAt(9).value.callOn(scope)
|
||||||
val a10 = this.elementAt(10).value.execute(scope)
|
val a10 = this.elementAt(10).value.callOn(scope)
|
||||||
val a11 = this.elementAt(11).value.execute(scope)
|
val a11 = this.elementAt(11).value.callOn(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11), tailBlockMode)
|
||||||
} else null
|
} else null
|
||||||
else -> null
|
else -> null
|
||||||
@ -166,12 +166,12 @@ data class ParsedArgument(
|
|||||||
// Named argument
|
// Named argument
|
||||||
if (named == null) named = linkedMapOf()
|
if (named == null) named = linkedMapOf()
|
||||||
if (named.containsKey(x.name)) scope.raiseIllegalArgument("argument '${x.name}' is already set")
|
if (named.containsKey(x.name)) scope.raiseIllegalArgument("argument '${x.name}' is already set")
|
||||||
val v = x.value.execute(scope)
|
val v = x.value.callOn(scope)
|
||||||
named[x.name] = v
|
named[x.name] = v
|
||||||
namedSeen = true
|
namedSeen = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val value = x.value.execute(scope)
|
val value = x.value.callOn(scope)
|
||||||
if (x.isSplat) {
|
if (x.isSplat) {
|
||||||
when {
|
when {
|
||||||
// IMPORTANT: handle ObjMap BEFORE generic Iterable to ensure map splats
|
// IMPORTANT: handle ObjMap BEFORE generic Iterable to ensure map splats
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
class BlockStatement(
|
||||||
|
val block: Script,
|
||||||
|
val slotPlan: Map<String, Int>,
|
||||||
|
val scopeId: Int,
|
||||||
|
val captureSlots: List<CaptureSlot> = emptyList(),
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return bytecodeOnly(scope, "block statement")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun statements(): List<Statement> = block.debugStatements()
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
|
|
||||||
|
interface BytecodeBodyProvider {
|
||||||
|
fun bytecodeBody(): BytecodeStatement?
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
interface BytecodeCallable
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
|
import net.sergeych.lyng.bytecode.CmdVm
|
||||||
|
import net.sergeych.lyng.bytecode.seedFrameLocalsFromScope
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
internal suspend fun executeBytecodeWithSeed(scope: Scope, stmt: Statement, label: String): Obj {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
} ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
scope.pos = bytecode.pos
|
||||||
|
return CmdVm().execute(bytecode.bytecodeFunction(), scope, scope.args) { frame, _ ->
|
||||||
|
seedFrameLocalsFromScope(frame, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile-time call metadata for known functions. Used to select lambda receiver semantics.
|
||||||
|
*/
|
||||||
|
data class CallSignature(
|
||||||
|
val tailBlockReceiverType: String? = null
|
||||||
|
)
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
data class CaptureSlot(
|
||||||
|
val name: String,
|
||||||
|
val ownerScopeId: Int? = null,
|
||||||
|
val ownerSlot: Int? = null,
|
||||||
|
)
|
||||||
@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.sergeych.lyng.obj.*
|
||||||
|
|
||||||
|
data class ClassDeclBaseSpec(
|
||||||
|
val name: String,
|
||||||
|
val args: List<ParsedArgument>?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ClassDeclSpec(
|
||||||
|
val declaredName: String?,
|
||||||
|
val className: String,
|
||||||
|
val typeName: String,
|
||||||
|
val startPos: Pos,
|
||||||
|
val isExtern: Boolean,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isObject: Boolean,
|
||||||
|
val isAnonymous: Boolean,
|
||||||
|
val baseSpecs: List<ClassDeclBaseSpec>,
|
||||||
|
val constructorArgs: ArgsDeclaration?,
|
||||||
|
val constructorFieldIds: Map<String, Int>?,
|
||||||
|
val bodyInit: Statement?,
|
||||||
|
val initScope: List<Statement>,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal suspend fun executeClassDecl(
|
||||||
|
scope: Scope,
|
||||||
|
spec: ClassDeclSpec,
|
||||||
|
bodyCaptureRecords: List<ObjRecord>? = null,
|
||||||
|
bodyCaptureNames: List<String>? = null
|
||||||
|
): Obj {
|
||||||
|
fun checkClosedParents(parents: List<ObjClass>, pos: Pos) {
|
||||||
|
val closedParent = parents.firstOrNull { it.isClosed } ?: return
|
||||||
|
throw ScriptError(pos, "can't inherit from closed class ${closedParent.className}")
|
||||||
|
}
|
||||||
|
if (spec.isObject) {
|
||||||
|
val parentClasses = spec.baseSpecs.map { baseSpec ->
|
||||||
|
val rec = scope[baseSpec.name] ?: throw ScriptError(spec.startPos, "unknown base class: ${baseSpec.name}")
|
||||||
|
(rec.value as? ObjClass) ?: throw ScriptError(spec.startPos, "${baseSpec.name} is not a class")
|
||||||
|
}
|
||||||
|
checkClosedParents(parentClasses, spec.startPos)
|
||||||
|
|
||||||
|
val newClass = ObjInstanceClass(spec.className, *parentClasses.toTypedArray())
|
||||||
|
newClass.isAnonymous = spec.isAnonymous
|
||||||
|
newClass.constructorMeta = ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||||
|
for (i in parentClasses.indices) {
|
||||||
|
val argsList = spec.baseSpecs[i].args
|
||||||
|
if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList
|
||||||
|
}
|
||||||
|
|
||||||
|
val classScope = scope.createChildScope(newThisObj = newClass)
|
||||||
|
if (!bodyCaptureRecords.isNullOrEmpty() && !bodyCaptureNames.isNullOrEmpty()) {
|
||||||
|
classScope.captureRecords = bodyCaptureRecords
|
||||||
|
classScope.captureNames = bodyCaptureNames
|
||||||
|
}
|
||||||
|
classScope.currentClassCtx = newClass
|
||||||
|
newClass.classScope = classScope
|
||||||
|
classScope.addConst("object", newClass)
|
||||||
|
|
||||||
|
spec.bodyInit?.let { executeBytecodeWithSeed(classScope, it, "object body init") }
|
||||||
|
|
||||||
|
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
|
||||||
|
if (spec.declaredName != null) {
|
||||||
|
scope.addItem(spec.declaredName, false, instance)
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.isExtern) {
|
||||||
|
val rec = scope[spec.className]
|
||||||
|
val existing = rec?.value as? ObjClass
|
||||||
|
val resolved = if (existing != null) {
|
||||||
|
existing
|
||||||
|
} else if (spec.className.contains('.')) {
|
||||||
|
scope.resolveQualifiedIdentifier(spec.className) as? ObjClass
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val stub = resolved ?: ObjInstanceClass(spec.className).apply { this.isAbstract = true }
|
||||||
|
spec.declaredName?.let { scope.addItem(it, false, stub) }
|
||||||
|
return stub
|
||||||
|
}
|
||||||
|
|
||||||
|
val parentClasses = spec.baseSpecs.map { baseSpec ->
|
||||||
|
val rec = scope[baseSpec.name]
|
||||||
|
val cls = rec?.value as? ObjClass
|
||||||
|
if (cls != null) return@map cls
|
||||||
|
if (baseSpec.name == "Exception") return@map ObjException.Root
|
||||||
|
if (rec == null) throw ScriptError(spec.startPos, "unknown base class: ${baseSpec.name}")
|
||||||
|
throw ScriptError(spec.startPos, "${baseSpec.name} is not a class")
|
||||||
|
}
|
||||||
|
checkClosedParents(parentClasses, spec.startPos)
|
||||||
|
|
||||||
|
val constructorCode = object : Statement() {
|
||||||
|
override val pos: Pos = spec.startPos
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val instance = scope.thisObj as ObjInstance
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newClass = ObjInstanceClass(spec.className, *parentClasses.toTypedArray()).also {
|
||||||
|
it.isAbstract = spec.isAbstract
|
||||||
|
it.isClosed = spec.isClosed
|
||||||
|
it.instanceConstructor = constructorCode
|
||||||
|
it.constructorMeta = spec.constructorArgs
|
||||||
|
for (i in parentClasses.indices) {
|
||||||
|
val argsList = spec.baseSpecs[i].args
|
||||||
|
if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList
|
||||||
|
}
|
||||||
|
spec.constructorArgs?.params?.forEach { p ->
|
||||||
|
if (p.accessType != null) {
|
||||||
|
it.createField(
|
||||||
|
p.name,
|
||||||
|
ObjNull,
|
||||||
|
isMutable = p.accessType == AccessType.Var,
|
||||||
|
visibility = p.visibility ?: Visibility.Public,
|
||||||
|
declaringClass = it,
|
||||||
|
pos = Pos.builtIn,
|
||||||
|
isTransient = p.isTransient,
|
||||||
|
type = ObjRecord.Type.ConstructorField,
|
||||||
|
fieldId = spec.constructorFieldIds?.get(p.name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.declaredName?.let { name ->
|
||||||
|
scope.addItem(name, false, newClass)
|
||||||
|
val module = scope as? ModuleScope
|
||||||
|
val frame = module?.moduleFrame
|
||||||
|
if (module != null && frame != null) {
|
||||||
|
val idx = module.moduleFrameLocalSlotNames.indexOf(name)
|
||||||
|
if (idx >= 0) {
|
||||||
|
frame.setObj(idx, newClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val classScope = scope.createChildScope(newThisObj = newClass)
|
||||||
|
if (!bodyCaptureRecords.isNullOrEmpty() && !bodyCaptureNames.isNullOrEmpty()) {
|
||||||
|
classScope.captureRecords = bodyCaptureRecords
|
||||||
|
classScope.captureNames = bodyCaptureNames
|
||||||
|
}
|
||||||
|
classScope.currentClassCtx = newClass
|
||||||
|
newClass.classScope = classScope
|
||||||
|
spec.bodyInit?.let { executeBytecodeWithSeed(classScope, it, "class body init") }
|
||||||
|
if (spec.initScope.isNotEmpty()) {
|
||||||
|
for (s in spec.initScope) {
|
||||||
|
executeBytecodeWithSeed(classScope, s, "class init")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newClass.checkAbstractSatisfaction(spec.startPos)
|
||||||
|
return newClass
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun requireBytecodeBody(
|
||||||
|
scope: Scope,
|
||||||
|
stmt: Statement,
|
||||||
|
label: String
|
||||||
|
): net.sergeych.lyng.bytecode.BytecodeStatement {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassDeclStatement(
|
||||||
|
val spec: ClassDeclSpec,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = spec.startPos
|
||||||
|
val declaredName: String? get() = spec.declaredName
|
||||||
|
val typeName: String get() = spec.typeName
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return executeClassDecl(scope, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val target = scope.parent ?: scope
|
||||||
|
return executeClassDecl(target, spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjProperty
|
||||||
|
|
||||||
|
class ClassInstanceInitDeclStatement(
|
||||||
|
val initStatement: Statement,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return bytecodeOnly(scope, "class instance init declaration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassInstanceFieldDeclStatement(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val fieldId: Int?,
|
||||||
|
val initStatement: Statement?,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return bytecodeOnly(scope, "class instance field declaration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassInstancePropertyDeclStatement(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val prop: ObjProperty,
|
||||||
|
val methodId: Int?,
|
||||||
|
val initStatement: Statement?,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return bytecodeOnly(scope, "class instance property declaration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassInstanceDelegatedDeclStatement(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val methodId: Int?,
|
||||||
|
val initStatement: Statement?,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return bytecodeOnly(scope, "class instance delegated declaration")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjString
|
||||||
|
import net.sergeych.lyng.obj.ObjUnset
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
|
||||||
|
class ClassStaticFieldInitStatement(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val initializer: Statement?,
|
||||||
|
val isDelegated: Boolean,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val initValue = initializer?.let { execBytecodeOnly(scope, it, "class static field init") }?.byValueCopy()
|
||||||
|
?: ObjNull
|
||||||
|
val cls = scope.thisObj as? ObjClass
|
||||||
|
?: scope.raiseIllegalState("static field init requires class scope")
|
||||||
|
return if (isDelegated) {
|
||||||
|
val accessTypeStr = if (isMutable) "Var" else "Val"
|
||||||
|
val accessType = ObjString(accessTypeStr)
|
||||||
|
val finalDelegate = try {
|
||||||
|
initValue.invokeInstanceMethod(
|
||||||
|
scope,
|
||||||
|
"bind",
|
||||||
|
Arguments(ObjString(name), accessType, scope.thisObj)
|
||||||
|
)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
initValue
|
||||||
|
}
|
||||||
|
cls.createClassField(
|
||||||
|
name,
|
||||||
|
ObjUnset,
|
||||||
|
isMutable,
|
||||||
|
visibility,
|
||||||
|
writeVisibility,
|
||||||
|
startPos,
|
||||||
|
isTransient = isTransient,
|
||||||
|
type = ObjRecord.Type.Delegated
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
scope.addItem(
|
||||||
|
name,
|
||||||
|
isMutable,
|
||||||
|
ObjUnset,
|
||||||
|
visibility,
|
||||||
|
writeVisibility,
|
||||||
|
recordType = ObjRecord.Type.Delegated,
|
||||||
|
isTransient = isTransient
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
finalDelegate
|
||||||
|
} else {
|
||||||
|
cls.createClassField(
|
||||||
|
name,
|
||||||
|
initValue,
|
||||||
|
isMutable,
|
||||||
|
visibility,
|
||||||
|
writeVisibility,
|
||||||
|
startPos,
|
||||||
|
isTransient = isTransient
|
||||||
|
)
|
||||||
|
scope.addItem(
|
||||||
|
name,
|
||||||
|
isMutable,
|
||||||
|
initValue,
|
||||||
|
visibility,
|
||||||
|
writeVisibility,
|
||||||
|
recordType = ObjRecord.Type.Field,
|
||||||
|
isTransient = isTransient
|
||||||
|
)
|
||||||
|
initValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun execBytecodeOnly(scope: Scope, stmt: Statement, label: String): Obj {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
} ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
return bytecode.execute(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,67 +18,43 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scope that adds a "closure" to caller; most often it is used to apply class instance to caller scope.
|
* Bytecode-oriented closure scope that keeps the call scope parent chain for stack traces
|
||||||
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
|
* while carrying the lexical closure for `this` variants and module resolution.
|
||||||
* from [closureScope] with proper precedence
|
* Unlike legacy closure scopes, it does not override name lookup.
|
||||||
*/
|
*/
|
||||||
class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
class BytecodeClosureScope(
|
||||||
// Important: use closureScope.thisObj so unqualified members (e.g., fields) resolve to the instance
|
val callScope: Scope,
|
||||||
// we captured, not to the caller's `this` (e.g., FlowBuilder).
|
val closureScope: Scope,
|
||||||
|
private val preferredThisType: String? = null
|
||||||
|
) :
|
||||||
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
|
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Preserve the lexical class context of the closure by default. This ensures that lambdas
|
val desired = preferredThisType?.let { typeName ->
|
||||||
// created inside a class method keep access to that class's private/protected members even
|
callScope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||||
// when executed from within another object's method (e.g., Mutex.withLock), which may set
|
}
|
||||||
// its own currentClassCtx temporarily. If the closure has no class context, inherit caller's.
|
val primaryThis = closureScope.thisObj
|
||||||
|
val merged = ArrayList<Obj>(callScope.thisVariants.size + closureScope.thisVariants.size + 1)
|
||||||
|
desired?.let { merged.add(it) }
|
||||||
|
merged.addAll(callScope.thisVariants)
|
||||||
|
merged.addAll(closureScope.thisVariants)
|
||||||
|
setThisVariants(primaryThis, merged)
|
||||||
this.currentClassCtx = closureScope.currentClassCtx ?: callScope.currentClassCtx
|
this.currentClassCtx = closureScope.currentClassCtx ?: callScope.currentClassCtx
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun get(name: String): ObjRecord? {
|
|
||||||
if (name == "this") return thisObj.asReadonly
|
|
||||||
|
|
||||||
// 1. Current frame locals (parameters, local variables)
|
|
||||||
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
|
|
||||||
|
|
||||||
// 2. Lexical environment (captured locals from entire ancestry)
|
|
||||||
closureScope.chainLookupIgnoreClosure(name, followClosure = true, caller = currentClassCtx)?.let { return it }
|
|
||||||
|
|
||||||
// 3. Lexical this members (captured receiver)
|
|
||||||
val receiver = thisObj
|
|
||||||
val effectiveClass = receiver as? ObjClass ?: receiver.objClass
|
|
||||||
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, name)) {
|
|
||||||
return rec.copy(receiver = receiver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Finally, root object fallback
|
|
||||||
Obj.rootObjectType.members[name]?.let { rec ->
|
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
|
||||||
return rec.copy(receiver = receiver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Call environment (caller locals, caller this, and global fallback)
|
|
||||||
return callScope.get(name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApplyScope(_parent: Scope,val applied: Scope) : Scope(_parent, thisObj = applied.thisObj) {
|
class ApplyScope(val callScope: Scope, val applied: Scope) :
|
||||||
|
Scope(callScope, thisObj = applied.thisObj) {
|
||||||
|
|
||||||
override fun get(name: String): ObjRecord? {
|
override fun get(name: String): ObjRecord? {
|
||||||
return applied.get(name) ?: super.get(name)
|
return applied.get(name) ?: super.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyClosure(closure: Scope): Scope {
|
override fun applyClosure(closure: Scope, preferredThisType: String?): Scope {
|
||||||
return this
|
return BytecodeClosureScope(this, closure, preferredThisType)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -19,8 +19,24 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
sealed class CodeContext {
|
sealed class CodeContext {
|
||||||
class Module(@Suppress("unused") val packageName: String?): CodeContext()
|
class Module(@Suppress("unused") val packageName: String?): CodeContext()
|
||||||
class Function(val name: String): CodeContext()
|
class Function(
|
||||||
|
val name: String,
|
||||||
|
val implicitThisMembers: Boolean = false,
|
||||||
|
val implicitThisTypeName: String? = null,
|
||||||
|
val typeParams: Set<String> = emptySet(),
|
||||||
|
val typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
||||||
|
): CodeContext()
|
||||||
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
||||||
|
var typeParams: Set<String> = emptySet()
|
||||||
|
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
||||||
val pendingInitializations = mutableMapOf<String, Pos>()
|
val pendingInitializations = mutableMapOf<String, Pos>()
|
||||||
|
val declaredMembers = mutableSetOf<String>()
|
||||||
|
val classScopeMembers = mutableSetOf<String>()
|
||||||
|
val memberOverrides = mutableMapOf<String, Boolean>()
|
||||||
|
val memberFieldIds = mutableMapOf<String, Int>()
|
||||||
|
val memberMethodIds = mutableMapOf<String, Int>()
|
||||||
|
var nextFieldId: Int = 0
|
||||||
|
var nextMethodId: Int = 0
|
||||||
|
var slotPlanId: Int? = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
class DelegatedVarDeclStatement(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val initializer: Statement,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val slotIndex: Int?,
|
||||||
|
val scopeId: Int?,
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(context: Scope): Obj {
|
||||||
|
return bytecodeOnly(context, "delegated var declaration")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.ListLiteralRef
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
class DestructuringVarDeclStatement(
|
||||||
|
val pattern: ListLiteralRef,
|
||||||
|
val names: List<String>,
|
||||||
|
val initializer: Statement,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(context: Scope): Obj {
|
||||||
|
return bytecodeOnly(context, "destructuring declaration")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
class EnumDeclStatement(
|
||||||
|
val declaredName: String,
|
||||||
|
val qualifiedName: String,
|
||||||
|
val entries: List<String>,
|
||||||
|
val lifted: Boolean,
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return bytecodeOnly(scope, "enum declaration")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val target = scope.parent ?: scope
|
||||||
|
return execute(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
internal fun extensionCallableName(typeName: String, memberName: String): String {
|
||||||
|
return "__ext__${sanitizeExtensionTypeName(typeName)}__${memberName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun extensionPropertyGetterName(typeName: String, memberName: String): String {
|
||||||
|
return "__ext_get__${sanitizeExtensionTypeName(typeName)}__${memberName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun extensionPropertySetterName(typeName: String, memberName: String): String {
|
||||||
|
return "__ext_set__${sanitizeExtensionTypeName(typeName)}__${memberName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sanitizeExtensionTypeName(typeName: String): String {
|
||||||
|
return typeName.replace('.', '_')
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjProperty
|
||||||
|
|
||||||
|
class ExtensionPropertyDeclStatement(
|
||||||
|
val extTypeName: String,
|
||||||
|
val property: ObjProperty,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val setterVisibility: Visibility?,
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(context: Scope): Obj {
|
||||||
|
return bytecodeOnly(context, "extension property declaration")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.sergeych.lyng.bytecode.SlotType
|
||||||
|
import net.sergeych.lyng.obj.*
|
||||||
|
|
||||||
|
interface FrameAccess {
|
||||||
|
fun getSlotTypeCode(slot: Int): Byte
|
||||||
|
fun getObj(slot: Int): Obj
|
||||||
|
fun getInt(slot: Int): Long
|
||||||
|
fun getReal(slot: Int): Double
|
||||||
|
fun getBool(slot: Int): Boolean
|
||||||
|
fun setObj(slot: Int, value: Obj)
|
||||||
|
fun setInt(slot: Int, value: Long)
|
||||||
|
fun setReal(slot: Int, value: Double)
|
||||||
|
fun setBool(slot: Int, value: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
class FrameSlotRef(
|
||||||
|
private val frame: FrameAccess,
|
||||||
|
private val slot: Int,
|
||||||
|
) : net.sergeych.lyng.obj.Obj() {
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
val resolvedOther = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
return read().compareTo(scope, resolvedOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(): Obj {
|
||||||
|
val typeCode = frame.getSlotTypeCode(slot)
|
||||||
|
return when (typeCode) {
|
||||||
|
SlotType.INT.code -> ObjInt.of(frame.getInt(slot))
|
||||||
|
SlotType.REAL.code -> ObjReal.of(frame.getReal(slot))
|
||||||
|
SlotType.BOOL.code -> if (frame.getBool(slot)) ObjTrue else ObjFalse
|
||||||
|
SlotType.OBJ.code -> frame.getObj(slot)
|
||||||
|
else -> frame.getObj(slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun refersTo(frame: FrameAccess, slot: Int): Boolean {
|
||||||
|
return this.frame === frame && this.slot == slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(value: Obj) {
|
||||||
|
when (value) {
|
||||||
|
is ObjInt -> frame.setInt(slot, value.value)
|
||||||
|
is ObjReal -> frame.setReal(slot, value.value)
|
||||||
|
is ObjBool -> frame.setBool(slot, value.value)
|
||||||
|
else -> frame.setObj(slot, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecordSlotRef(
|
||||||
|
private val record: ObjRecord,
|
||||||
|
) : net.sergeych.lyng.obj.Obj() {
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
val resolvedOther = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
return read().compareTo(scope, resolvedOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(): Obj {
|
||||||
|
val direct = record.value
|
||||||
|
return if (direct is FrameSlotRef) direct.read() else direct
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(value: Obj) {
|
||||||
|
record.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjExternCallable
|
||||||
|
import net.sergeych.lyng.obj.ObjExtensionMethodCallable
|
||||||
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjString
|
||||||
|
import net.sergeych.lyng.obj.ObjUnset
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
|
||||||
|
class FunctionClosureBox(
|
||||||
|
var closure: Scope? = null,
|
||||||
|
var captureContext: Scope? = null,
|
||||||
|
var captureRecords: List<ObjRecord>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FunctionDeclSpec(
|
||||||
|
val name: String,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val isStatic: Boolean,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val isDelegated: Boolean,
|
||||||
|
val delegateExpression: Statement?,
|
||||||
|
val delegateInitStatement: Statement?,
|
||||||
|
val extTypeName: String?,
|
||||||
|
val extensionWrapperName: String?,
|
||||||
|
val memberMethodId: Int?,
|
||||||
|
val actualExtern: Boolean,
|
||||||
|
val parentIsClassBody: Boolean,
|
||||||
|
val externCallSignature: CallSignature?,
|
||||||
|
val annotation: (suspend (Scope, ObjString, Statement) -> Statement)?,
|
||||||
|
val fnBody: Statement,
|
||||||
|
val closureBox: FunctionClosureBox,
|
||||||
|
val captureSlots: List<CaptureSlot>,
|
||||||
|
val slotIndex: Int?,
|
||||||
|
val scopeId: Int?,
|
||||||
|
val startPos: Pos,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal suspend fun executeFunctionDecl(
|
||||||
|
scope: Scope,
|
||||||
|
spec: FunctionDeclSpec,
|
||||||
|
captureRecords: List<ObjRecord>? = null
|
||||||
|
): Obj {
|
||||||
|
spec.closureBox.captureRecords = captureRecords
|
||||||
|
if (spec.actualExtern && spec.extTypeName == null && !spec.parentIsClassBody) {
|
||||||
|
val existing = scope.get(spec.name)
|
||||||
|
if (existing != null) {
|
||||||
|
val value = (existing.value as? ObjExternCallable) ?: ObjExternCallable.wrap(existing.value)
|
||||||
|
scope.addItem(
|
||||||
|
spec.name,
|
||||||
|
false,
|
||||||
|
value,
|
||||||
|
spec.visibility,
|
||||||
|
callSignature = existing.callSignature
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.isDelegated) {
|
||||||
|
val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate")
|
||||||
|
val accessType = ObjString("Callable")
|
||||||
|
val initValue = executeBytecodeWithSeed(scope, delegateExpr, "delegated function")
|
||||||
|
val finalDelegate = try {
|
||||||
|
initValue.invokeInstanceMethod(scope, "bind", Arguments(ObjString(spec.name), accessType, scope.thisObj))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
initValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.extTypeName != null) {
|
||||||
|
val type = scope[spec.extTypeName]?.value ?: scope.raiseSymbolNotFound("class ${spec.extTypeName} not found")
|
||||||
|
if (type !is ObjClass) scope.raiseClassCastError("${spec.extTypeName} is not the class instance")
|
||||||
|
scope.addExtension(
|
||||||
|
type,
|
||||||
|
spec.name,
|
||||||
|
ObjRecord(ObjUnset, isMutable = false, visibility = spec.visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return finalDelegate
|
||||||
|
}
|
||||||
|
|
||||||
|
val th = scope.thisObj
|
||||||
|
if (spec.isStatic) {
|
||||||
|
(th as ObjClass).createClassField(
|
||||||
|
spec.name,
|
||||||
|
ObjUnset,
|
||||||
|
false,
|
||||||
|
spec.visibility,
|
||||||
|
null,
|
||||||
|
spec.startPos,
|
||||||
|
isTransient = spec.isTransient,
|
||||||
|
type = ObjRecord.Type.Delegated
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
scope.addItem(
|
||||||
|
spec.name,
|
||||||
|
false,
|
||||||
|
ObjUnset,
|
||||||
|
spec.visibility,
|
||||||
|
recordType = ObjRecord.Type.Delegated,
|
||||||
|
isTransient = spec.isTransient
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
} else if (th is ObjClass) {
|
||||||
|
val cls: ObjClass = th
|
||||||
|
cls.createField(
|
||||||
|
spec.name,
|
||||||
|
ObjUnset,
|
||||||
|
false,
|
||||||
|
spec.visibility,
|
||||||
|
null,
|
||||||
|
spec.startPos,
|
||||||
|
declaringClass = cls,
|
||||||
|
isAbstract = spec.isAbstract,
|
||||||
|
isClosed = spec.isClosed,
|
||||||
|
isOverride = spec.isOverride,
|
||||||
|
isTransient = spec.isTransient,
|
||||||
|
type = ObjRecord.Type.Delegated,
|
||||||
|
methodId = spec.memberMethodId
|
||||||
|
)
|
||||||
|
val initStmt = spec.delegateInitStatement
|
||||||
|
?: scope.raiseIllegalState("missing delegated init statement for ${spec.name}")
|
||||||
|
cls.instanceInitializers += requireBytecodeBody(scope, initStmt, "delegated function init")
|
||||||
|
} else {
|
||||||
|
scope.addItem(
|
||||||
|
spec.name,
|
||||||
|
false,
|
||||||
|
ObjUnset,
|
||||||
|
spec.visibility,
|
||||||
|
recordType = ObjRecord.Type.Delegated,
|
||||||
|
isTransient = spec.isTransient
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return finalDelegate
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.isStatic || !spec.parentIsClassBody) {
|
||||||
|
spec.closureBox.closure = scope
|
||||||
|
}
|
||||||
|
if (spec.parentIsClassBody) {
|
||||||
|
spec.closureBox.captureContext = scope
|
||||||
|
}
|
||||||
|
|
||||||
|
val annotatedFnBody = spec.annotation?.invoke(scope, ObjString(spec.name), spec.fnBody) ?: spec.fnBody
|
||||||
|
val compiledFnBody = annotatedFnBody
|
||||||
|
|
||||||
|
spec.extTypeName?.let { typeName ->
|
||||||
|
val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found")
|
||||||
|
if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance")
|
||||||
|
scope.addExtension(
|
||||||
|
type,
|
||||||
|
spec.name,
|
||||||
|
ObjRecord(
|
||||||
|
compiledFnBody,
|
||||||
|
isMutable = false,
|
||||||
|
visibility = spec.visibility,
|
||||||
|
declaringClass = null,
|
||||||
|
type = ObjRecord.Type.Fun
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val wrapperName = spec.extensionWrapperName ?: extensionCallableName(typeName, spec.name)
|
||||||
|
val wrapper = ObjExtensionMethodCallable(spec.name, compiledFnBody)
|
||||||
|
scope.addItem(wrapperName, false, wrapper, spec.visibility, recordType = ObjRecord.Type.Fun)
|
||||||
|
} ?: run {
|
||||||
|
val th = scope.thisObj
|
||||||
|
if (!spec.isStatic && th is ObjClass) {
|
||||||
|
val cls: ObjClass = th
|
||||||
|
cls.createField(
|
||||||
|
spec.name,
|
||||||
|
compiledFnBody,
|
||||||
|
isMutable = true,
|
||||||
|
visibility = spec.visibility,
|
||||||
|
pos = spec.startPos,
|
||||||
|
declaringClass = cls,
|
||||||
|
isAbstract = spec.isAbstract,
|
||||||
|
isClosed = spec.isClosed,
|
||||||
|
isOverride = spec.isOverride,
|
||||||
|
type = ObjRecord.Type.Fun,
|
||||||
|
methodId = spec.memberMethodId
|
||||||
|
)
|
||||||
|
val memberValue = cls.members[spec.name]?.value ?: compiledFnBody
|
||||||
|
scope.addItem(spec.name, false, memberValue, spec.visibility, callSignature = spec.externCallSignature)
|
||||||
|
compiledFnBody
|
||||||
|
} else {
|
||||||
|
scope.addItem(
|
||||||
|
spec.name,
|
||||||
|
false,
|
||||||
|
compiledFnBody,
|
||||||
|
spec.visibility,
|
||||||
|
recordType = ObjRecord.Type.Fun,
|
||||||
|
callSignature = spec.externCallSignature
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return annotatedFnBody
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun requireBytecodeBody(
|
||||||
|
scope: Scope,
|
||||||
|
stmt: Statement,
|
||||||
|
label: String
|
||||||
|
): net.sergeych.lyng.bytecode.BytecodeStatement {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
}
|
||||||
|
|
||||||
|
class FunctionDeclStatement(
|
||||||
|
val spec: FunctionDeclSpec,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = spec.startPos
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return executeFunctionDecl(scope, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val target = scope.parent ?: scope
|
||||||
|
return executeFunctionDecl(target, spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
sealed class ImportBindingSource {
|
||||||
|
data class Module(val name: String, val pos: Pos) : ImportBindingSource()
|
||||||
|
object Root : ImportBindingSource()
|
||||||
|
object Seed : ImportBindingSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ImportBinding(
|
||||||
|
val symbol: String,
|
||||||
|
val source: ImportBindingSource,
|
||||||
|
)
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
|
||||||
|
class InlineBlockStatement(
|
||||||
|
private val statements: List<Statement>,
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
var last: Obj = ObjVoid
|
||||||
|
for (stmt in statements) {
|
||||||
|
last = requireBytecodeBody(scope, stmt, "inline block").execute(scope)
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
fun statements(): List<Statement> = statements
|
||||||
|
|
||||||
|
private suspend fun requireBytecodeBody(scope: Scope, stmt: Statement, label: String): net.sergeych.lyng.bytecode.BytecodeStatement {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
|
import net.sergeych.lyng.obj.ObjProperty
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjString
|
||||||
|
import net.sergeych.lyng.obj.ObjUnset
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
|
||||||
|
class InstanceFieldInitStatement(
|
||||||
|
val storageName: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val isLateInitVal: Boolean,
|
||||||
|
val initializer: Statement?,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val initValue = initializer?.let { execBytecodeOnly(scope, it, "instance field init") }?.byValueCopy()
|
||||||
|
?: if (isLateInitVal) ObjUnset else ObjNull
|
||||||
|
scope.addItem(
|
||||||
|
storageName,
|
||||||
|
isMutable,
|
||||||
|
initValue,
|
||||||
|
visibility,
|
||||||
|
writeVisibility,
|
||||||
|
recordType = ObjRecord.Type.Field,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride,
|
||||||
|
isTransient = isTransient
|
||||||
|
)
|
||||||
|
return ObjVoid
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun execBytecodeOnly(scope: Scope, stmt: Statement, label: String): Obj {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
} ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
return bytecode.execute(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InstancePropertyInitStatement(
|
||||||
|
val storageName: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val prop: ObjProperty,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
scope.addItem(
|
||||||
|
storageName,
|
||||||
|
isMutable,
|
||||||
|
prop,
|
||||||
|
visibility,
|
||||||
|
writeVisibility,
|
||||||
|
recordType = ObjRecord.Type.Property,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride,
|
||||||
|
isTransient = isTransient
|
||||||
|
)
|
||||||
|
return ObjVoid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InstanceDelegatedInitStatement(
|
||||||
|
val storageName: String,
|
||||||
|
val memberName: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val accessTypeLabel: String,
|
||||||
|
val initializer: Statement,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val initValue = executeBytecodeWithSeed(scope, initializer, "instance delegated init")
|
||||||
|
val accessType = ObjString(accessTypeLabel)
|
||||||
|
val finalDelegate = try {
|
||||||
|
initValue.invokeInstanceMethod(
|
||||||
|
scope,
|
||||||
|
"bind",
|
||||||
|
Arguments(ObjString(memberName), accessType, scope.thisObj)
|
||||||
|
)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
initValue
|
||||||
|
}
|
||||||
|
scope.addItem(
|
||||||
|
storageName,
|
||||||
|
isMutable,
|
||||||
|
ObjUnset,
|
||||||
|
visibility,
|
||||||
|
writeVisibility,
|
||||||
|
recordType = ObjRecord.Type.Delegated,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride,
|
||||||
|
isTransient = isTransient
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
return ObjVoid
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeFrame
|
||||||
|
import net.sergeych.lyng.bytecode.CmdFunction
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
import net.sergeych.lyng.obj.ObjString
|
import net.sergeych.lyng.obj.ObjString
|
||||||
import net.sergeych.lyng.pacman.ImportProvider
|
import net.sergeych.lyng.pacman.ImportProvider
|
||||||
@ -33,6 +35,29 @@ class ModuleScope(
|
|||||||
|
|
||||||
constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName)
|
constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName)
|
||||||
|
|
||||||
|
internal var importedModules: List<ModuleScope> = emptyList()
|
||||||
|
internal var moduleFrame: BytecodeFrame? = null
|
||||||
|
internal var moduleFrameLocalCount: Int = -1
|
||||||
|
internal var moduleFrameLocalSlotNames: Array<String?> = emptyArray()
|
||||||
|
internal var moduleFrameLocalSlotMutables: BooleanArray = BooleanArray(0)
|
||||||
|
internal var moduleFrameLocalSlotDelegated: BooleanArray = BooleanArray(0)
|
||||||
|
|
||||||
|
internal fun ensureModuleFrame(fn: CmdFunction): BytecodeFrame {
|
||||||
|
val current = moduleFrame
|
||||||
|
val frame = if (current == null || moduleFrameLocalCount != fn.localCount) {
|
||||||
|
BytecodeFrame(fn.localCount, 0).also {
|
||||||
|
moduleFrame = it
|
||||||
|
moduleFrameLocalCount = fn.localCount
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current
|
||||||
|
}
|
||||||
|
moduleFrameLocalSlotNames = fn.localSlotNames
|
||||||
|
moduleFrameLocalSlotMutables = fn.localSlotMutables
|
||||||
|
moduleFrameLocalSlotDelegated = fn.localSlotDelegated
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import symbols into the scope. It _is called_ after the module is imported by [ImportProvider.prepareImport]
|
* Import symbols into the scope. It _is called_ after the module is imported by [ImportProvider.prepareImport]
|
||||||
* which checks symbol availability and accessibility prior to execution.
|
* which checks symbol availability and accessibility prior to execution.
|
||||||
@ -59,6 +84,7 @@ class ModuleScope(
|
|||||||
// when importing records, we keep track of its package (not otherwise needed)
|
// when importing records, we keep track of its package (not otherwise needed)
|
||||||
if (record.importedFrom == null) record.importedFrom = this
|
if (record.importedFrom == null) record.importedFrom = this
|
||||||
scope.objects[newName] = record
|
scope.objects[newName] = record
|
||||||
|
scope.updateSlotFor(newName, record)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,5 +117,3 @@ class ModuleScope(
|
|||||||
super.get(name)
|
super.get(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -358,6 +358,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
"in" -> Token("in", from, Token.Type.IN)
|
"in" -> Token("in", from, Token.Type.IN)
|
||||||
"is" -> Token("is", from, Token.Type.IS)
|
"is" -> Token("is", from, Token.Type.IS)
|
||||||
"by" -> Token("by", from, Token.Type.BY)
|
"by" -> Token("by", from, Token.Type.BY)
|
||||||
|
"step" -> Token("step", from, Token.Type.STEP)
|
||||||
"object" -> Token("object", from, Token.Type.OBJECT)
|
"object" -> Token("object", from, Token.Type.OBJECT)
|
||||||
"as" -> {
|
"as" -> {
|
||||||
// support both `as` and tight `as?` without spaces
|
// support both `as` and tight `as?` without spaces
|
||||||
|
|||||||
@ -72,4 +72,5 @@ object PerfFlags {
|
|||||||
|
|
||||||
// Specialized non-allocating integer range iteration in hot loops
|
// Specialized non-allocating integer range iteration in hot loops
|
||||||
var RANGE_FAST_ITER: Boolean = PerfDefaults.RANGE_FAST_ITER
|
var RANGE_FAST_ITER: Boolean = PerfDefaults.RANGE_FAST_ITER
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.bytecode.CmdFrame
|
||||||
|
import net.sergeych.lyng.bytecode.CmdVm
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
|
|
||||||
|
class PropertyAccessorStatement(
|
||||||
|
val body: Statement,
|
||||||
|
val argName: String?,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
if (argName != null) {
|
||||||
|
val prev = scope.skipScopeCreation
|
||||||
|
scope.skipScopeCreation = true
|
||||||
|
return try {
|
||||||
|
val bytecodeStmt = requireBytecodeBody(scope, body, "property accessor")
|
||||||
|
val fn = bytecodeStmt.bytecodeFunction()
|
||||||
|
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||||
|
val slotPlan = fn.localSlotPlanByName()
|
||||||
|
val slotIndex = slotPlan[argName]
|
||||||
|
?: scope.raiseIllegalState("property accessor argument $argName missing from slot plan")
|
||||||
|
val argValue = arguments.list.firstOrNull() ?: ObjNull
|
||||||
|
frame.frame.setObj(slotIndex, argValue)
|
||||||
|
}
|
||||||
|
scope.pos = pos
|
||||||
|
CmdVm().execute(fn, scope, scope.args, binder)
|
||||||
|
} finally {
|
||||||
|
scope.skipScopeCreation = prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requireBytecodeBody(scope, body, "property accessor").execute(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun requireBytecodeBody(scope: Scope, stmt: Statement, label: String): net.sergeych.lyng.bytecode.BytecodeStatement {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,7 +20,7 @@ package net.sergeych.lyng
|
|||||||
/**
|
/**
|
||||||
* Tiny, size-bounded cache for compiled Regex patterns. Activated only when [PerfFlags.REGEX_CACHE] is true.
|
* Tiny, size-bounded cache for compiled Regex patterns. Activated only when [PerfFlags.REGEX_CACHE] is true.
|
||||||
* This is a very simple FIFO-ish cache sufficient for micro-benchmarks and common repeated patterns.
|
* This is a very simple FIFO-ish cache sufficient for micro-benchmarks and common repeated patterns.
|
||||||
* Not thread-safe by design; the interpreter typically runs scripts on confined executors.
|
* Not thread-safe by design; the runtime typically runs scripts on confined executors.
|
||||||
*/
|
*/
|
||||||
object RegexCache {
|
object RegexCache {
|
||||||
private const val MAX = 64
|
private const val MAX = 64
|
||||||
|
|||||||
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
|
import net.sergeych.lyng.bytecode.CmdDisassembler
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
import net.sergeych.lyng.pacman.ImportProvider
|
import net.sergeych.lyng.pacman.ImportProvider
|
||||||
@ -37,7 +39,7 @@ fun nextFrameId(): Long = FrameIdGen.nextId()
|
|||||||
*
|
*
|
||||||
* There are special types of scopes:
|
* There are special types of scopes:
|
||||||
*
|
*
|
||||||
* - [ClosureScope] - scope used to apply a closure to some thisObj scope
|
* - [BytecodeClosureScope] - scope used to apply a closure to some thisObj scope
|
||||||
*/
|
*/
|
||||||
open class Scope(
|
open class Scope(
|
||||||
var parent: Scope?,
|
var parent: Scope?,
|
||||||
@ -50,11 +52,15 @@ open class Scope(
|
|||||||
var currentClassCtx: net.sergeych.lyng.obj.ObjClass? = parent?.currentClassCtx
|
var currentClassCtx: net.sergeych.lyng.obj.ObjClass? = parent?.currentClassCtx
|
||||||
// Unique id per scope frame for PICs; regenerated on each borrow from the pool.
|
// Unique id per scope frame for PICs; regenerated on each borrow from the pool.
|
||||||
var frameId: Long = nextFrameId()
|
var frameId: Long = nextFrameId()
|
||||||
|
@PublishedApi
|
||||||
|
internal val thisVariants: MutableList<Obj> = mutableListOf()
|
||||||
|
|
||||||
// Fast-path storage for local variables/arguments accessed by slot index.
|
// Fast-path storage for local variables/arguments accessed by slot index.
|
||||||
// Enabled by default for child scopes; module/class scopes can ignore it.
|
// Enabled by default for child scopes; module/class scopes can ignore it.
|
||||||
private val slots: MutableList<ObjRecord> = mutableListOf()
|
private val slots: MutableList<ObjRecord> = mutableListOf()
|
||||||
private val nameToSlot: MutableMap<String, Int> = mutableMapOf()
|
private val nameToSlot: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
internal var captureRecords: List<ObjRecord>? = null
|
||||||
|
internal var captureNames: List<String>? = null
|
||||||
/**
|
/**
|
||||||
* Auxiliary per-frame map of local bindings (locals declared in this frame).
|
* Auxiliary per-frame map of local bindings (locals declared in this frame).
|
||||||
* This helps resolving locals across suspension when slot ownership isn't
|
* This helps resolving locals across suspension when slot ownership isn't
|
||||||
@ -64,6 +70,31 @@ open class Scope(
|
|||||||
|
|
||||||
internal val extensions: MutableMap<ObjClass, MutableMap<String, ObjRecord>> = mutableMapOf()
|
internal val extensions: MutableMap<ObjClass, MutableMap<String, ObjRecord>> = mutableMapOf()
|
||||||
|
|
||||||
|
init {
|
||||||
|
setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun setThisVariants(primary: Obj, extras: List<Obj>) {
|
||||||
|
thisObj = primary
|
||||||
|
val extrasSnapshot = when {
|
||||||
|
extras.isEmpty() -> emptyList()
|
||||||
|
else -> {
|
||||||
|
try {
|
||||||
|
extras.toList()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thisVariants.clear()
|
||||||
|
thisVariants.add(primary)
|
||||||
|
for (obj in extrasSnapshot) {
|
||||||
|
if (obj !== primary && !thisVariants.contains(obj)) {
|
||||||
|
thisVariants.add(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun addExtension(cls: ObjClass, name: String, record: ObjRecord) {
|
fun addExtension(cls: ObjClass, name: String, record: ObjRecord) {
|
||||||
extensions.getOrPut(cls) { mutableMapOf() }[name] = record
|
extensions.getOrPut(cls) { mutableMapOf() }[name] = record
|
||||||
}
|
}
|
||||||
@ -77,7 +108,7 @@ open class Scope(
|
|||||||
for (cls in receiverClass.mro) {
|
for (cls in receiverClass.mro) {
|
||||||
s.extensions[cls]?.get(name)?.let { return it }
|
s.extensions[cls]?.get(name)?.let { return it }
|
||||||
}
|
}
|
||||||
if (s is ClosureScope) {
|
if (s is BytecodeClosureScope) {
|
||||||
s.closureScope.findExtension(receiverClass, name)?.let { return it }
|
s.closureScope.findExtension(receiverClass, name)?.let { return it }
|
||||||
}
|
}
|
||||||
s = s.parent
|
s = s.parent
|
||||||
@ -101,7 +132,7 @@ open class Scope(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal lookup helpers that deliberately avoid invoking overridden `get` implementations
|
* Internal lookup helpers that deliberately avoid invoking overridden `get` implementations
|
||||||
* (notably in ClosureScope) to prevent accidental ping-pong and infinite recursion across
|
* (notably in BytecodeClosureScope) to prevent accidental ping-pong and infinite recursion across
|
||||||
* intertwined closure frames. They traverse the plain parent chain and consult only locals
|
* intertwined closure frames. They traverse the plain parent chain and consult only locals
|
||||||
* and bindings of each frame. Instance/class member fallback must be decided by the caller.
|
* and bindings of each frame. Instance/class member fallback must be decided by the caller.
|
||||||
*/
|
*/
|
||||||
@ -124,6 +155,14 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
s.getSlotIndexOf(name)?.let { idx ->
|
s.getSlotIndexOf(name)?.let { idx ->
|
||||||
val rec = s.getSlotRecord(idx)
|
val rec = s.getSlotRecord(idx)
|
||||||
|
val hasDirectBinding =
|
||||||
|
s.objects.containsKey(name) ||
|
||||||
|
s.localBindings.containsKey(name) ||
|
||||||
|
(caller?.let { ctx ->
|
||||||
|
s.objects.containsKey(ctx.mangledName(name)) ||
|
||||||
|
s.localBindings.containsKey(ctx.mangledName(name))
|
||||||
|
} ?: false)
|
||||||
|
if (!hasDirectBinding && rec.value === ObjUnset) return null
|
||||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -136,7 +175,7 @@ open class Scope(
|
|||||||
val effectiveCaller = caller ?: currentClassCtx
|
val effectiveCaller = caller ?: currentClassCtx
|
||||||
while (s != null && hops++ < 1024) {
|
while (s != null && hops++ < 1024) {
|
||||||
tryGetLocalRecord(s, name, effectiveCaller)?.let { return it }
|
tryGetLocalRecord(s, name, effectiveCaller)?.let { return it }
|
||||||
s = if (followClosure && s is ClosureScope) s.closureScope else s.parent
|
s = if (followClosure && s is BytecodeClosureScope) s.closureScope else s.parent
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -145,7 +184,7 @@ open class Scope(
|
|||||||
* Perform base Scope.get semantics for this frame without delegating into parent.get
|
* Perform base Scope.get semantics for this frame without delegating into parent.get
|
||||||
* virtual dispatch. This checks:
|
* virtual dispatch. This checks:
|
||||||
* - locals/bindings in this frame
|
* - locals/bindings in this frame
|
||||||
* - walks raw parent chain for locals/bindings (ignoring ClosureScope-specific overrides)
|
* - walks raw parent chain for locals/bindings (ignoring BytecodeClosureScope-specific overrides)
|
||||||
* - finally falls back to this frame's `thisObj` instance/class members
|
* - finally falls back to this frame's `thisObj` instance/class members
|
||||||
*/
|
*/
|
||||||
internal fun baseGetIgnoreClosure(name: String): ObjRecord? {
|
internal fun baseGetIgnoreClosure(name: String): ObjRecord? {
|
||||||
@ -175,7 +214,7 @@ open class Scope(
|
|||||||
* - locals/bindings of each frame
|
* - locals/bindings of each frame
|
||||||
* - then instance/class members of each frame's `thisObj`.
|
* - then instance/class members of each frame's `thisObj`.
|
||||||
* This completely avoids invoking overridden `get` implementations, preventing
|
* This completely avoids invoking overridden `get` implementations, preventing
|
||||||
* ping-pong recursion between `ClosureScope` frames.
|
* ping-pong recursion between `BytecodeClosureScope` frames.
|
||||||
*/
|
*/
|
||||||
internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx, followClosure: Boolean = false): ObjRecord? {
|
internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx, followClosure: Boolean = false): ObjRecord? {
|
||||||
var s: Scope? = this
|
var s: Scope? = this
|
||||||
@ -192,7 +231,7 @@ open class Scope(
|
|||||||
} else return rec
|
} else return rec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s = if (followClosure && s is ClosureScope) s.closureScope else s.parent
|
s = if (followClosure && s is BytecodeClosureScope) s.closureScope else s.parent
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -326,19 +365,25 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Obj> thisAs(): T {
|
inline fun <reified T : Obj> thisAs(): T {
|
||||||
var s: Scope? = this
|
for (obj in thisVariants) {
|
||||||
while (s != null) {
|
if (obj is T) return obj
|
||||||
val t = s.thisObj
|
|
||||||
if (t is T) return t
|
|
||||||
s = s.parent
|
|
||||||
}
|
}
|
||||||
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||||
|
|
||||||
|
internal fun getLocalRecordDirect(name: String): ObjRecord? = objects[name]
|
||||||
|
|
||||||
open operator fun get(name: String): ObjRecord? {
|
open operator fun get(name: String): ObjRecord? {
|
||||||
if (name == "this") return thisObj.asReadonly
|
if (name == "this") return thisObj.asReadonly
|
||||||
|
if (name == "__PACKAGE__") {
|
||||||
|
var s: Scope? = this
|
||||||
|
while (s != null) {
|
||||||
|
if (s is ModuleScope) return s.packageNameObj
|
||||||
|
s = s.parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Prefer direct locals/bindings declared in this frame
|
// 1. Prefer direct locals/bindings declared in this frame
|
||||||
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
|
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
|
||||||
@ -377,10 +422,20 @@ open class Scope(
|
|||||||
// Slot fast-path API
|
// Slot fast-path API
|
||||||
fun getSlotRecord(index: Int): ObjRecord = slots[index]
|
fun getSlotRecord(index: Int): ObjRecord = slots[index]
|
||||||
fun setSlotValue(index: Int, newValue: Obj) {
|
fun setSlotValue(index: Int, newValue: Obj) {
|
||||||
slots[index].value = newValue
|
val record = slots[index]
|
||||||
|
val value = record.value
|
||||||
|
if (value is FrameSlotRef) {
|
||||||
|
value.write(newValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
record.value = newValue
|
||||||
}
|
}
|
||||||
|
val slotCount: Int
|
||||||
|
get() = slots.size
|
||||||
|
|
||||||
fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
|
fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
|
||||||
|
|
||||||
|
internal fun slotNameToIndexSnapshot(): Map<String, Int> = nameToSlot.toMap()
|
||||||
fun allocateSlotFor(name: String, record: ObjRecord): Int {
|
fun allocateSlotFor(name: String, record: ObjRecord): Int {
|
||||||
val idx = slots.size
|
val idx = slots.size
|
||||||
slots.add(record)
|
slots.add(record)
|
||||||
@ -390,6 +445,12 @@ open class Scope(
|
|||||||
|
|
||||||
fun updateSlotFor(name: String, record: ObjRecord) {
|
fun updateSlotFor(name: String, record: ObjRecord) {
|
||||||
nameToSlot[name]?.let { slots[it] = record }
|
nameToSlot[name]?.let { slots[it] = record }
|
||||||
|
if (objects[name] == null) {
|
||||||
|
objects[name] = record
|
||||||
|
}
|
||||||
|
if (localBindings[name] == null) {
|
||||||
|
localBindings[name] = record
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -410,6 +471,66 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun applySlotPlanReset(plan: Map<String, Int>, records: Map<String, ObjRecord>) {
|
||||||
|
if (plan.isEmpty()) return
|
||||||
|
slots.clear()
|
||||||
|
nameToSlot.clear()
|
||||||
|
val maxIndex = plan.values.maxOrNull() ?: return
|
||||||
|
val targetSize = maxIndex + 1
|
||||||
|
repeat(targetSize) {
|
||||||
|
slots.add(ObjRecord(ObjUnset, isMutable = true))
|
||||||
|
}
|
||||||
|
for ((name, idx) in plan) {
|
||||||
|
nameToSlot[name] = idx
|
||||||
|
val record = records[name]
|
||||||
|
if (record != null && record.value !== ObjUnset) {
|
||||||
|
slots[idx] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applySlotPlanWithSnapshot(plan: Map<String, Int>): Map<String, Int?> {
|
||||||
|
if (plan.isEmpty()) return emptyMap()
|
||||||
|
val maxIndex = plan.values.maxOrNull() ?: return emptyMap()
|
||||||
|
if (slots.size <= maxIndex) {
|
||||||
|
val targetSize = maxIndex + 1
|
||||||
|
while (slots.size < targetSize) {
|
||||||
|
slots.add(ObjRecord(ObjUnset, isMutable = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val snapshot = LinkedHashMap<String, Int?>(plan.size)
|
||||||
|
for ((name, idx) in plan) {
|
||||||
|
snapshot[name] = nameToSlot[name]
|
||||||
|
nameToSlot[name] = idx
|
||||||
|
}
|
||||||
|
return snapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restoreSlotPlan(snapshot: Map<String, Int?>) {
|
||||||
|
if (snapshot.isEmpty()) return
|
||||||
|
for ((name, idx) in snapshot) {
|
||||||
|
if (idx == null) {
|
||||||
|
nameToSlot.remove(name)
|
||||||
|
} else {
|
||||||
|
nameToSlot[name] = idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasSlotPlanConflict(plan: Map<String, Int>): Boolean {
|
||||||
|
if (plan.isEmpty() || nameToSlot.isEmpty()) return false
|
||||||
|
val planIndexToNames = HashMap<Int, HashSet<String>>(plan.size)
|
||||||
|
for ((name, idx) in plan) {
|
||||||
|
val names = planIndexToNames.getOrPut(idx) { HashSet(2) }
|
||||||
|
names.add(name)
|
||||||
|
}
|
||||||
|
for ((existingName, existingIndex) in nameToSlot) {
|
||||||
|
val plannedNames = planIndexToNames[existingIndex] ?: continue
|
||||||
|
if (!plannedNames.contains(existingName)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all references and maps to prevent memory leaks when pooled.
|
* Clear all references and maps to prevent memory leaks when pooled.
|
||||||
*/
|
*/
|
||||||
@ -417,6 +538,7 @@ open class Scope(
|
|||||||
this.parent = null
|
this.parent = null
|
||||||
this.skipScopeCreation = false
|
this.skipScopeCreation = false
|
||||||
this.currentClassCtx = null
|
this.currentClassCtx = null
|
||||||
|
thisVariants.clear()
|
||||||
objects.clear()
|
objects.clear()
|
||||||
slots.clear()
|
slots.clear()
|
||||||
nameToSlot.clear()
|
nameToSlot.clear()
|
||||||
@ -447,7 +569,7 @@ open class Scope(
|
|||||||
this.parent = parent
|
this.parent = parent
|
||||||
this.args = args
|
this.args = args
|
||||||
this.pos = pos
|
this.pos = pos
|
||||||
this.thisObj = thisObj
|
setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
|
||||||
// Pre-size local slots for upcoming parameter assignment where possible
|
// Pre-size local slots for upcoming parameter assignment where possible
|
||||||
reserveLocalCapacity(args.list.size + 4)
|
reserveLocalCapacity(args.list.size + 4)
|
||||||
}
|
}
|
||||||
@ -516,11 +638,6 @@ open class Scope(
|
|||||||
it.value = value
|
it.value = value
|
||||||
// keep local binding index consistent within the frame
|
// keep local binding index consistent within the frame
|
||||||
localBindings[name] = it
|
localBindings[name] = it
|
||||||
// If we are a ClosureScope, mirror binding into the caller frame to keep it discoverable
|
|
||||||
// across suspension when resumed on the call frame
|
|
||||||
if (this is ClosureScope) {
|
|
||||||
callScope.localBindings[name] = it
|
|
||||||
}
|
|
||||||
bumpClassLayoutIfNeeded(name, value, recordType)
|
bumpClassLayoutIfNeeded(name, value, recordType)
|
||||||
it
|
it
|
||||||
} ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride)
|
} ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride)
|
||||||
@ -536,7 +653,10 @@ open class Scope(
|
|||||||
isAbstract: Boolean = false,
|
isAbstract: Boolean = false,
|
||||||
isClosed: Boolean = false,
|
isClosed: Boolean = false,
|
||||||
isOverride: Boolean = false,
|
isOverride: Boolean = false,
|
||||||
isTransient: Boolean = false
|
isTransient: Boolean = false,
|
||||||
|
callSignature: CallSignature? = null,
|
||||||
|
fieldId: Int? = null,
|
||||||
|
methodId: Int? = null
|
||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
val rec = ObjRecord(
|
val rec = ObjRecord(
|
||||||
value, isMutable, visibility, writeVisibility,
|
value, isMutable, visibility, writeVisibility,
|
||||||
@ -545,15 +665,19 @@ open class Scope(
|
|||||||
isAbstract = isAbstract,
|
isAbstract = isAbstract,
|
||||||
isClosed = isClosed,
|
isClosed = isClosed,
|
||||||
isOverride = isOverride,
|
isOverride = isOverride,
|
||||||
isTransient = isTransient
|
isTransient = isTransient,
|
||||||
|
callSignature = callSignature,
|
||||||
|
memberName = name,
|
||||||
|
fieldId = fieldId,
|
||||||
|
methodId = methodId
|
||||||
)
|
)
|
||||||
objects[name] = rec
|
objects[name] = rec
|
||||||
bumpClassLayoutIfNeeded(name, value, recordType)
|
bumpClassLayoutIfNeeded(name, value, recordType)
|
||||||
if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) {
|
if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) {
|
||||||
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
||||||
if (inst != null) {
|
if (inst != null) {
|
||||||
val slot = inst.objClass.fieldSlotForKey(name)
|
val slotId = rec.fieldId ?: inst.objClass.fieldSlotForKey(name)?.slot
|
||||||
if (slot != null) inst.setFieldSlotRecord(slot.slot, rec)
|
if (slotId != null) inst.setFieldSlotRecord(slotId, rec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (value is Statement ||
|
if (value is Statement ||
|
||||||
@ -562,25 +686,12 @@ open class Scope(
|
|||||||
recordType == ObjRecord.Type.Property) {
|
recordType == ObjRecord.Type.Property) {
|
||||||
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
||||||
if (inst != null) {
|
if (inst != null) {
|
||||||
val slot = inst.objClass.methodSlotForKey(name)
|
val slotId = rec.methodId ?: inst.objClass.methodSlotForKey(name)?.slot
|
||||||
if (slot != null) inst.setMethodSlotRecord(slot.slot, rec)
|
if (slotId != null) inst.setMethodSlotRecord(slotId, rec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Index this binding within the current frame to help resolve locals across suspension
|
// Index this binding within the current frame to help resolve locals across suspension
|
||||||
localBindings[name] = rec
|
localBindings[name] = rec
|
||||||
// If we are a ClosureScope, mirror binding into the caller frame to keep it discoverable
|
|
||||||
// across suspension when resumed on the call frame
|
|
||||||
if (this is ClosureScope) {
|
|
||||||
callScope.localBindings[name] = rec
|
|
||||||
// Additionally, expose the binding in caller's objects and slot map so identifier
|
|
||||||
// resolution after suspension can still find it even if the active scope is a child
|
|
||||||
// of the callScope (e.g., due to internal withChildFrame usage).
|
|
||||||
// This keeps visibility within the method body but prevents leaking outside the caller frame.
|
|
||||||
callScope.objects[name] = rec
|
|
||||||
if (callScope.getSlotIndexOf(name) == null) {
|
|
||||||
callScope.allocateSlotFor(name, rec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Map to a slot for fast local access (ensure consistency)
|
// Map to a slot for fast local access (ensure consistency)
|
||||||
if (nameToSlot.isEmpty()) {
|
if (nameToSlot.isEmpty()) {
|
||||||
allocateSlotFor(name, rec)
|
allocateSlotFor(name, rec)
|
||||||
@ -608,25 +719,31 @@ open class Scope(
|
|||||||
return ns.objClass
|
return ns.objClass
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Scope.() -> Unit) {
|
inline fun addVoidFn(vararg names: String, crossinline fn: suspend ScopeFacade.() -> Unit) {
|
||||||
addFn(*names) {
|
addFn(*names) {
|
||||||
fn(this)
|
fn(this)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
|
fun disassembleSymbol(name: String): String {
|
||||||
val newFn = object : Statement() {
|
val record = get(name) ?: return "$name is not found"
|
||||||
override val pos: Pos = Pos.builtIn
|
val stmt = record.value as? Statement ?: return "$name is not a compiled body"
|
||||||
|
val bytecode = (stmt as? BytecodeStatement)?.bytecodeFunction()
|
||||||
|
?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
|
||||||
|
?: return "$name is not a compiled body"
|
||||||
|
return CmdDisassembler.disassemble(bytecode)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj = scope.fn()
|
fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend ScopeFacade.() -> Obj) {
|
||||||
|
val newFn = net.sergeych.lyng.obj.ObjExternCallable.fromBridge { fn() }
|
||||||
}
|
|
||||||
for (name in names) {
|
for (name in names) {
|
||||||
addItem(
|
addItem(
|
||||||
name,
|
name,
|
||||||
false,
|
false,
|
||||||
newFn
|
newFn,
|
||||||
|
recordType = ObjRecord.Type.Fun,
|
||||||
|
callSignature = callSignature
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -640,9 +757,10 @@ open class Scope(
|
|||||||
eval(code.toSource())
|
eval(code.toSource())
|
||||||
|
|
||||||
suspend fun eval(source: Source): Obj {
|
suspend fun eval(source: Source): Obj {
|
||||||
return Compiler.compile(
|
return Compiler.compileWithResolution(
|
||||||
source,
|
source,
|
||||||
currentImportProvider
|
currentImportProvider,
|
||||||
|
seedScope = this
|
||||||
).execute(this)
|
).execute(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,31 +813,53 @@ open class Scope(
|
|||||||
println("--------------------")
|
println("--------------------")
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure)
|
open fun applyClosure(closure: Scope, preferredThisType: String? = null): Scope =
|
||||||
|
BytecodeClosureScope(this, closure, preferredThisType)
|
||||||
|
|
||||||
|
internal fun applyClosureForBytecode(closure: Scope, preferredThisType: String? = null): Scope {
|
||||||
|
return BytecodeClosureScope(this, closure, preferredThisType)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve and evaluate a qualified identifier exactly as compiled code would.
|
* Resolve and evaluate a qualified identifier (e.g. `A.B.C`) using the same name/field
|
||||||
* For input like `A.B.C`, it builds the same ObjRef chain the compiler emits:
|
* resolution rules as compiled code, without invoking the compiler or ObjRef evaluation.
|
||||||
* `LocalVarRef("A", Pos.builtIn)` followed by `FieldRef` for each segment, then evaluates it.
|
|
||||||
* This mirrors `eval("A.B.C")` resolution semantics without invoking the compiler.
|
|
||||||
*/
|
*/
|
||||||
suspend fun resolveQualifiedIdentifier(qualifiedName: String): Obj {
|
suspend fun resolveQualifiedIdentifier(qualifiedName: String): Obj {
|
||||||
val trimmed = qualifiedName.trim()
|
val trimmed = qualifiedName.trim()
|
||||||
if (trimmed.isEmpty()) raiseSymbolNotFound("empty identifier")
|
if (trimmed.isEmpty()) raiseSymbolNotFound("empty identifier")
|
||||||
val parts = trimmed.split('.')
|
val parts = trimmed.split('.')
|
||||||
var ref: ObjRef = LocalVarRef(parts[0], Pos.builtIn)
|
val first = parts[0]
|
||||||
for (i in 1 until parts.size) {
|
val base: Obj = if (first == "this") {
|
||||||
ref = FieldRef(ref, parts[i], false)
|
thisObj
|
||||||
|
} else {
|
||||||
|
val rec = get(first) ?: raiseSymbolNotFound(first)
|
||||||
|
resolve(rec, first)
|
||||||
}
|
}
|
||||||
return ref.evalValue(this)
|
var current = base
|
||||||
|
for (i in 1 until parts.size) {
|
||||||
|
val name = parts[i]
|
||||||
|
val rec = current.readField(this, name)
|
||||||
|
current = resolve(rec, name)
|
||||||
|
}
|
||||||
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun resolve(rec: ObjRecord, name: String): Obj {
|
suspend fun resolve(rec: ObjRecord, name: String): Obj {
|
||||||
|
val value = rec.value
|
||||||
|
if (value is FrameSlotRef) {
|
||||||
|
return value.read()
|
||||||
|
}
|
||||||
val receiver = rec.receiver ?: thisObj
|
val receiver = rec.receiver ?: thisObj
|
||||||
return receiver.resolveRecord(this, rec, name, rec.declaringClass).value
|
return receiver.resolveRecord(this, rec, name, rec.declaringClass).value
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) {
|
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) {
|
||||||
|
val value = rec.value
|
||||||
|
if (value is FrameSlotRef) {
|
||||||
|
if (!rec.isMutable && value.read() !== ObjUnset) raiseIllegalAssignment("can't reassign val $name")
|
||||||
|
value.write(newValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (rec.type == ObjRecord.Type.Delegated) {
|
if (rec.type == ObjRecord.Type.Delegated) {
|
||||||
val receiver = rec.receiver ?: thisObj
|
val receiver = rec.receiver ?: thisObj
|
||||||
val del = rec.delegate ?: run {
|
val del = rec.delegate ?: run {
|
||||||
@ -752,4 +892,10 @@ open class Scope(
|
|||||||
fun new(): Scope =
|
fun new(): Scope =
|
||||||
Script.defaultImportManager.copy().newModuleAt(Pos.builtIn)
|
Script.defaultImportManager.copy().newModuleAt(Pos.builtIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun requireClass(name: String): net.sergeych.lyng.obj.ObjClass {
|
||||||
|
val rec = get(name) ?: raiseSymbolNotFound(name)
|
||||||
|
return rec.value as? net.sergeych.lyng.obj.ObjClass
|
||||||
|
?: raiseClassCastError("Expected class $name, got ${rec.value.objClass.className}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
110
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ScopeFacade.kt
Normal file
110
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ScopeFacade.kt
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjString
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limited facade for Kotlin bridge callables.
|
||||||
|
* Exposes only the minimal API needed to read/write vars and invoke methods.
|
||||||
|
*/
|
||||||
|
interface ScopeFacade {
|
||||||
|
val args: Arguments
|
||||||
|
var pos: Pos
|
||||||
|
var thisObj: Obj
|
||||||
|
operator fun get(name: String): ObjRecord?
|
||||||
|
suspend fun resolve(rec: ObjRecord, name: String): Obj
|
||||||
|
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj)
|
||||||
|
fun raiseError(message: String): Nothing
|
||||||
|
fun raiseError(obj: net.sergeych.lyng.obj.ObjException): Nothing
|
||||||
|
fun raiseClassCastError(message: String): Nothing
|
||||||
|
fun raiseIllegalArgument(message: String): Nothing
|
||||||
|
fun raiseNoSuchElement(message: String = "No such element"): Nothing
|
||||||
|
fun raiseSymbolNotFound(name: String): Nothing
|
||||||
|
fun raiseIllegalState(message: String = "Illegal argument error"): Nothing
|
||||||
|
fun raiseNotImplemented(what: String = "operation"): Nothing
|
||||||
|
suspend fun call(callee: Obj, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Obj
|
||||||
|
suspend fun toStringOf(obj: Obj, forInspect: Boolean = false): ObjString
|
||||||
|
suspend fun inspect(obj: Obj): String
|
||||||
|
fun trace(text: String = "")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ScopeBridge(internal val scope: Scope) : ScopeFacade {
|
||||||
|
override val args: Arguments
|
||||||
|
get() = scope.args
|
||||||
|
override var pos: Pos
|
||||||
|
get() = scope.pos
|
||||||
|
set(value) { scope.pos = value }
|
||||||
|
override var thisObj: Obj
|
||||||
|
get() = scope.thisObj
|
||||||
|
set(value) { scope.thisObj = value }
|
||||||
|
override fun get(name: String): ObjRecord? = scope[name]
|
||||||
|
override suspend fun resolve(rec: ObjRecord, name: String): Obj = scope.resolve(rec, name)
|
||||||
|
override suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) = scope.assign(rec, name, newValue)
|
||||||
|
override fun raiseError(message: String): Nothing = scope.raiseError(message)
|
||||||
|
override fun raiseError(obj: net.sergeych.lyng.obj.ObjException): Nothing = scope.raiseError(obj)
|
||||||
|
override fun raiseClassCastError(message: String): Nothing = scope.raiseClassCastError(message)
|
||||||
|
override fun raiseIllegalArgument(message: String): Nothing = scope.raiseIllegalArgument(message)
|
||||||
|
override fun raiseNoSuchElement(message: String): Nothing = scope.raiseNoSuchElement(message)
|
||||||
|
override fun raiseSymbolNotFound(name: String): Nothing = scope.raiseSymbolNotFound(name)
|
||||||
|
override fun raiseIllegalState(message: String): Nothing = scope.raiseIllegalState(message)
|
||||||
|
override fun raiseNotImplemented(what: String): Nothing = scope.raiseNotImplemented(what)
|
||||||
|
override suspend fun call(callee: Obj, args: Arguments, newThisObj: Obj?): Obj {
|
||||||
|
return callee.callOn(scope.createChildScope(scope.pos, args = args, newThisObj = newThisObj))
|
||||||
|
}
|
||||||
|
override suspend fun toStringOf(obj: Obj, forInspect: Boolean): ObjString = obj.toString(scope, forInspect)
|
||||||
|
override suspend fun inspect(obj: Obj): String = obj.inspect(scope)
|
||||||
|
override fun trace(text: String) = scope.trace(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Public factory for bridge facades. */
|
||||||
|
fun Scope.asFacade(): ScopeFacade = ScopeBridge(this)
|
||||||
|
|
||||||
|
inline fun <reified T : Obj> ScopeFacade.requiredArg(index: Int): T {
|
||||||
|
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
|
||||||
|
return (args.list[index].byValueCopy() as? T)
|
||||||
|
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}")
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Obj> ScopeFacade.requireOnlyArg(): T {
|
||||||
|
if (args.list.size != 1) raiseError("Expected exactly 1 argument, got ${args.list.size}")
|
||||||
|
return requiredArg(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ScopeFacade.requireExactCount(count: Int) {
|
||||||
|
if (args.list.size != count) {
|
||||||
|
raiseError("Expected exactly $count arguments, got ${args.list.size}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ScopeFacade.requireNoArgs() {
|
||||||
|
if (args.list.isNotEmpty()) {
|
||||||
|
raiseError("This function does not accept any arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Obj> ScopeFacade.thisAs(): T {
|
||||||
|
val obj = thisObj
|
||||||
|
return (obj as? T) ?: raiseClassCastError(
|
||||||
|
"Cannot cast ${obj.objClass.className} to ${T::class.simpleName}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ScopeFacade.requireScope(): Scope =
|
||||||
|
(this as? ScopeBridge)?.scope ?: raiseIllegalState("ScopeFacade requires ScopeBridge")
|
||||||
@ -20,10 +20,13 @@ package net.sergeych.lyng
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import net.sergeych.lyng.Script.Companion.defaultImportManager
|
import net.sergeych.lyng.Script.Companion.defaultImportManager
|
||||||
|
import net.sergeych.lyng.bytecode.CmdFunction
|
||||||
|
import net.sergeych.lyng.bytecode.CmdVm
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
import net.sergeych.lyng.stdlib_included.rootLyng
|
import net.sergeych.lyng.stdlib_included.rootLyng
|
||||||
|
import net.sergeych.lyng.bridge.LyngClassBridge
|
||||||
import net.sergeych.lynon.ObjLynonClass
|
import net.sergeych.lynon.ObjLynonClass
|
||||||
import net.sergeych.mp_tools.globalDefer
|
import net.sergeych.mp_tools.globalDefer
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
@ -32,17 +35,129 @@ import kotlin.math.*
|
|||||||
class Script(
|
class Script(
|
||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
private val statements: List<Statement> = emptyList(),
|
private val statements: List<Statement> = emptyList(),
|
||||||
|
private val moduleSlotPlan: Map<String, Int> = emptyMap(),
|
||||||
|
private val moduleDeclaredNames: Set<String> = emptySet(),
|
||||||
|
private val importBindings: Map<String, ImportBinding> = emptyMap(),
|
||||||
|
private val importedModules: List<ImportBindingSource.Module> = emptyList(),
|
||||||
|
private val moduleBytecode: CmdFunction? = null,
|
||||||
// private val catchReturn: Boolean = false,
|
// private val catchReturn: Boolean = false,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
|
fun statements(): List<Statement> = statements
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
var lastResult: Obj = ObjVoid
|
scope.pos = pos
|
||||||
for (s in statements) {
|
val execScope = resolveModuleScope(scope) ?: scope
|
||||||
lastResult = s.execute(scope)
|
val isModuleScope = execScope is ModuleScope
|
||||||
|
val shouldSeedModule = isModuleScope || execScope.thisObj === ObjVoid
|
||||||
|
val moduleTarget = execScope
|
||||||
|
if (shouldSeedModule) {
|
||||||
|
seedModuleSlots(moduleTarget, scope)
|
||||||
}
|
}
|
||||||
return lastResult
|
moduleBytecode?.let { fn ->
|
||||||
|
if (execScope is ModuleScope) {
|
||||||
|
execScope.ensureModuleFrame(fn)
|
||||||
|
}
|
||||||
|
return CmdVm().execute(fn, execScope, scope.args) { frame, _ ->
|
||||||
|
seedModuleLocals(frame, moduleTarget, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (statements.isNotEmpty()) {
|
||||||
|
scope.raiseIllegalState("bytecode-only execution is required; missing module bytecode")
|
||||||
|
}
|
||||||
|
return ObjVoid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun seedModuleSlots(scope: Scope, seedScope: Scope) {
|
||||||
|
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
||||||
|
seedImportBindings(scope, seedScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun seedModuleLocals(
|
||||||
|
frame: net.sergeych.lyng.bytecode.CmdFrame,
|
||||||
|
scope: Scope,
|
||||||
|
seedScope: Scope
|
||||||
|
) {
|
||||||
|
val localNames = frame.fn.localSlotNames
|
||||||
|
if (localNames.isEmpty()) return
|
||||||
|
val values = HashMap<String, Obj>()
|
||||||
|
val base = frame.fn.scopeSlotCount
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
if (moduleDeclaredNames.contains(name)) continue
|
||||||
|
val record = seedScope.getLocalRecordDirect(name) ?: findSeedRecord(seedScope, name) ?: continue
|
||||||
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
|
scope.resolve(record, name)
|
||||||
|
} else {
|
||||||
|
record.value
|
||||||
|
}
|
||||||
|
frame.setObjUnchecked(base + i, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun seedImportBindings(scope: Scope, seedScope: Scope) {
|
||||||
|
val provider = scope.currentImportProvider
|
||||||
|
val importedModules = LinkedHashSet<ModuleScope>()
|
||||||
|
for (moduleRef in this.importedModules) {
|
||||||
|
importedModules.add(provider.prepareImport(moduleRef.pos, moduleRef.name, null))
|
||||||
|
}
|
||||||
|
if (scope is ModuleScope) {
|
||||||
|
scope.importedModules = importedModules.toList()
|
||||||
|
}
|
||||||
|
for ((name, binding) in importBindings) {
|
||||||
|
val record = when (val source = binding.source) {
|
||||||
|
is ImportBindingSource.Module -> {
|
||||||
|
val module = provider.prepareImport(source.pos, source.name, null)
|
||||||
|
importedModules.add(module)
|
||||||
|
module.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
|
||||||
|
?: scope.raiseSymbolNotFound("symbol ${source.name}.${binding.symbol} not found")
|
||||||
|
}
|
||||||
|
ImportBindingSource.Root -> {
|
||||||
|
provider.rootScope.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
|
||||||
|
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
||||||
|
}
|
||||||
|
ImportBindingSource.Seed -> {
|
||||||
|
findSeedRecord(seedScope, binding.symbol)
|
||||||
|
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name == "Exception" && record.value !is ObjClass) {
|
||||||
|
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false))
|
||||||
|
} else {
|
||||||
|
scope.updateSlotFor(name, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (module in importedModules) {
|
||||||
|
for ((cls, map) in module.extensions) {
|
||||||
|
for ((symbol, record) in map) {
|
||||||
|
if (record.visibility.isPublic) {
|
||||||
|
scope.addExtension(cls, symbol, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSeedRecord(scope: Scope?, name: String): ObjRecord? {
|
||||||
|
var s = scope
|
||||||
|
var hops = 0
|
||||||
|
while (s != null && hops++ < 1024) {
|
||||||
|
s.objects[name]?.let { return it }
|
||||||
|
s.localBindings[name]?.let { return it }
|
||||||
|
s.getSlotIndexOf(name)?.let { idx ->
|
||||||
|
val rec = s.getSlotRecord(idx)
|
||||||
|
if (rec.value !== ObjUnset) return rec
|
||||||
|
}
|
||||||
|
s = s.parent
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveModuleScope(scope: Scope): ModuleScope? {
|
||||||
|
return scope as? ModuleScope
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun debugStatements(): List<Statement> = statements
|
||||||
|
|
||||||
suspend fun execute() = execute(
|
suspend fun execute() = execute(
|
||||||
defaultImportManager.newStdScope()
|
defaultImportManager.newStdScope()
|
||||||
)
|
)
|
||||||
@ -61,19 +176,29 @@ class Script(
|
|||||||
addConst("Unset", ObjUnset)
|
addConst("Unset", ObjUnset)
|
||||||
addFn("print") {
|
addFn("print") {
|
||||||
for ((i, a) in args.withIndex()) {
|
for ((i, a) in args.withIndex()) {
|
||||||
if (i > 0) print(' ' + a.toString(this).value)
|
if (i > 0) print(' ' + toStringOf(a).value)
|
||||||
else print(a.toString(this).value)
|
else print(toStringOf(a).value)
|
||||||
}
|
}
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
addFn("println") {
|
addFn("println") {
|
||||||
for ((i, a) in args.withIndex()) {
|
for ((i, a) in args.withIndex()) {
|
||||||
if (i > 0) print(' ' + a.toString(this).value)
|
if (i > 0) print(' ' + toStringOf(a).value)
|
||||||
else print(a.toString(this).value)
|
else print(toStringOf(a).value)
|
||||||
}
|
}
|
||||||
println()
|
println()
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
|
addFn("call") {
|
||||||
|
val callee = args.list.firstOrNull()
|
||||||
|
?: raiseError("call requires a callable as the first argument")
|
||||||
|
val rest = if (args.list.size > 1) {
|
||||||
|
Arguments(args.list.drop(1))
|
||||||
|
} else {
|
||||||
|
Arguments.EMPTY
|
||||||
|
}
|
||||||
|
call(callee, rest)
|
||||||
|
}
|
||||||
addFn("floor") {
|
addFn("floor") {
|
||||||
val x = args.firstAndOnly()
|
val x = args.firstAndOnly()
|
||||||
(if (x is ObjInt) x
|
(if (x is ObjInt) x
|
||||||
@ -170,12 +295,12 @@ class Script(
|
|||||||
|
|
||||||
var result = value
|
var result = value
|
||||||
if (range.start != null && !range.start.isNull) {
|
if (range.start != null && !range.start.isNull) {
|
||||||
if (result.compareTo(this, range.start) < 0) {
|
if (result.compareTo(requireScope(), range.start) < 0) {
|
||||||
result = range.start
|
result = range.start
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (range.end != null && !range.end.isNull) {
|
if (range.end != null && !range.end.isNull) {
|
||||||
val cmp = range.end.compareTo(this, result)
|
val cmp = range.end.compareTo(requireScope(), result)
|
||||||
if (range.isEndInclusive) {
|
if (range.isEndInclusive) {
|
||||||
if (cmp < 0) result = range.end
|
if (cmp < 0) result = range.end
|
||||||
} else {
|
} else {
|
||||||
@ -198,43 +323,53 @@ class Script(
|
|||||||
addVoidFn("assert") {
|
addVoidFn("assert") {
|
||||||
val cond = requiredArg<ObjBool>(0)
|
val cond = requiredArg<ObjBool>(0)
|
||||||
val message = if (args.size > 1)
|
val message = if (args.size > 1)
|
||||||
": " + (args[1] as Statement).execute(this).toString(this).value
|
": " + toStringOf(call(args[1] as Obj)).value
|
||||||
else ""
|
else ""
|
||||||
if (!cond.value == true)
|
if (!cond.value == true)
|
||||||
raiseError(ObjAssertionFailedException(this, "Assertion failed$message"))
|
raiseError(ObjAssertionFailedException(requireScope(), "Assertion failed$message"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unwrapCompareArg(value: Obj): Obj {
|
||||||
|
val resolved = when (value) {
|
||||||
|
is FrameSlotRef -> value.read()
|
||||||
|
is RecordSlotRef -> value.read()
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
|
return if (resolved === ObjUnset.objClass) ObjUnset else resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
addVoidFn("assertEquals") {
|
addVoidFn("assertEquals") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = unwrapCompareArg(requiredArg(0))
|
||||||
val b = requiredArg<Obj>(1)
|
val b = unwrapCompareArg(requiredArg(1))
|
||||||
if (a.compareTo(this, b) != 0)
|
if (a.compareTo(requireScope(), b) != 0) {
|
||||||
raiseError(
|
raiseError(
|
||||||
ObjAssertionFailedException(
|
ObjAssertionFailedException(
|
||||||
this,
|
requireScope(),
|
||||||
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
"Assertion failed: ${inspect(a)} == ${inspect(b)}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// alias used in tests
|
// alias used in tests
|
||||||
addVoidFn("assertEqual") {
|
addVoidFn("assertEqual") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = unwrapCompareArg(requiredArg(0))
|
||||||
val b = requiredArg<Obj>(1)
|
val b = unwrapCompareArg(requiredArg(1))
|
||||||
if (a.compareTo(this, b) != 0)
|
if (a.compareTo(requireScope(), b) != 0)
|
||||||
raiseError(
|
raiseError(
|
||||||
ObjAssertionFailedException(
|
ObjAssertionFailedException(
|
||||||
this,
|
requireScope(),
|
||||||
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
"Assertion failed: ${inspect(a)} == ${inspect(b)}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
addVoidFn("assertNotEquals") {
|
addVoidFn("assertNotEquals") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = unwrapCompareArg(requiredArg(0))
|
||||||
val b = requiredArg<Obj>(1)
|
val b = unwrapCompareArg(requiredArg(1))
|
||||||
if (a.compareTo(this, b) == 0)
|
if (a.compareTo(requireScope(), b) == 0)
|
||||||
raiseError(
|
raiseError(
|
||||||
ObjAssertionFailedException(
|
ObjAssertionFailedException(
|
||||||
this,
|
requireScope(),
|
||||||
"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"
|
"Assertion failed: ${inspect(a)} != ${inspect(b)}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -251,23 +386,23 @@ class Script(
|
|||||||
will be accepted.
|
will be accepted.
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
) {
|
) {
|
||||||
val code: Statement
|
val code: Obj
|
||||||
val expectedClass: ObjClass?
|
val expectedClass: ObjClass?
|
||||||
when (args.size) {
|
when (args.size) {
|
||||||
1 -> {
|
1 -> {
|
||||||
code = requiredArg<Statement>(0)
|
code = requiredArg<Obj>(0)
|
||||||
expectedClass = null
|
expectedClass = null
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
code = requiredArg<Statement>(1)
|
code = requiredArg<Obj>(1)
|
||||||
expectedClass = requiredArg<ObjClass>(0)
|
expectedClass = requiredArg<ObjClass>(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}")
|
else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}")
|
||||||
}
|
}
|
||||||
val result = try {
|
val result = try {
|
||||||
code.execute(this)
|
call(code)
|
||||||
null
|
null
|
||||||
} catch (e: ExecutionError) {
|
} catch (e: ExecutionError) {
|
||||||
e.errorObject
|
e.errorObject
|
||||||
@ -276,7 +411,7 @@ class Script(
|
|||||||
}
|
}
|
||||||
if (result == null) raiseError(
|
if (result == null) raiseError(
|
||||||
ObjAssertionFailedException(
|
ObjAssertionFailedException(
|
||||||
this,
|
requireScope(),
|
||||||
"Expected exception but nothing was thrown"
|
"Expected exception but nothing was thrown"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -289,8 +424,8 @@ class Script(
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
addFn("dynamic") {
|
addFn("dynamic", callSignature = CallSignature(tailBlockReceiverType = "DelegateContext")) {
|
||||||
ObjDynamic.create(this, requireOnlyArg())
|
ObjDynamic.create(requireScope(), requireOnlyArg())
|
||||||
}
|
}
|
||||||
|
|
||||||
val root = this
|
val root = this
|
||||||
@ -307,7 +442,7 @@ class Script(
|
|||||||
val condition = requiredArg<ObjBool>(0)
|
val condition = requiredArg<ObjBool>(0)
|
||||||
if (!condition.value) {
|
if (!condition.value) {
|
||||||
var message = args.list.getOrNull(1)
|
var message = args.list.getOrNull(1)
|
||||||
if (message is Statement) message = message.execute(this)
|
if (message is Obj && message.objClass == Statement.type) message = call(message)
|
||||||
raiseIllegalArgument(message?.toString() ?: "requirement not met")
|
raiseIllegalArgument(message?.toString() ?: "requirement not met")
|
||||||
}
|
}
|
||||||
ObjVoid
|
ObjVoid
|
||||||
@ -316,23 +451,42 @@ class Script(
|
|||||||
val condition = requiredArg<ObjBool>(0)
|
val condition = requiredArg<ObjBool>(0)
|
||||||
if (!condition.value) {
|
if (!condition.value) {
|
||||||
var message = args.list.getOrNull(1)
|
var message = args.list.getOrNull(1)
|
||||||
if (message is Statement) message = message.execute(this)
|
if (message is Obj && message.objClass == Statement.type) message = call(message)
|
||||||
raiseIllegalState(message?.toString() ?: "check failed")
|
raiseIllegalState(message?.toString() ?: "check failed")
|
||||||
}
|
}
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
addFn("traceScope") {
|
addFn("traceScope") {
|
||||||
this.trace(args.getOrNull(0)?.toString() ?: "")
|
trace(args.getOrNull(0)?.toString() ?: "")
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
|
addFn("run") {
|
||||||
|
call(requireOnlyArg())
|
||||||
|
}
|
||||||
|
addFn("cached") {
|
||||||
|
val builder = requireOnlyArg<Obj>()
|
||||||
|
val capturedScope = this
|
||||||
|
var calculated = false
|
||||||
|
var cachedValue: Obj = ObjVoid
|
||||||
|
net.sergeych.lyng.obj.ObjExternCallable.fromBridge {
|
||||||
|
if (!calculated) {
|
||||||
|
cachedValue = capturedScope.call(builder)
|
||||||
|
calculated = true
|
||||||
|
}
|
||||||
|
cachedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addFn("lazy") {
|
||||||
|
val builder = requireOnlyArg<Obj>()
|
||||||
|
ObjLazyDelegate(builder, requireScope())
|
||||||
|
}
|
||||||
addVoidFn("delay") {
|
addVoidFn("delay") {
|
||||||
val a = args.firstAndOnly()
|
val a = args.firstAndOnly()
|
||||||
when (a) {
|
when (a) {
|
||||||
is ObjInt -> delay(a.value)
|
is ObjInt -> delay(a.value)
|
||||||
is ObjReal -> delay((a.value * 1000).roundToLong())
|
is ObjReal -> delay((a.value * 1000).roundToLong())
|
||||||
is ObjDuration -> delay(a.duration)
|
is ObjDuration -> delay(a.duration)
|
||||||
else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${a.inspect(this)}")
|
else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${inspect(a)}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,6 +506,7 @@ class Script(
|
|||||||
// interfaces
|
// interfaces
|
||||||
addConst("Iterable", ObjIterable)
|
addConst("Iterable", ObjIterable)
|
||||||
addConst("Collection", ObjCollection)
|
addConst("Collection", ObjCollection)
|
||||||
|
addConst("Iterator", ObjIterator)
|
||||||
addConst("Array", ObjArray)
|
addConst("Array", ObjArray)
|
||||||
addConst("RingBuffer", ObjRingBuffer.type)
|
addConst("RingBuffer", ObjRingBuffer.type)
|
||||||
addConst("Class", ObjClassType)
|
addConst("Class", ObjClassType)
|
||||||
@ -360,13 +515,19 @@ class Script(
|
|||||||
addConst("CompletableDeferred", ObjCompletableDeferred.type)
|
addConst("CompletableDeferred", ObjCompletableDeferred.type)
|
||||||
addConst("Mutex", ObjMutex.type)
|
addConst("Mutex", ObjMutex.type)
|
||||||
addConst("Flow", ObjFlow.type)
|
addConst("Flow", ObjFlow.type)
|
||||||
|
addConst("FlowBuilder", ObjFlowBuilder.type)
|
||||||
|
addConst("Delegate", ObjDynamic.type)
|
||||||
|
addConst("DelegateContext", ObjDynamicContext.type)
|
||||||
|
|
||||||
addConst("Regex", ObjRegex.type)
|
addConst("Regex", ObjRegex.type)
|
||||||
|
addConst("RegexMatch", ObjRegexMatch.type)
|
||||||
|
addConst("MapEntry", ObjMapEntry.type)
|
||||||
|
|
||||||
addFn("launch") {
|
addFn("launch") {
|
||||||
val callable = requireOnlyArg<Statement>()
|
val callable = requireOnlyArg<Obj>()
|
||||||
|
val captured = this
|
||||||
ObjDeferred(globalDefer {
|
ObjDeferred(globalDefer {
|
||||||
callable.execute(this@addFn)
|
captured.call(callable)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,10 +536,10 @@ class Script(
|
|||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
|
|
||||||
addFn("flow") {
|
addFn("flow", callSignature = CallSignature(tailBlockReceiverType = "FlowBuilder")) {
|
||||||
// important is: current context contains closure often used in call;
|
// important is: current context contains closure often used in call;
|
||||||
// we'll need it for the producer
|
// we'll need it for the producer
|
||||||
ObjFlow(requireOnlyArg<Statement>(), this)
|
ObjFlow(requireOnlyArg<Obj>(), requireScope())
|
||||||
}
|
}
|
||||||
|
|
||||||
val pi = ObjReal(PI)
|
val pi = ObjReal(PI)
|
||||||
@ -400,9 +561,10 @@ class Script(
|
|||||||
|
|
||||||
val defaultImportManager: ImportManager by lazy {
|
val defaultImportManager: ImportManager by lazy {
|
||||||
ImportManager(rootScope, SecurityManager.allowAll).apply {
|
ImportManager(rootScope, SecurityManager.allowAll).apply {
|
||||||
addTextPackages(
|
addPackage("lyng.stdlib") { module ->
|
||||||
rootLyng
|
module.eval(Source("lyng.stdlib", rootLyng))
|
||||||
)
|
ObjKotlinIterator.bindTo(module.requireClass("KotlinIterator"))
|
||||||
|
}
|
||||||
addPackage("lyng.buffer") {
|
addPackage("lyng.buffer") {
|
||||||
it.addConstDoc(
|
it.addConstDoc(
|
||||||
name = "Buffer",
|
name = "Buffer",
|
||||||
@ -453,7 +615,7 @@ class Script(
|
|||||||
is ObjInt -> delay(a.value * 1000)
|
is ObjInt -> delay(a.value * 1000)
|
||||||
is ObjReal -> delay((a.value * 1000).roundToLong())
|
is ObjReal -> delay((a.value * 1000).roundToLong())
|
||||||
is ObjDuration -> delay(a.duration)
|
is ObjDuration -> delay(a.duration)
|
||||||
else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(this)}")
|
else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${inspect(a)}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,7 +36,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
PLUS, MINUS, STAR, SLASH, PERCENT,
|
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||||
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN, IFNULLASSIGN,
|
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN, IFNULLASSIGN,
|
||||||
PLUS2, MINUS2,
|
PLUS2, MINUS2,
|
||||||
IN, NOTIN, IS, NOTIS, BY,
|
IN, NOTIN, IS, NOTIS, BY, STEP,
|
||||||
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ, MATCH, NOTMATCH,
|
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ, MATCH, NOTMATCH,
|
||||||
SHUTTLE,
|
SHUTTLE,
|
||||||
AND, BITAND, OR, BITOR, BITXOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
|
AND, BITAND, OR, BITOR, BITXOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjException
|
||||||
|
|
||||||
|
class TryStatement(
|
||||||
|
val body: Statement,
|
||||||
|
val catches: List<CatchBlock>,
|
||||||
|
val finallyClause: Statement?,
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
data class CatchBlock(
|
||||||
|
val catchVarName: String,
|
||||||
|
val catchVarPos: Pos,
|
||||||
|
val classNames: List<String>,
|
||||||
|
val block: Statement
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return bytecodeOnly(scope, "try statement")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveExceptionClass(scope: Scope, name: String): ObjClass {
|
||||||
|
val rec = scope[name]
|
||||||
|
val cls = rec?.value as? ObjClass
|
||||||
|
if (cls != null) return cls
|
||||||
|
if (name == "Exception") return ObjException.Root
|
||||||
|
scope.raiseSymbolNotFound("error class does not exist or is not a class: $name")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,8 +22,23 @@ package net.sergeych.lyng
|
|||||||
// this is highly experimental and subject to complete redesign
|
// this is highly experimental and subject to complete redesign
|
||||||
// very soon
|
// very soon
|
||||||
sealed class TypeDecl(val isNullable:Boolean = false) {
|
sealed class TypeDecl(val isNullable:Boolean = false) {
|
||||||
|
enum class Variance { In, Out, Invariant }
|
||||||
// ??
|
// ??
|
||||||
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
|
data class Function(
|
||||||
|
val receiver: TypeDecl?,
|
||||||
|
val params: List<TypeDecl>,
|
||||||
|
val returnType: TypeDecl,
|
||||||
|
val nullable: Boolean = false
|
||||||
|
) : TypeDecl(nullable)
|
||||||
|
data class TypeVar(val name: String, val nullable: Boolean = false) : TypeDecl(nullable)
|
||||||
|
data class Union(val options: List<TypeDecl>, val nullable: Boolean = false) : TypeDecl(nullable)
|
||||||
|
data class Intersection(val options: List<TypeDecl>, val nullable: Boolean = false) : TypeDecl(nullable)
|
||||||
|
data class TypeParam(
|
||||||
|
val name: String,
|
||||||
|
val variance: Variance = Variance.Invariant,
|
||||||
|
val bound: TypeDecl? = null,
|
||||||
|
val defaultType: TypeDecl? = null
|
||||||
|
)
|
||||||
object TypeAny : TypeDecl()
|
object TypeAny : TypeDecl()
|
||||||
object TypeNullableAny : TypeDecl(true)
|
object TypeNullableAny : TypeDecl(true)
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
|
||||||
|
class VarDeclStatement(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val initializer: Statement?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val slotIndex: Int?,
|
||||||
|
val scopeId: Int?,
|
||||||
|
private val startPos: Pos,
|
||||||
|
val initializerObjClass: ObjClass? = null,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(context: Scope): Obj {
|
||||||
|
return bytecodeOnly(context, "var declaration")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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 net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
sealed class WhenCondition(open val expr: Statement, open val pos: Pos) {
|
||||||
|
protected fun bytecodeOnly(scope: Scope): Nothing {
|
||||||
|
return scope.raiseIllegalState("bytecode-only execution is required; when condition needs compiled bytecode")
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract suspend fun matches(scope: Scope, value: Obj): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
class WhenEqualsCondition(
|
||||||
|
override val expr: Statement,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : WhenCondition(expr, pos) {
|
||||||
|
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
||||||
|
return bytecodeOnly(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WhenInCondition(
|
||||||
|
override val expr: Statement,
|
||||||
|
val negated: Boolean,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : WhenCondition(expr, pos) {
|
||||||
|
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
||||||
|
return bytecodeOnly(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WhenIsCondition(
|
||||||
|
override val expr: Statement,
|
||||||
|
val negated: Boolean,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : WhenCondition(expr, pos) {
|
||||||
|
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
||||||
|
return bytecodeOnly(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class WhenCase(val conditions: List<WhenCondition>, val block: Statement)
|
||||||
|
|
||||||
|
class WhenStatement(
|
||||||
|
val value: Statement,
|
||||||
|
val cases: List<WhenCase>,
|
||||||
|
val elseCase: Statement?,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return bytecodeOnly(scope, "when statement")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,7 +27,7 @@ import net.sergeych.lyng.highlight.SimpleLyngHighlighter
|
|||||||
import net.sergeych.lyng.highlight.offsetOf
|
import net.sergeych.lyng.highlight.offsetOf
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
|
|
||||||
enum class SymbolKind { Class, Enum, Function, Value, Variable, Parameter }
|
enum class SymbolKind { Class, Enum, TypeAlias, Function, Value, Variable, Parameter }
|
||||||
|
|
||||||
data class Symbol(
|
data class Symbol(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
@ -126,13 +126,22 @@ object Binder {
|
|||||||
}
|
}
|
||||||
// Members (including fields and methods)
|
// Members (including fields and methods)
|
||||||
for (m in d.members) {
|
for (m in d.members) {
|
||||||
if (m is MiniMemberValDecl) {
|
when (m) {
|
||||||
val fs = source.offsetOf(m.nameStart)
|
is MiniMemberValDecl -> {
|
||||||
val fe = fs + m.name.length
|
val fs = source.offsetOf(m.nameStart)
|
||||||
val kind = if (m.mutable) SymbolKind.Variable else SymbolKind.Value
|
val fe = fs + m.name.length
|
||||||
val fieldSym = Symbol(nextId++, m.name, kind, fs, fe, containerId = sym.id, type = DocLookupUtils.typeOf(m.type))
|
val kind = if (m.mutable) SymbolKind.Variable else SymbolKind.Value
|
||||||
symbols += fieldSym
|
val fieldSym = Symbol(nextId++, m.name, kind, fs, fe, containerId = sym.id, type = DocLookupUtils.typeOf(m.type))
|
||||||
classScope.fields += fieldSym.id
|
symbols += fieldSym
|
||||||
|
classScope.fields += fieldSym.id
|
||||||
|
}
|
||||||
|
is MiniMemberTypeAliasDecl -> {
|
||||||
|
val fs = source.offsetOf(m.nameStart)
|
||||||
|
val fe = fs + m.name.length
|
||||||
|
val aliasSym = Symbol(nextId++, m.name, SymbolKind.TypeAlias, fs, fe, containerId = sym.id, type = DocLookupUtils.typeOf(m.target))
|
||||||
|
symbols += aliasSym
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,6 +206,12 @@ object Binder {
|
|||||||
symbols += sym
|
symbols += sym
|
||||||
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
||||||
}
|
}
|
||||||
|
is MiniTypeAliasDecl -> {
|
||||||
|
val (s, e) = nameOffsets(d.nameStart, d.name)
|
||||||
|
val sym = Symbol(nextId++, d.name, SymbolKind.TypeAlias, s, e, containerId = null, type = DocLookupUtils.typeOf(d.target))
|
||||||
|
symbols += sym
|
||||||
|
topLevelByName.getOrPut(d.name) { mutableListOf() }.add(sym.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,614 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kotlin bridge reflection facade: handle-based access for fast get/set/call.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng.bridge
|
||||||
|
|
||||||
|
import net.sergeych.lyng.*
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjIllegalAccessException
|
||||||
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
|
import net.sergeych.lyng.obj.ObjProperty
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjString
|
||||||
|
import net.sergeych.lyng.obj.ObjUnset
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
import net.sergeych.lyng.requireScope
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where a bridge resolver should search for names.
|
||||||
|
*
|
||||||
|
* Used by [LookupSpec] to control reflection scope for Kotlin-side tooling and bindings.
|
||||||
|
*/
|
||||||
|
enum class LookupTarget {
|
||||||
|
/** Resolve from the current frame only (locals/params declared in the active scope). */
|
||||||
|
CurrentFrame,
|
||||||
|
/** Resolve by walking the raw parent chain of frames (locals only, no member fallback). */
|
||||||
|
ParentChain,
|
||||||
|
/** Resolve against the module frame (top-level declarations in the module). */
|
||||||
|
ModuleFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicit receiver view, similar to `this@Base` in Lyng.
|
||||||
|
*
|
||||||
|
* When provided, the resolver will treat `this` as the requested type
|
||||||
|
* for member resolution and visibility checks.
|
||||||
|
*/
|
||||||
|
data class ReceiverView(
|
||||||
|
val type: ObjClass? = null,
|
||||||
|
val typeName: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup rules for bridge resolution.
|
||||||
|
*
|
||||||
|
* @property targets where to resolve names from
|
||||||
|
* @property receiverView optional explicit receiver for member lookup (like `this@Base`)
|
||||||
|
*/
|
||||||
|
data class LookupSpec(
|
||||||
|
val targets: Set<LookupTarget> = setOf(LookupTarget.CurrentFrame, LookupTarget.ModuleFrame),
|
||||||
|
val receiverView: ReceiverView? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base handle type returned by the Kotlin reflection bridge.
|
||||||
|
*
|
||||||
|
* Handles are inexpensive to keep and cache; they resolve lazily and
|
||||||
|
* may internally cache slots/records once a frame is known.
|
||||||
|
*/
|
||||||
|
sealed interface BridgeHandle {
|
||||||
|
/** Name of the underlying symbol (as written in Lyng). */
|
||||||
|
val name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read-only value handle resolved in a [ScopeFacade]. */
|
||||||
|
interface ValHandle : BridgeHandle {
|
||||||
|
/** Read the current value. */
|
||||||
|
suspend fun get(scope: ScopeFacade): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read/write value handle resolved in a [ScopeFacade]. */
|
||||||
|
interface VarHandle : ValHandle {
|
||||||
|
/** Assign a new value. */
|
||||||
|
suspend fun set(scope: ScopeFacade, value: Obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Callable handle (function/closure/method). */
|
||||||
|
interface CallableHandle : BridgeHandle {
|
||||||
|
/**
|
||||||
|
* Call the target with optional [args].
|
||||||
|
*
|
||||||
|
* @param newThisObj overrides receiver for member calls (defaults to current `this`/record receiver).
|
||||||
|
*/
|
||||||
|
suspend fun call(scope: ScopeFacade, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Member handle resolved against an instance or receiver view. */
|
||||||
|
interface MemberHandle : BridgeHandle {
|
||||||
|
/** Declaring class resolved for the last call/get/set (if known). */
|
||||||
|
val declaringClass: ObjClass?
|
||||||
|
/** Explicit receiver view used for resolution (if any). */
|
||||||
|
val receiverView: ReceiverView?
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Member field/property (read-only). */
|
||||||
|
interface MemberValHandle : MemberHandle, ValHandle
|
||||||
|
|
||||||
|
/** Member var/property with write access. */
|
||||||
|
interface MemberVarHandle : MemberHandle, VarHandle
|
||||||
|
|
||||||
|
/** Member callable (method or extension). */
|
||||||
|
interface MemberCallableHandle : MemberHandle, CallableHandle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Direct record handle (debug/inspection).
|
||||||
|
*
|
||||||
|
* Exposes raw [ObjRecord] access and should be used only in tooling.
|
||||||
|
*/
|
||||||
|
interface RecordHandle : BridgeHandle {
|
||||||
|
/** Resolve and return the raw [ObjRecord]. */
|
||||||
|
fun record(): ObjRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridge resolver API (entry point for Kotlin reflection and bindings).
|
||||||
|
*
|
||||||
|
* Obtain via [ScopeFacade.resolver] and reuse for multiple lookups.
|
||||||
|
* Resolver methods return handles that can be cached and reused across calls.
|
||||||
|
*/
|
||||||
|
interface BridgeResolver {
|
||||||
|
/** Source position used for error reporting. */
|
||||||
|
val pos: Pos
|
||||||
|
|
||||||
|
/** Treat `this` as [type] for member lookup (like `this@Type`). */
|
||||||
|
fun selfAs(type: ObjClass): BridgeResolver
|
||||||
|
/** Treat `this` as [typeName] for member lookup (like `this@Type`). */
|
||||||
|
fun selfAs(typeName: String): BridgeResolver
|
||||||
|
|
||||||
|
/** Resolve a read-only value by name using [lookup]. */
|
||||||
|
fun resolveVal(name: String, lookup: LookupSpec = LookupSpec()): ValHandle
|
||||||
|
/** Resolve a mutable value by name using [lookup]. */
|
||||||
|
fun resolveVar(name: String, lookup: LookupSpec = LookupSpec()): VarHandle
|
||||||
|
/** Resolve a callable by name using [lookup]. */
|
||||||
|
fun resolveCallable(name: String, lookup: LookupSpec = LookupSpec()): CallableHandle
|
||||||
|
|
||||||
|
/** Resolve a member value on [receiver]. */
|
||||||
|
fun resolveMemberVal(
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberValHandle
|
||||||
|
|
||||||
|
/** Resolve a mutable member on [receiver]. */
|
||||||
|
fun resolveMemberVar(
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberVarHandle
|
||||||
|
|
||||||
|
/** Resolve a member callable on [receiver]. */
|
||||||
|
fun resolveMemberCallable(
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberCallableHandle
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve an extension function treated as a member for reflection.
|
||||||
|
*
|
||||||
|
* This uses the extension wrapper name (same rules as Lyng compiler).
|
||||||
|
*/
|
||||||
|
fun resolveExtensionCallable(
|
||||||
|
receiverClass: ObjClass,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberCallableHandle
|
||||||
|
|
||||||
|
/** Debug: resolve locals by name (optional, for tooling). */
|
||||||
|
fun resolveLocalVal(name: String): ValHandle
|
||||||
|
/** Debug: resolve mutable locals by name (optional, for tooling). */
|
||||||
|
fun resolveLocalVar(name: String): VarHandle
|
||||||
|
|
||||||
|
/** Debug: access raw record handles if needed. */
|
||||||
|
fun resolveRecord(name: String, lookup: LookupSpec = LookupSpec()): RecordHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience: call by name with implicit caching in resolver implementation.
|
||||||
|
*
|
||||||
|
* Implemented by the default resolver; useful for lightweight call-by-name flows.
|
||||||
|
*/
|
||||||
|
interface BridgeCallByName {
|
||||||
|
/** Resolve and call [name] with [args] using [lookup]. */
|
||||||
|
suspend fun callByName(
|
||||||
|
scope: ScopeFacade,
|
||||||
|
name: String,
|
||||||
|
args: Arguments = Arguments.EMPTY,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional typed wrapper (sugar) around [ValHandle].
|
||||||
|
*
|
||||||
|
* Performs a runtime cast to [T] and raises a class cast error on mismatch.
|
||||||
|
*/
|
||||||
|
interface TypedHandle<T : Obj> : ValHandle {
|
||||||
|
/** Read value and cast it to [T]. */
|
||||||
|
suspend fun getTyped(scope: ScopeFacade): T
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for bridge resolver.
|
||||||
|
*
|
||||||
|
* Prefer this over ad-hoc lookups when writing Kotlin extensions or tooling.
|
||||||
|
*/
|
||||||
|
fun ScopeFacade.resolver(): BridgeResolver = BridgeResolverImpl(this)
|
||||||
|
|
||||||
|
private class BridgeResolverImpl(
|
||||||
|
private val facade: ScopeFacade,
|
||||||
|
private val receiverView: ReceiverView? = null
|
||||||
|
) : BridgeResolver, BridgeCallByName {
|
||||||
|
private val cachedCallables: MutableMap<String, CallableHandle> = LinkedHashMap()
|
||||||
|
|
||||||
|
override val pos: Pos
|
||||||
|
get() = facade.pos
|
||||||
|
|
||||||
|
override fun selfAs(type: ObjClass): BridgeResolver = BridgeResolverImpl(facade, ReceiverView(type = type))
|
||||||
|
|
||||||
|
override fun selfAs(typeName: String): BridgeResolver = BridgeResolverImpl(facade, ReceiverView(typeName = typeName))
|
||||||
|
|
||||||
|
override fun resolveVal(name: String, lookup: LookupSpec): ValHandle =
|
||||||
|
LocalValHandle(this, name, lookup)
|
||||||
|
|
||||||
|
override fun resolveVar(name: String, lookup: LookupSpec): VarHandle =
|
||||||
|
LocalVarHandle(this, name, lookup)
|
||||||
|
|
||||||
|
override fun resolveCallable(name: String, lookup: LookupSpec): CallableHandle =
|
||||||
|
LocalCallableHandle(this, name, lookup)
|
||||||
|
|
||||||
|
override fun resolveMemberVal(receiver: Obj, name: String, lookup: LookupSpec): MemberValHandle =
|
||||||
|
MemberValHandleImpl(this, receiver, name, lookup.receiverView ?: receiverView)
|
||||||
|
|
||||||
|
override fun resolveMemberVar(receiver: Obj, name: String, lookup: LookupSpec): MemberVarHandle =
|
||||||
|
MemberVarHandleImpl(this, receiver, name, lookup.receiverView ?: receiverView)
|
||||||
|
|
||||||
|
override fun resolveMemberCallable(receiver: Obj, name: String, lookup: LookupSpec): MemberCallableHandle =
|
||||||
|
MemberCallableHandleImpl(this, receiver, name, lookup.receiverView ?: receiverView)
|
||||||
|
|
||||||
|
override fun resolveExtensionCallable(receiverClass: ObjClass, name: String, lookup: LookupSpec): MemberCallableHandle =
|
||||||
|
ExtensionCallableHandleImpl(this, receiverClass, name, lookup)
|
||||||
|
|
||||||
|
override fun resolveLocalVal(name: String): ValHandle =
|
||||||
|
LocalValHandle(this, name, LookupSpec(targets = setOf(LookupTarget.CurrentFrame)))
|
||||||
|
|
||||||
|
override fun resolveLocalVar(name: String): VarHandle =
|
||||||
|
LocalVarHandle(this, name, LookupSpec(targets = setOf(LookupTarget.CurrentFrame)))
|
||||||
|
|
||||||
|
override fun resolveRecord(name: String, lookup: LookupSpec): RecordHandle =
|
||||||
|
RecordHandleImpl(this, name, lookup)
|
||||||
|
|
||||||
|
override suspend fun callByName(scope: ScopeFacade, name: String, args: Arguments, lookup: LookupSpec): Obj {
|
||||||
|
val handle = cachedCallables.getOrPut(name) { resolveCallable(name, lookup) }
|
||||||
|
return handle.call(scope, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun facade(): ScopeFacade = facade
|
||||||
|
|
||||||
|
fun resolveLocalRecord(scope: Scope, name: String, lookup: LookupSpec): ObjRecord {
|
||||||
|
val caller = scope.currentClassCtx
|
||||||
|
if (LookupTarget.CurrentFrame in lookup.targets) {
|
||||||
|
scope.tryGetLocalRecord(scope, name, caller)?.let { return it }
|
||||||
|
}
|
||||||
|
if (LookupTarget.ParentChain in lookup.targets) {
|
||||||
|
scope.chainLookupIgnoreClosure(name, followClosure = false, caller = caller)?.let { return it }
|
||||||
|
}
|
||||||
|
if (LookupTarget.ModuleFrame in lookup.targets) {
|
||||||
|
findModuleScope(scope)?.let { module ->
|
||||||
|
module.tryGetLocalRecord(module, name, caller)?.let { return it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
facade.raiseSymbolNotFound(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveReceiver(scope: Scope, receiver: Obj, view: ReceiverView?): Obj {
|
||||||
|
if (view == null) return receiver
|
||||||
|
if (receiver !== scope.thisObj) return receiver
|
||||||
|
val target = when {
|
||||||
|
view.type != null -> scope.thisVariants.firstOrNull { it.isInstanceOf(view.type) }
|
||||||
|
view.typeName != null -> scope.thisVariants.firstOrNull { it.isInstanceOf(view.typeName) }
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return target ?: facade.raiseSymbolNotFound(view.typeName ?: view.type?.className ?: "<receiver>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveMemberRecord(scope: Scope, receiver: Obj, name: String): MemberResolution {
|
||||||
|
if (receiver is ObjClass) {
|
||||||
|
val rec = receiver.classScope?.objects?.get(name) ?: receiver.members[name]
|
||||||
|
?: facade.raiseSymbolNotFound("member $name not found on ${receiver.className}")
|
||||||
|
val decl = rec.declaringClass ?: receiver
|
||||||
|
if (!canAccessMember(rec.visibility, decl, scope.currentClassCtx, name)) {
|
||||||
|
facade.raiseError(
|
||||||
|
ObjIllegalAccessException(
|
||||||
|
scope,
|
||||||
|
"can't access ${name}: not visible (declared in ${decl.className}, caller ${scope.currentClassCtx?.className ?: "?"})"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return MemberResolution(rec, decl, receiver, rec.fieldId, rec.methodId)
|
||||||
|
}
|
||||||
|
val cls = receiver.objClass
|
||||||
|
val resolved = cls.resolveInstanceMember(name)
|
||||||
|
?: facade.raiseSymbolNotFound("member $name not found on ${cls.className}")
|
||||||
|
val decl = resolved.declaringClass
|
||||||
|
if (!canAccessMember(resolved.record.visibility, decl, scope.currentClassCtx, name)) {
|
||||||
|
facade.raiseError(
|
||||||
|
ObjIllegalAccessException(
|
||||||
|
scope,
|
||||||
|
"can't access ${name}: not visible (declared in ${decl.className}, caller ${scope.currentClassCtx?.className ?: "?"})"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val fieldId = if (resolved.record.type == ObjRecord.Type.Field ||
|
||||||
|
resolved.record.type == ObjRecord.Type.ConstructorField
|
||||||
|
) {
|
||||||
|
resolved.record.fieldId ?: cls.instanceFieldIdMap()[name]
|
||||||
|
} else null
|
||||||
|
val methodId = if (resolved.record.type == ObjRecord.Type.Fun ||
|
||||||
|
resolved.record.type == ObjRecord.Type.Property ||
|
||||||
|
resolved.record.type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
resolved.record.methodId ?: cls.instanceMethodIdMap(includeAbstract = true)[name]
|
||||||
|
} else null
|
||||||
|
return MemberResolution(resolved.record, decl, receiver.objClass, fieldId, methodId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findModuleScope(scope: Scope): ModuleScope? {
|
||||||
|
var s: Scope? = scope
|
||||||
|
var hops = 0
|
||||||
|
while (s != null && hops++ < 1024) {
|
||||||
|
if (s is ModuleScope) return s
|
||||||
|
s = s.parent
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class LocalResolution(
|
||||||
|
val record: ObjRecord,
|
||||||
|
val frameId: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class MemberResolution(
|
||||||
|
val record: ObjRecord,
|
||||||
|
val declaringClass: ObjClass,
|
||||||
|
val receiverClass: ObjClass,
|
||||||
|
val fieldId: Int?,
|
||||||
|
val methodId: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
private abstract class LocalHandleBase(
|
||||||
|
protected val resolver: BridgeResolverImpl,
|
||||||
|
override val name: String,
|
||||||
|
private val lookup: LookupSpec
|
||||||
|
) : BridgeHandle {
|
||||||
|
private var cached: LocalResolution? = null
|
||||||
|
|
||||||
|
protected fun resolve(scope: Scope): ObjRecord {
|
||||||
|
val cachedLocal = cached
|
||||||
|
if (cachedLocal != null && cachedLocal.frameId == scope.frameId) {
|
||||||
|
return cachedLocal.record
|
||||||
|
}
|
||||||
|
val rec = resolver.resolveLocalRecord(scope, name, lookup)
|
||||||
|
cached = LocalResolution(rec, scope.frameId)
|
||||||
|
return rec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocalValHandle(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec
|
||||||
|
) : LocalHandleBase(resolver, name, lookup), ValHandle {
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val rec = resolve(real)
|
||||||
|
return real.resolve(rec, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocalVarHandle(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec
|
||||||
|
) : LocalHandleBase(resolver, name, lookup), VarHandle {
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val rec = resolve(real)
|
||||||
|
return real.resolve(rec, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(scope: ScopeFacade, value: Obj) {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val rec = resolve(real)
|
||||||
|
real.assign(rec, name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocalCallableHandle(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec
|
||||||
|
) : LocalHandleBase(resolver, name, lookup), CallableHandle {
|
||||||
|
override suspend fun call(scope: ScopeFacade, args: Arguments, newThisObj: Obj?): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val rec = resolve(real)
|
||||||
|
val callee = rec.value
|
||||||
|
return scope.call(callee, args, newThisObj ?: rec.receiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class MemberHandleBase(
|
||||||
|
protected val resolver: BridgeResolverImpl,
|
||||||
|
receiver: Obj,
|
||||||
|
override val name: String,
|
||||||
|
override val receiverView: ReceiverView?
|
||||||
|
) : MemberHandle {
|
||||||
|
private val baseReceiver: Obj = receiver
|
||||||
|
private var cachedResolution: MemberResolution? = null
|
||||||
|
private var cachedDeclaringClass: ObjClass? = null
|
||||||
|
|
||||||
|
protected fun resolve(scope: Scope): Pair<Obj, MemberResolution> {
|
||||||
|
val resolvedReceiver = resolver.resolveReceiver(scope, baseReceiver, receiverView)
|
||||||
|
val cached = cachedResolution
|
||||||
|
if (cached != null && resolvedReceiver.objClass === cached.receiverClass) {
|
||||||
|
cachedDeclaringClass = cached.declaringClass
|
||||||
|
return Pair(resolvedReceiver, cached)
|
||||||
|
}
|
||||||
|
val res = resolver.resolveMemberRecord(scope, resolvedReceiver, name)
|
||||||
|
cachedResolution = res
|
||||||
|
cachedDeclaringClass = res.declaringClass
|
||||||
|
return Pair(resolvedReceiver, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun declaringClass(): ObjClass? = cachedDeclaringClass
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemberValHandleImpl(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
receiverView: ReceiverView?
|
||||||
|
) : MemberHandleBase(resolver, receiver, name, receiverView), MemberValHandle {
|
||||||
|
override val declaringClass: ObjClass?
|
||||||
|
get() = declaringClass()
|
||||||
|
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val (receiver, res) = resolve(real)
|
||||||
|
val rec = resolveMemberRecordFast(receiver, res)
|
||||||
|
return receiver.resolveRecord(real, rec, name, res.declaringClass).value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemberVarHandleImpl(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
receiverView: ReceiverView?
|
||||||
|
) : MemberHandleBase(resolver, receiver, name, receiverView), MemberVarHandle {
|
||||||
|
override val declaringClass: ObjClass?
|
||||||
|
get() = declaringClass()
|
||||||
|
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val (receiver, res) = resolve(real)
|
||||||
|
val rec = resolveMemberRecordFast(receiver, res)
|
||||||
|
return receiver.resolveRecord(real, rec, name, res.declaringClass).value
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(scope: ScopeFacade, value: Obj) {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val (receiver, res) = resolve(real)
|
||||||
|
val rec = resolveMemberRecordFast(receiver, res)
|
||||||
|
assignMemberRecord(real, receiver, res.declaringClass, rec, name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemberCallableHandleImpl(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
receiverView: ReceiverView?
|
||||||
|
) : MemberHandleBase(resolver, receiver, name, receiverView), MemberCallableHandle {
|
||||||
|
override val declaringClass: ObjClass?
|
||||||
|
get() = declaringClass()
|
||||||
|
|
||||||
|
override suspend fun call(scope: ScopeFacade, args: Arguments, newThisObj: Obj?): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val (receiver, res) = resolve(real)
|
||||||
|
val rec = resolveMemberRecordFast(receiver, res)
|
||||||
|
if (rec.type != ObjRecord.Type.Fun) {
|
||||||
|
scope.raiseError("member $name is not callable")
|
||||||
|
}
|
||||||
|
return rec.value.invoke(real, receiver, args, res.declaringClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExtensionCallableHandleImpl(
|
||||||
|
private val resolver: BridgeResolverImpl,
|
||||||
|
private val receiverClass: ObjClass,
|
||||||
|
override val name: String,
|
||||||
|
private val lookup: LookupSpec
|
||||||
|
) : MemberCallableHandle {
|
||||||
|
override val receiverView: ReceiverView?
|
||||||
|
get() = null
|
||||||
|
override val declaringClass: ObjClass?
|
||||||
|
get() = receiverClass
|
||||||
|
|
||||||
|
override suspend fun call(scope: ScopeFacade, args: Arguments, newThisObj: Obj?): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val wrapperName = extensionCallableName(receiverClass.className, name)
|
||||||
|
val rec = resolver.resolveLocalRecord(real, wrapperName, lookup)
|
||||||
|
val receiver = newThisObj ?: real.thisObj
|
||||||
|
val callArgs = Arguments(listOf(receiver) + args.list)
|
||||||
|
return scope.call(rec.value, callArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RecordHandleImpl(
|
||||||
|
private val resolver: BridgeResolverImpl,
|
||||||
|
override val name: String,
|
||||||
|
private val lookup: LookupSpec
|
||||||
|
) : RecordHandle {
|
||||||
|
override fun record(): ObjRecord {
|
||||||
|
val scope = resolver.facade().requireScope()
|
||||||
|
return resolver.resolveLocalRecord(scope, name, lookup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TypedHandleImpl<T : Obj>(
|
||||||
|
private val inner: ValHandle,
|
||||||
|
private val clazzName: String
|
||||||
|
) : TypedHandle<T> {
|
||||||
|
override val name: String
|
||||||
|
get() = inner.name
|
||||||
|
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj = inner.get(scope)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override suspend fun getTyped(scope: ScopeFacade): T {
|
||||||
|
val value = inner.get(scope)
|
||||||
|
return (value as? T)
|
||||||
|
?: scope.raiseClassCastError("Expected $clazzName, got ${value.objClass.className}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveMemberRecordFast(receiver: Obj, res: MemberResolution): ObjRecord {
|
||||||
|
val inst = receiver as? ObjInstance
|
||||||
|
if (inst != null) {
|
||||||
|
res.fieldId?.let { inst.fieldRecordForId(it)?.let { return it } }
|
||||||
|
res.methodId?.let { inst.methodRecordForId(it)?.let { return it } }
|
||||||
|
}
|
||||||
|
return res.record
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun assignMemberRecord(
|
||||||
|
scope: Scope,
|
||||||
|
receiver: Obj,
|
||||||
|
declaringClass: ObjClass,
|
||||||
|
rec: ObjRecord,
|
||||||
|
name: String,
|
||||||
|
value: Obj
|
||||||
|
) {
|
||||||
|
val caller = scope.currentClassCtx
|
||||||
|
if (!canAccessMember(rec.effectiveWriteVisibility, declaringClass, caller, name)) {
|
||||||
|
scope.raiseError(
|
||||||
|
ObjIllegalAccessException(
|
||||||
|
scope,
|
||||||
|
"can't assign ${name}: not visible (declared in ${declaringClass.className}, caller ${caller?.className ?: "?"})"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
when {
|
||||||
|
rec.type == ObjRecord.Type.Delegated -> {
|
||||||
|
val del = rec.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||||
|
val th = if (receiver === ObjVoid) net.sergeych.lyng.obj.ObjNull else receiver
|
||||||
|
del.invokeInstanceMethod(scope, "setValue", Arguments(th, ObjString(name), value))
|
||||||
|
}
|
||||||
|
rec.value is ObjProperty || rec.type == ObjRecord.Type.Property -> {
|
||||||
|
val prop = rec.value as? ObjProperty
|
||||||
|
?: scope.raiseError("Expected ObjProperty for property member $name")
|
||||||
|
prop.callSetter(scope, receiver, value, declaringClass)
|
||||||
|
}
|
||||||
|
rec.isMutable -> {
|
||||||
|
val slotRef = rec.value
|
||||||
|
if (slotRef is net.sergeych.lyng.FrameSlotRef) {
|
||||||
|
if (!rec.isMutable && slotRef.read() !== ObjUnset) scope.raiseError("can't reassign val $name")
|
||||||
|
slotRef.write(value)
|
||||||
|
} else {
|
||||||
|
rec.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> scope.raiseError("can't assign to read-only field: $name")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,351 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kotlin bridge bindings for Lyng classes (Lyng-first workflow).
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng.bridge
|
||||||
|
|
||||||
|
import net.sergeych.lyng.*
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjExternCallable
|
||||||
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
|
import net.sergeych.lyng.obj.ObjProperty
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
|
import net.sergeych.lyng.requiredArg
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-instance bridge context passed to init hooks.
|
||||||
|
*
|
||||||
|
* Exposes the underlying [instance] and a mutable [data] slot for Kotlin-side state.
|
||||||
|
*/
|
||||||
|
interface BridgeInstanceContext {
|
||||||
|
/** The Lyng instance being initialized. */
|
||||||
|
val instance: Obj
|
||||||
|
/** Arbitrary Kotlin-side data attached to the instance. */
|
||||||
|
var data: Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binder DSL for attaching Kotlin implementations to a declared Lyng class.
|
||||||
|
*
|
||||||
|
* Use [LyngClassBridge.bind] to obtain a binder and register implementations.
|
||||||
|
* Bindings must happen before the first instance of the class is created.
|
||||||
|
*
|
||||||
|
* Important: members you bind here must be declared as `extern` in Lyng so the
|
||||||
|
* compiler emits the ABI slots that Kotlin bindings attach to.
|
||||||
|
*/
|
||||||
|
interface ClassBridgeBinder {
|
||||||
|
/** Arbitrary Kotlin-side data attached to the class. */
|
||||||
|
var classData: Any?
|
||||||
|
/** Register an initialization hook that runs for each instance. */
|
||||||
|
fun init(block: suspend BridgeInstanceContext.(ScopeFacade) -> Unit)
|
||||||
|
/** Register an initialization hook with direct access to the instance. */
|
||||||
|
fun initWithInstance(block: suspend (ScopeFacade, Obj) -> Unit)
|
||||||
|
/** Bind a Lyng function/member to a Kotlin implementation (requires `extern` in Lyng). */
|
||||||
|
fun addFun(name: String, impl: suspend (ScopeFacade, Obj, Arguments) -> Obj)
|
||||||
|
/** Bind a read-only member (val/property getter) declared as `extern`. */
|
||||||
|
fun addVal(name: String, impl: suspend (ScopeFacade, Obj) -> Obj)
|
||||||
|
/** Bind a mutable member (var/property getter/setter) declared as `extern`. */
|
||||||
|
fun addVar(
|
||||||
|
name: String,
|
||||||
|
get: suspend (ScopeFacade, Obj) -> Obj,
|
||||||
|
set: suspend (ScopeFacade, Obj, Obj) -> Unit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for Kotlin bindings to declared Lyng classes.
|
||||||
|
*
|
||||||
|
* The workflow is Lyng-first: declare the class and its members in Lyng,
|
||||||
|
* then bind the implementations from Kotlin. Bound members must be marked
|
||||||
|
* `extern` so the compiler emits the ABI slots for Kotlin to attach to.
|
||||||
|
*/
|
||||||
|
object LyngClassBridge {
|
||||||
|
/**
|
||||||
|
* Resolve a Lyng class by [className] and bind Kotlin implementations.
|
||||||
|
*
|
||||||
|
* @param module module name used for resolution (required when [module] scope is not provided)
|
||||||
|
* @param importManager import manager used to resolve the module
|
||||||
|
*/
|
||||||
|
suspend fun bind(
|
||||||
|
className: String,
|
||||||
|
module: String? = null,
|
||||||
|
importManager: ImportManager = Script.defaultImportManager,
|
||||||
|
block: ClassBridgeBinder.() -> Unit
|
||||||
|
): ObjClass {
|
||||||
|
val cls = resolveClass(className, module, null, importManager)
|
||||||
|
return bind(cls, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a Lyng class within an existing [moduleScope] and bind Kotlin implementations.
|
||||||
|
*/
|
||||||
|
suspend fun bind(
|
||||||
|
moduleScope: ModuleScope,
|
||||||
|
className: String,
|
||||||
|
block: ClassBridgeBinder.() -> Unit
|
||||||
|
): ObjClass {
|
||||||
|
val cls = resolveClass(className, null, moduleScope, Script.defaultImportManager)
|
||||||
|
return bind(cls, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind Kotlin implementations to an already resolved [clazz].
|
||||||
|
*
|
||||||
|
* This must run before the first instance is created.
|
||||||
|
*/
|
||||||
|
fun bind(clazz: ObjClass, block: ClassBridgeBinder.() -> Unit): ObjClass {
|
||||||
|
val binder = ClassBridgeBinderImpl(clazz)
|
||||||
|
binder.block()
|
||||||
|
binder.commit()
|
||||||
|
return clazz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sugar for [LyngClassBridge.bind] on a module scope.
|
||||||
|
*
|
||||||
|
* Bound members must be declared as `extern` in Lyng.
|
||||||
|
*/
|
||||||
|
suspend fun ModuleScope.bind(
|
||||||
|
className: String,
|
||||||
|
block: ClassBridgeBinder.() -> Unit
|
||||||
|
): ObjClass = LyngClassBridge.bind(this, className, block)
|
||||||
|
|
||||||
|
/** Kotlin-side data slot attached to a Lyng instance. */
|
||||||
|
var ObjInstance.data: Any?
|
||||||
|
get() = kotlinInstanceData
|
||||||
|
set(value) { kotlinInstanceData = value }
|
||||||
|
|
||||||
|
/** Kotlin-side data slot attached to a Lyng class. */
|
||||||
|
var ObjClass.classData: Any?
|
||||||
|
get() = kotlinClassData
|
||||||
|
set(value) { kotlinClassData = value }
|
||||||
|
|
||||||
|
private enum class MemberKind { Instance, Static }
|
||||||
|
|
||||||
|
private data class MemberTarget(
|
||||||
|
val name: String,
|
||||||
|
val record: ObjRecord,
|
||||||
|
val kind: MemberKind,
|
||||||
|
val mirrorClassScope: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
private class BridgeInstanceContextImpl(
|
||||||
|
override val instance: Obj
|
||||||
|
) : BridgeInstanceContext {
|
||||||
|
private fun instanceObj(): ObjInstance =
|
||||||
|
instance as? ObjInstance ?: error("Bridge instance is not an ObjInstance")
|
||||||
|
|
||||||
|
override var data: Any?
|
||||||
|
get() = instanceObj().kotlinInstanceData
|
||||||
|
set(value) { instanceObj().kotlinInstanceData = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClassBridgeBinderImpl(
|
||||||
|
private val cls: ObjClass
|
||||||
|
) : ClassBridgeBinder {
|
||||||
|
private val initHooks = mutableListOf<suspend (ScopeFacade, ObjInstance) -> Unit>()
|
||||||
|
private var checkedTemplate = false
|
||||||
|
|
||||||
|
override var classData: Any?
|
||||||
|
get() = cls.kotlinClassData
|
||||||
|
set(value) { cls.kotlinClassData = value }
|
||||||
|
|
||||||
|
override fun init(block: suspend BridgeInstanceContext.(ScopeFacade) -> Unit) {
|
||||||
|
initHooks.add { scope, inst ->
|
||||||
|
val ctx = BridgeInstanceContextImpl(inst)
|
||||||
|
ctx.block(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initWithInstance(block: suspend (ScopeFacade, Obj) -> Unit) {
|
||||||
|
initHooks.add { scope, inst ->
|
||||||
|
block(scope, inst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addFun(name: String, impl: suspend (ScopeFacade, Obj, Arguments) -> Obj) {
|
||||||
|
ensureTemplateNotBuilt()
|
||||||
|
val target = findMember(name)
|
||||||
|
val callable = ObjExternCallable.fromBridge {
|
||||||
|
impl(this, thisObj, args)
|
||||||
|
}
|
||||||
|
val methodId = cls.ensureMethodIdForBridge(name, target.record)
|
||||||
|
val newRecord = target.record.copy(
|
||||||
|
value = callable,
|
||||||
|
type = ObjRecord.Type.Fun,
|
||||||
|
methodId = methodId
|
||||||
|
)
|
||||||
|
replaceMember(target, newRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addVal(name: String, impl: suspend (ScopeFacade, Obj) -> Obj) {
|
||||||
|
ensureTemplateNotBuilt()
|
||||||
|
val target = findMember(name)
|
||||||
|
if (target.record.isMutable) {
|
||||||
|
throw ScriptError(Pos.builtIn, "extern val $name is mutable in class ${cls.className}")
|
||||||
|
}
|
||||||
|
val getter = ObjExternCallable.fromBridge {
|
||||||
|
impl(this, thisObj)
|
||||||
|
}
|
||||||
|
val prop = ObjProperty(name, getter, null)
|
||||||
|
val isFieldLike = target.record.type == ObjRecord.Type.Field ||
|
||||||
|
target.record.type == ObjRecord.Type.ConstructorField
|
||||||
|
val newRecord = if (isFieldLike) {
|
||||||
|
removeFieldInitializersFor(name)
|
||||||
|
target.record.copy(
|
||||||
|
value = prop,
|
||||||
|
type = target.record.type,
|
||||||
|
fieldId = target.record.fieldId,
|
||||||
|
methodId = target.record.methodId
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val methodId = cls.ensureMethodIdForBridge(name, target.record)
|
||||||
|
target.record.copy(
|
||||||
|
value = prop,
|
||||||
|
type = ObjRecord.Type.Property,
|
||||||
|
methodId = methodId,
|
||||||
|
fieldId = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
replaceMember(target, newRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addVar(
|
||||||
|
name: String,
|
||||||
|
get: suspend (ScopeFacade, Obj) -> Obj,
|
||||||
|
set: suspend (ScopeFacade, Obj, Obj) -> Unit
|
||||||
|
) {
|
||||||
|
ensureTemplateNotBuilt()
|
||||||
|
val target = findMember(name)
|
||||||
|
if (!target.record.isMutable) {
|
||||||
|
throw ScriptError(Pos.builtIn, "extern var $name is readonly in class ${cls.className}")
|
||||||
|
}
|
||||||
|
val getter = ObjExternCallable.fromBridge {
|
||||||
|
get(this, thisObj)
|
||||||
|
}
|
||||||
|
val setter = ObjExternCallable.fromBridge {
|
||||||
|
val value = requiredArg<Obj>(0)
|
||||||
|
set(this, thisObj, value)
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
|
val prop = ObjProperty(name, getter, setter)
|
||||||
|
val isFieldLike = target.record.type == ObjRecord.Type.Field ||
|
||||||
|
target.record.type == ObjRecord.Type.ConstructorField
|
||||||
|
val newRecord = if (isFieldLike) {
|
||||||
|
removeFieldInitializersFor(name)
|
||||||
|
target.record.copy(
|
||||||
|
value = prop,
|
||||||
|
type = target.record.type,
|
||||||
|
fieldId = target.record.fieldId,
|
||||||
|
methodId = target.record.methodId
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val methodId = cls.ensureMethodIdForBridge(name, target.record)
|
||||||
|
target.record.copy(
|
||||||
|
value = prop,
|
||||||
|
type = ObjRecord.Type.Property,
|
||||||
|
methodId = methodId,
|
||||||
|
fieldId = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
replaceMember(target, newRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commit() {
|
||||||
|
if (initHooks.isNotEmpty()) {
|
||||||
|
val target = cls.bridgeInitHooks ?: mutableListOf<suspend (ScopeFacade, ObjInstance) -> Unit>().also {
|
||||||
|
cls.bridgeInitHooks = it
|
||||||
|
}
|
||||||
|
target.addAll(initHooks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureTemplateNotBuilt() {
|
||||||
|
if (!checkedTemplate) {
|
||||||
|
if (cls.instanceTemplateBuilt) {
|
||||||
|
throw ScriptError(
|
||||||
|
Pos.builtIn,
|
||||||
|
"bridge binding for ${cls.className} must happen before first instance is created"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
checkedTemplate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceMember(target: MemberTarget, newRecord: ObjRecord) {
|
||||||
|
when (target.kind) {
|
||||||
|
MemberKind.Instance -> {
|
||||||
|
cls.replaceMemberForBridge(target.name, newRecord)
|
||||||
|
if (target.mirrorClassScope && cls.classScope?.objects?.containsKey(target.name) == true) {
|
||||||
|
cls.replaceClassScopeMemberForBridge(target.name, newRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MemberKind.Static -> cls.replaceClassScopeMemberForBridge(target.name, newRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findMember(name: String): MemberTarget {
|
||||||
|
val inst = cls.members[name]
|
||||||
|
val stat = cls.classScope?.objects?.get(name)
|
||||||
|
if (inst != null) {
|
||||||
|
return MemberTarget(name, inst, MemberKind.Instance, mirrorClassScope = stat != null)
|
||||||
|
}
|
||||||
|
if (stat != null) return MemberTarget(name, stat, MemberKind.Static)
|
||||||
|
throw ScriptError(Pos.builtIn, "extern member $name not found in class ${cls.className}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeFieldInitializersFor(name: String) {
|
||||||
|
if (cls.instanceInitializers.isEmpty()) return
|
||||||
|
val storageName = cls.mangledName(name)
|
||||||
|
cls.instanceInitializers.removeAll { init ->
|
||||||
|
val stmt = init as? Statement ?: return@removeAll false
|
||||||
|
val original = (stmt as? BytecodeStatement)?.original ?: stmt
|
||||||
|
original is InstanceFieldInitStatement && original.storageName == storageName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun resolveClass(
|
||||||
|
className: String,
|
||||||
|
module: String?,
|
||||||
|
moduleScope: ModuleScope?,
|
||||||
|
importManager: ImportManager
|
||||||
|
): ObjClass {
|
||||||
|
val scope = moduleScope ?: run {
|
||||||
|
if (module == null) {
|
||||||
|
throw ScriptError(Pos.builtIn, "module is required to resolve $className")
|
||||||
|
}
|
||||||
|
importManager.createModuleScope(Pos.builtIn, module)
|
||||||
|
}
|
||||||
|
val rec = scope.get(className)
|
||||||
|
val direct = rec?.value as? ObjClass
|
||||||
|
if (direct != null) return direct
|
||||||
|
if (className.contains('.')) {
|
||||||
|
val resolved = scope.resolveQualifiedIdentifier(className)
|
||||||
|
val cls = resolved as? ObjClass
|
||||||
|
if (cls != null) return cls
|
||||||
|
}
|
||||||
|
throw ScriptError(Pos.builtIn, "class $className not found in module ${scope.packageName}")
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
|
|
||||||
|
class BytecodeCompileException(
|
||||||
|
message: String,
|
||||||
|
val pos: Pos? = null,
|
||||||
|
) : RuntimeException(message) {
|
||||||
|
override fun toString(): String =
|
||||||
|
pos?.let { "${super.toString()} at $it" } ?: super.toString()
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
import net.sergeych.lyng.ArgsDeclaration
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
|
import net.sergeych.lyng.Visibility
|
||||||
|
import net.sergeych.lyng.obj.ListLiteralRef
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjProperty
|
||||||
|
|
||||||
|
sealed class BytecodeConst {
|
||||||
|
object Null : BytecodeConst()
|
||||||
|
data class Bool(val value: Boolean) : BytecodeConst()
|
||||||
|
data class IntVal(val value: Long) : BytecodeConst()
|
||||||
|
data class RealVal(val value: Double) : BytecodeConst()
|
||||||
|
data class StringVal(val value: String) : BytecodeConst()
|
||||||
|
data class PosVal(val pos: Pos) : BytecodeConst()
|
||||||
|
data class ObjRef(val value: Obj) : BytecodeConst()
|
||||||
|
data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst()
|
||||||
|
data class LambdaFn(
|
||||||
|
val fn: CmdFunction,
|
||||||
|
val captureTableId: Int?,
|
||||||
|
val captureNames: List<String>,
|
||||||
|
val paramSlotPlan: Map<String, Int>,
|
||||||
|
val argsDeclaration: ArgsDeclaration?,
|
||||||
|
val preferredThisType: String?,
|
||||||
|
val wrapAsExtensionCallable: Boolean,
|
||||||
|
val returnLabels: Set<String>,
|
||||||
|
val pos: Pos,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class EnumDecl(
|
||||||
|
val declaredName: String,
|
||||||
|
val qualifiedName: String,
|
||||||
|
val entries: List<String>,
|
||||||
|
val lifted: Boolean,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class FunctionDecl(
|
||||||
|
val spec: net.sergeych.lyng.FunctionDeclSpec,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class ClassDecl(
|
||||||
|
val spec: net.sergeych.lyng.ClassDeclSpec,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst()
|
||||||
|
data class CaptureTable(val entries: List<BytecodeCaptureEntry>) : BytecodeConst()
|
||||||
|
data class ExtensionPropertyDecl(
|
||||||
|
val extTypeName: String,
|
||||||
|
val property: ObjProperty,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val setterVisibility: Visibility?,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class LocalDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class DelegatedDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class ClassFieldDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class ClassDelegatedDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class ClassInstanceInitDecl(
|
||||||
|
val initStatement: Obj,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class ClassInstanceFieldDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val fieldId: Int?,
|
||||||
|
val initStatement: Obj?,
|
||||||
|
val pos: Pos,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class ClassInstancePropertyDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val prop: ObjProperty,
|
||||||
|
val methodId: Int?,
|
||||||
|
val initStatement: Obj?,
|
||||||
|
val pos: Pos,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class ClassInstanceDelegatedDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val methodId: Int?,
|
||||||
|
val initStatement: Obj?,
|
||||||
|
val pos: Pos,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class InstanceFieldDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class InstancePropertyDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class InstanceDelegatedDecl(
|
||||||
|
val storageName: String,
|
||||||
|
val memberName: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val writeVisibility: Visibility?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val accessTypeLabel: String,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class DestructureDecl(
|
||||||
|
val pattern: ListLiteralRef,
|
||||||
|
val names: List<String>,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val pos: Pos,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class DestructureAssign(
|
||||||
|
val pattern: ListLiteralRef,
|
||||||
|
val pos: Pos,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class CallArgsPlan(val tailBlock: Boolean, val specs: List<CallArgSpec>) : BytecodeConst()
|
||||||
|
data class CallArgSpec(val name: String?, val isSplat: Boolean)
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
import net.sergeych.lyng.FrameAccess
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
|
|
||||||
|
class BytecodeFrame(
|
||||||
|
val localCount: Int,
|
||||||
|
val argCount: Int,
|
||||||
|
) : FrameAccess {
|
||||||
|
val slotCount: Int = localCount + argCount
|
||||||
|
val argBase: Int = localCount
|
||||||
|
|
||||||
|
private val slotTypes: ByteArray = ByteArray(slotCount) { SlotType.UNKNOWN.code }
|
||||||
|
private val objSlots: Array<Obj?> = arrayOfNulls(slotCount)
|
||||||
|
private val intSlots: LongArray = LongArray(slotCount)
|
||||||
|
private val realSlots: DoubleArray = DoubleArray(slotCount)
|
||||||
|
private val boolSlots: BooleanArray = BooleanArray(slotCount)
|
||||||
|
|
||||||
|
fun getSlotType(slot: Int): SlotType = SlotType.values().first { it.code == slotTypes[slot] }
|
||||||
|
override fun getSlotTypeCode(slot: Int): Byte = slotTypes[slot]
|
||||||
|
fun setSlotType(slot: Int, type: SlotType) {
|
||||||
|
slotTypes[slot] = type.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getObj(slot: Int): Obj {
|
||||||
|
val value = objSlots[slot] ?: return ObjNull
|
||||||
|
return when (value) {
|
||||||
|
is net.sergeych.lyng.FrameSlotRef -> value.read()
|
||||||
|
is net.sergeych.lyng.RecordSlotRef -> value.read()
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getRawObj(slot: Int): Obj? = objSlots[slot]
|
||||||
|
|
||||||
|
override fun setObj(slot: Int, value: Obj) {
|
||||||
|
when (val current = objSlots[slot]) {
|
||||||
|
is net.sergeych.lyng.FrameSlotRef -> {
|
||||||
|
if (current.refersTo(this, slot)) {
|
||||||
|
objSlots[slot] = value
|
||||||
|
} else {
|
||||||
|
current.write(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.RecordSlotRef -> current.write(value)
|
||||||
|
else -> objSlots[slot] = value
|
||||||
|
}
|
||||||
|
slotTypes[slot] = SlotType.OBJ.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInt(slot: Int): Long = intSlots[slot]
|
||||||
|
override fun setInt(slot: Int, value: Long) {
|
||||||
|
intSlots[slot] = value
|
||||||
|
slotTypes[slot] = SlotType.INT.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReal(slot: Int): Double = realSlots[slot]
|
||||||
|
override fun setReal(slot: Int, value: Double) {
|
||||||
|
realSlots[slot] = value
|
||||||
|
slotTypes[slot] = SlotType.REAL.code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBool(slot: Int): Boolean = boolSlots[slot]
|
||||||
|
override fun setBool(slot: Int, value: Boolean) {
|
||||||
|
boolSlots[slot] = value
|
||||||
|
slotTypes[slot] = SlotType.BOOL.code
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSlot(slot: Int) {
|
||||||
|
slotTypes[slot] = SlotType.UNKNOWN.code
|
||||||
|
objSlots[slot] = null
|
||||||
|
intSlots[slot] = 0L
|
||||||
|
realSlots[slot] = 0.0
|
||||||
|
boolSlots[slot] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,417 @@
|
|||||||
|
/*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
import net.sergeych.lyng.*
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ValueFnRef
|
||||||
|
|
||||||
|
class BytecodeStatement private constructor(
|
||||||
|
val original: Statement,
|
||||||
|
private val function: CmdFunction,
|
||||||
|
) : Statement(original.isStaticConst, original.isConst, original.returnType) {
|
||||||
|
override val pos: Pos = original.pos
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
scope.pos = pos
|
||||||
|
return CmdVm().execute(function, scope, scope.args)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun bytecodeFunction(): CmdFunction = function
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun wrap(
|
||||||
|
statement: Statement,
|
||||||
|
nameHint: String,
|
||||||
|
allowLocalSlots: Boolean,
|
||||||
|
returnLabels: Set<String> = emptySet(),
|
||||||
|
rangeLocalNames: Set<String> = emptySet(),
|
||||||
|
allowedScopeNames: Set<String>? = null,
|
||||||
|
scopeSlotNameSet: Set<String>? = null,
|
||||||
|
moduleScopeId: Int? = null,
|
||||||
|
forcedLocalSlots: Map<String, Int> = emptyMap(),
|
||||||
|
forcedLocalScopeId: Int? = null,
|
||||||
|
forcedLocalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
|
||||||
|
globalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
|
||||||
|
globalSlotScopeId: Int? = null,
|
||||||
|
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
|
knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
||||||
|
knownObjectNames: Set<String> = emptySet(),
|
||||||
|
classFieldTypesByName: Map<String, Map<String, ObjClass>> = emptyMap(),
|
||||||
|
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||||
|
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
|
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||||
|
externCallableNames: Set<String> = emptySet(),
|
||||||
|
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
|
slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
|
||||||
|
): Statement {
|
||||||
|
if (statement is BytecodeStatement) return statement
|
||||||
|
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||||
|
if (hasUnsupported) {
|
||||||
|
val statementName = statement.toString()
|
||||||
|
throw BytecodeCompileException(
|
||||||
|
"Bytecode compile error: unsupported statement $statementName in '$nameHint'",
|
||||||
|
statement.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val safeLocals = allowLocalSlots
|
||||||
|
val compiler = BytecodeCompiler(
|
||||||
|
allowLocalSlots = safeLocals,
|
||||||
|
returnLabels = returnLabels,
|
||||||
|
rangeLocalNames = rangeLocalNames,
|
||||||
|
allowedScopeNames = allowedScopeNames,
|
||||||
|
scopeSlotNameSet = scopeSlotNameSet,
|
||||||
|
moduleScopeId = moduleScopeId,
|
||||||
|
forcedLocalSlots = forcedLocalSlots,
|
||||||
|
forcedLocalScopeId = forcedLocalScopeId,
|
||||||
|
forcedLocalSlotInfo = forcedLocalSlotInfo,
|
||||||
|
globalSlotInfo = globalSlotInfo,
|
||||||
|
globalSlotScopeId = globalSlotScopeId,
|
||||||
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
|
knownNameObjClass = knownNameObjClass,
|
||||||
|
knownObjectNames = knownObjectNames,
|
||||||
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
|
enumEntriesByName = enumEntriesByName,
|
||||||
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
externCallableNames = externCallableNames,
|
||||||
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
|
)
|
||||||
|
val compiled = compiler.compileStatement(nameHint, statement)
|
||||||
|
val fn = compiled ?: throw BytecodeCompileException(
|
||||||
|
"Bytecode compile error: failed to compile '$nameHint'",
|
||||||
|
statement.pos
|
||||||
|
)
|
||||||
|
return BytecodeStatement(statement, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun containsUnsupportedStatement(stmt: Statement): Boolean {
|
||||||
|
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||||
|
return when (target) {
|
||||||
|
is net.sergeych.lyng.ExpressionStatement -> {
|
||||||
|
val ref = target.ref
|
||||||
|
if (ref is net.sergeych.lyng.obj.StatementRef) {
|
||||||
|
containsUnsupportedStatement(ref.statement)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.IfStatement -> {
|
||||||
|
containsUnsupportedStatement(target.condition) ||
|
||||||
|
containsUnsupportedStatement(target.ifBody) ||
|
||||||
|
(target.elseBody?.let { containsUnsupportedStatement(it) } ?: false)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
val unsupported = containsUnsupportedStatement(target.source) ||
|
||||||
|
containsUnsupportedStatement(target.body) ||
|
||||||
|
(target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false)
|
||||||
|
unsupported
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.WhileStatement -> {
|
||||||
|
containsUnsupportedStatement(target.condition) ||
|
||||||
|
containsUnsupportedStatement(target.body) ||
|
||||||
|
(target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.DoWhileStatement -> {
|
||||||
|
containsUnsupportedStatement(target.body) ||
|
||||||
|
containsUnsupportedStatement(target.condition) ||
|
||||||
|
(target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.BlockStatement ->
|
||||||
|
target.statements().any { containsUnsupportedStatement(it) }
|
||||||
|
is net.sergeych.lyng.InlineBlockStatement ->
|
||||||
|
target.statements().any { containsUnsupportedStatement(it) }
|
||||||
|
is net.sergeych.lyng.VarDeclStatement ->
|
||||||
|
target.initializer?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
|
is net.sergeych.lyng.DelegatedVarDeclStatement ->
|
||||||
|
containsUnsupportedStatement(target.initializer)
|
||||||
|
is net.sergeych.lyng.DestructuringVarDeclStatement ->
|
||||||
|
containsUnsupportedStatement(target.initializer)
|
||||||
|
is net.sergeych.lyng.BreakStatement ->
|
||||||
|
target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
|
is net.sergeych.lyng.ContinueStatement -> false
|
||||||
|
is net.sergeych.lyng.ReturnStatement ->
|
||||||
|
target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
|
is net.sergeych.lyng.ThrowStatement ->
|
||||||
|
containsUnsupportedStatement(target.throwExpr)
|
||||||
|
is net.sergeych.lyng.NopStatement -> false
|
||||||
|
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> false
|
||||||
|
is net.sergeych.lyng.ClassDeclStatement -> false
|
||||||
|
is net.sergeych.lyng.FunctionDeclStatement -> false
|
||||||
|
is net.sergeych.lyng.EnumDeclStatement -> false
|
||||||
|
is net.sergeych.lyng.ClassStaticFieldInitStatement ->
|
||||||
|
target.initializer?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
|
is net.sergeych.lyng.ClassInstanceInitDeclStatement ->
|
||||||
|
containsUnsupportedStatement(target.initStatement)
|
||||||
|
is net.sergeych.lyng.ClassInstanceFieldDeclStatement ->
|
||||||
|
target.initStatement?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
|
is net.sergeych.lyng.ClassInstancePropertyDeclStatement ->
|
||||||
|
target.initStatement?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
|
is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement ->
|
||||||
|
target.initStatement?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
|
is net.sergeych.lyng.InstanceFieldInitStatement ->
|
||||||
|
target.initializer?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
|
is net.sergeych.lyng.InstancePropertyInitStatement -> false
|
||||||
|
is net.sergeych.lyng.InstanceDelegatedInitStatement ->
|
||||||
|
containsUnsupportedStatement(target.initializer)
|
||||||
|
is net.sergeych.lyng.TryStatement -> {
|
||||||
|
containsUnsupportedStatement(target.body) ||
|
||||||
|
target.catches.any { containsUnsupportedStatement(it.block) } ||
|
||||||
|
(target.finallyClause?.let { containsUnsupportedStatement(it) } ?: false)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.WhenStatement -> {
|
||||||
|
containsUnsupportedStatement(target.value) ||
|
||||||
|
target.cases.any { case ->
|
||||||
|
case.conditions.any { cond -> containsUnsupportedStatement(cond.expr) } ||
|
||||||
|
containsUnsupportedStatement(case.block)
|
||||||
|
} ||
|
||||||
|
(target.elseCase?.let { containsUnsupportedStatement(it) } ?: false)
|
||||||
|
}
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unwrapDeep(stmt: Statement): Statement {
|
||||||
|
return when (stmt) {
|
||||||
|
is BytecodeStatement -> unwrapDeep(stmt.original)
|
||||||
|
is net.sergeych.lyng.BlockStatement -> {
|
||||||
|
val unwrapped = stmt.statements().map { unwrapDeep(it) }
|
||||||
|
net.sergeych.lyng.BlockStatement(
|
||||||
|
net.sergeych.lyng.Script(stmt.pos, unwrapped),
|
||||||
|
stmt.slotPlan,
|
||||||
|
stmt.scopeId,
|
||||||
|
stmt.captureSlots,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.VarDeclStatement -> {
|
||||||
|
net.sergeych.lyng.VarDeclStatement(
|
||||||
|
stmt.name,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.initializer?.let { unwrapDeep(it) },
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.slotIndex,
|
||||||
|
stmt.scopeId,
|
||||||
|
stmt.pos,
|
||||||
|
stmt.initializerObjClass
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.DestructuringVarDeclStatement -> {
|
||||||
|
net.sergeych.lyng.DestructuringVarDeclStatement(
|
||||||
|
stmt.pattern,
|
||||||
|
stmt.names,
|
||||||
|
unwrapDeep(stmt.initializer),
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.IfStatement -> {
|
||||||
|
net.sergeych.lyng.IfStatement(
|
||||||
|
unwrapDeep(stmt.condition),
|
||||||
|
unwrapDeep(stmt.ifBody),
|
||||||
|
stmt.elseBody?.let { unwrapDeep(it) },
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
net.sergeych.lyng.ForInStatement(
|
||||||
|
stmt.loopVarName,
|
||||||
|
unwrapDeep(stmt.source),
|
||||||
|
stmt.constRange,
|
||||||
|
unwrapDeep(stmt.body),
|
||||||
|
stmt.elseStatement?.let { unwrapDeep(it) },
|
||||||
|
stmt.label,
|
||||||
|
stmt.canBreak,
|
||||||
|
stmt.loopSlotPlan,
|
||||||
|
stmt.loopScopeId,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.WhileStatement -> {
|
||||||
|
net.sergeych.lyng.WhileStatement(
|
||||||
|
unwrapDeep(stmt.condition),
|
||||||
|
unwrapDeep(stmt.body),
|
||||||
|
stmt.elseStatement?.let { unwrapDeep(it) },
|
||||||
|
stmt.label,
|
||||||
|
stmt.canBreak,
|
||||||
|
stmt.loopSlotPlan,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.DoWhileStatement -> {
|
||||||
|
net.sergeych.lyng.DoWhileStatement(
|
||||||
|
unwrapDeep(stmt.body),
|
||||||
|
unwrapDeep(stmt.condition),
|
||||||
|
stmt.elseStatement?.let { unwrapDeep(it) },
|
||||||
|
stmt.label,
|
||||||
|
stmt.loopSlotPlan,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.BreakStatement -> {
|
||||||
|
val resultExpr = stmt.resultExpr?.let { unwrapDeep(it) }
|
||||||
|
net.sergeych.lyng.BreakStatement(stmt.label, resultExpr, stmt.pos)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ContinueStatement ->
|
||||||
|
net.sergeych.lyng.ContinueStatement(stmt.label, stmt.pos)
|
||||||
|
is net.sergeych.lyng.ReturnStatement -> {
|
||||||
|
val resultExpr = stmt.resultExpr?.let { unwrapDeep(it) }
|
||||||
|
net.sergeych.lyng.ReturnStatement(stmt.label, resultExpr, stmt.pos)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ThrowStatement ->
|
||||||
|
net.sergeych.lyng.ThrowStatement(unwrapDeep(stmt.throwExpr), stmt.pos)
|
||||||
|
is net.sergeych.lyng.WhenStatement -> {
|
||||||
|
net.sergeych.lyng.WhenStatement(
|
||||||
|
unwrapDeep(stmt.value),
|
||||||
|
stmt.cases.map { case ->
|
||||||
|
net.sergeych.lyng.WhenCase(
|
||||||
|
case.conditions.map { unwrapWhenCondition(it) },
|
||||||
|
unwrapDeep(case.block)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
stmt.elseCase?.let { unwrapDeep(it) },
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ClassStaticFieldInitStatement -> {
|
||||||
|
net.sergeych.lyng.ClassStaticFieldInitStatement(
|
||||||
|
stmt.name,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.writeVisibility,
|
||||||
|
stmt.initializer?.let { unwrapDeep(it) },
|
||||||
|
stmt.isDelegated,
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ClassInstanceInitDeclStatement -> {
|
||||||
|
net.sergeych.lyng.ClassInstanceInitDeclStatement(
|
||||||
|
unwrapDeep(stmt.initStatement),
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ClassInstanceFieldDeclStatement -> {
|
||||||
|
net.sergeych.lyng.ClassInstanceFieldDeclStatement(
|
||||||
|
stmt.name,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.writeVisibility,
|
||||||
|
stmt.isAbstract,
|
||||||
|
stmt.isClosed,
|
||||||
|
stmt.isOverride,
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.fieldId,
|
||||||
|
stmt.initStatement?.let { unwrapDeep(it) },
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ClassInstancePropertyDeclStatement -> {
|
||||||
|
net.sergeych.lyng.ClassInstancePropertyDeclStatement(
|
||||||
|
stmt.name,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.writeVisibility,
|
||||||
|
stmt.isAbstract,
|
||||||
|
stmt.isClosed,
|
||||||
|
stmt.isOverride,
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.prop,
|
||||||
|
stmt.methodId,
|
||||||
|
stmt.initStatement?.let { unwrapDeep(it) },
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement -> {
|
||||||
|
net.sergeych.lyng.ClassInstanceDelegatedDeclStatement(
|
||||||
|
stmt.name,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.writeVisibility,
|
||||||
|
stmt.isAbstract,
|
||||||
|
stmt.isClosed,
|
||||||
|
stmt.isOverride,
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.methodId,
|
||||||
|
stmt.initStatement?.let { unwrapDeep(it) },
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.InstanceFieldInitStatement -> {
|
||||||
|
net.sergeych.lyng.InstanceFieldInitStatement(
|
||||||
|
stmt.storageName,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.writeVisibility,
|
||||||
|
stmt.isAbstract,
|
||||||
|
stmt.isClosed,
|
||||||
|
stmt.isOverride,
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.isLateInitVal,
|
||||||
|
stmt.initializer?.let { unwrapDeep(it) },
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.InstancePropertyInitStatement -> {
|
||||||
|
net.sergeych.lyng.InstancePropertyInitStatement(
|
||||||
|
stmt.storageName,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.writeVisibility,
|
||||||
|
stmt.isAbstract,
|
||||||
|
stmt.isClosed,
|
||||||
|
stmt.isOverride,
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.prop,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.InstanceDelegatedInitStatement -> {
|
||||||
|
net.sergeych.lyng.InstanceDelegatedInitStatement(
|
||||||
|
stmt.storageName,
|
||||||
|
stmt.memberName,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.writeVisibility,
|
||||||
|
stmt.isAbstract,
|
||||||
|
stmt.isClosed,
|
||||||
|
stmt.isOverride,
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.accessTypeLabel,
|
||||||
|
unwrapDeep(stmt.initializer),
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> stmt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unwrapWhenCondition(cond: WhenCondition): WhenCondition {
|
||||||
|
return when (cond) {
|
||||||
|
is WhenEqualsCondition -> WhenEqualsCondition(unwrapDeep(cond.expr), cond.pos)
|
||||||
|
is WhenInCondition -> WhenInCondition(unwrapDeep(cond.expr), cond.negated, cond.pos)
|
||||||
|
is WhenIsCondition -> WhenIsCondition(unwrapDeep(cond.expr), cond.negated, cond.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,832 @@
|
|||||||
|
/*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
class CmdBuilder {
|
||||||
|
sealed interface Operand {
|
||||||
|
data class IntVal(val value: Int) : Operand
|
||||||
|
data class LabelRef(val label: Label) : Operand
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Label(val id: Int)
|
||||||
|
|
||||||
|
data class Instr(val op: Opcode, val operands: List<Operand>)
|
||||||
|
|
||||||
|
private val instructions = mutableListOf<Instr>()
|
||||||
|
private val posByInstr = mutableListOf<net.sergeych.lyng.Pos?>()
|
||||||
|
private val constPool = mutableListOf<BytecodeConst>()
|
||||||
|
private val labelPositions = mutableMapOf<Label, Int>()
|
||||||
|
private var nextLabelId = 0
|
||||||
|
private var currentPos: net.sergeych.lyng.Pos? = null
|
||||||
|
|
||||||
|
fun addConst(c: BytecodeConst): Int {
|
||||||
|
constPool += c
|
||||||
|
return constPool.lastIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emit(op: Opcode, vararg operands: Int) {
|
||||||
|
instructions += Instr(op, operands.map { Operand.IntVal(it) })
|
||||||
|
posByInstr += currentPos
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emit(op: Opcode, operands: List<Operand>) {
|
||||||
|
instructions += Instr(op, operands)
|
||||||
|
posByInstr += currentPos
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPos(pos: net.sergeych.lyng.Pos?) {
|
||||||
|
currentPos = pos
|
||||||
|
}
|
||||||
|
|
||||||
|
fun label(): Label = Label(nextLabelId++)
|
||||||
|
|
||||||
|
fun mark(label: Label) {
|
||||||
|
labelPositions[label] = instructions.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
name: String,
|
||||||
|
localCount: Int,
|
||||||
|
addrCount: Int = 0,
|
||||||
|
returnLabels: Set<String> = emptySet(),
|
||||||
|
scopeSlotIndices: IntArray = IntArray(0),
|
||||||
|
scopeSlotNames: Array<String?> = emptyArray(),
|
||||||
|
scopeSlotIsModule: BooleanArray = BooleanArray(0),
|
||||||
|
localSlotNames: Array<String?> = emptyArray(),
|
||||||
|
localSlotMutables: BooleanArray = BooleanArray(0),
|
||||||
|
localSlotDelegated: BooleanArray = BooleanArray(0),
|
||||||
|
localSlotCaptures: BooleanArray = BooleanArray(0)
|
||||||
|
): CmdFunction {
|
||||||
|
val scopeSlotCount = scopeSlotIndices.size
|
||||||
|
require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) {
|
||||||
|
"scope slot name mapping size mismatch"
|
||||||
|
}
|
||||||
|
require(scopeSlotIsModule.isEmpty() || scopeSlotIsModule.size == scopeSlotCount) {
|
||||||
|
"scope slot module mapping size mismatch"
|
||||||
|
}
|
||||||
|
require(localSlotNames.size == localSlotMutables.size) { "local slot metadata size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotDelegated.size) { "local slot delegation size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotCaptures.size) { "local slot capture size mismatch" }
|
||||||
|
val labelIps = mutableMapOf<Label, Int>()
|
||||||
|
for ((label, idx) in labelPositions) {
|
||||||
|
labelIps[label] = idx
|
||||||
|
}
|
||||||
|
val cmds = ArrayList<Cmd>(instructions.size)
|
||||||
|
for (ins in instructions) {
|
||||||
|
val kinds = operandKinds(ins.op)
|
||||||
|
if (kinds.size != ins.operands.size) {
|
||||||
|
error("Operand count mismatch for ${ins.op}: expected ${kinds.size}, got ${ins.operands.size}")
|
||||||
|
}
|
||||||
|
val operands = IntArray(kinds.size)
|
||||||
|
for (i in kinds.indices) {
|
||||||
|
val operand = ins.operands[i]
|
||||||
|
val v = when (operand) {
|
||||||
|
is Operand.IntVal -> operand.value
|
||||||
|
is Operand.LabelRef -> labelIps[operand.label]
|
||||||
|
?: error("Unknown label ${operand.label.id} for ${ins.op}")
|
||||||
|
}
|
||||||
|
operands[i] = v
|
||||||
|
}
|
||||||
|
cmds.add(createCmd(ins.op, operands, scopeSlotCount, localSlotCaptures))
|
||||||
|
}
|
||||||
|
return CmdFunction(
|
||||||
|
name = name,
|
||||||
|
localCount = localCount,
|
||||||
|
addrCount = addrCount,
|
||||||
|
returnLabels = returnLabels,
|
||||||
|
scopeSlotCount = scopeSlotCount,
|
||||||
|
scopeSlotIndices = scopeSlotIndices,
|
||||||
|
scopeSlotNames = if (scopeSlotNames.isEmpty()) Array(scopeSlotCount) { null } else scopeSlotNames,
|
||||||
|
scopeSlotIsModule = if (scopeSlotIsModule.isEmpty()) BooleanArray(scopeSlotCount) else scopeSlotIsModule,
|
||||||
|
localSlotNames = localSlotNames,
|
||||||
|
localSlotMutables = localSlotMutables,
|
||||||
|
localSlotDelegated = localSlotDelegated,
|
||||||
|
localSlotCaptures = localSlotCaptures,
|
||||||
|
constants = constPool.toList(),
|
||||||
|
cmds = cmds.toTypedArray(),
|
||||||
|
posByIp = posByInstr.toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||||
|
return when (op) {
|
||||||
|
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN, Opcode.POP_TRY,
|
||||||
|
Opcode.CLEAR_PENDING_THROWABLE, Opcode.RETHROW_PENDING -> emptyList()
|
||||||
|
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
||||||
|
Opcode.UNBOX_INT_OBJ, Opcode.UNBOX_REAL_OBJ,
|
||||||
|
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
||||||
|
Opcode.OBJ_TO_BOOL, Opcode.GET_OBJ_CLASS,
|
||||||
|
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT,
|
||||||
|
Opcode.ASSERT_IS ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.CHECK_IS, Opcode.MAKE_QUALIFIED_VIEW ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.RANGE_INT_BOUNDS ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.RET_LABEL, Opcode.THROW ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.RESOLVE_SCOPE_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.DELEGATED_GET_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.DELEGATED_SET_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.BIND_DELEGATE_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR ->
|
||||||
|
listOf(OperandKind.ADDR, OperandKind.SLOT)
|
||||||
|
Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.CONST_NULL ->
|
||||||
|
listOf(OperandKind.SLOT)
|
||||||
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL,
|
||||||
|
Opcode.MAKE_LAMBDA_FN ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
|
listOf(OperandKind.CONST)
|
||||||
|
Opcode.PUSH_TRY ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
||||||
|
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
||||||
|
Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS, Opcode.DECL_CLASS_FIELD,
|
||||||
|
Opcode.DECL_CLASS_DELEGATED, Opcode.DECL_CLASS_INSTANCE_INIT, Opcode.DECL_CLASS_INSTANCE_FIELD,
|
||||||
|
Opcode.DECL_CLASS_INSTANCE_PROPERTY, Opcode.DECL_CLASS_INSTANCE_DELEGATED, Opcode.DECL_INSTANCE_FIELD,
|
||||||
|
Opcode.DECL_INSTANCE_PROPERTY, Opcode.DECL_INSTANCE_DELEGATED,
|
||||||
|
Opcode.ASSIGN_DESTRUCTURE ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||||
|
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
||||||
|
Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
||||||
|
Opcode.CMP_EQ_INT, Opcode.CMP_NEQ_INT, Opcode.CMP_LT_INT, Opcode.CMP_LTE_INT,
|
||||||
|
Opcode.CMP_GT_INT, Opcode.CMP_GTE_INT,
|
||||||
|
Opcode.CMP_EQ_REAL, Opcode.CMP_NEQ_REAL, Opcode.CMP_LT_REAL, Opcode.CMP_LTE_REAL,
|
||||||
|
Opcode.CMP_GT_REAL, Opcode.CMP_GTE_REAL,
|
||||||
|
Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL,
|
||||||
|
Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT,
|
||||||
|
Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT,
|
||||||
|
Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT,
|
||||||
|
Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ,
|
||||||
|
Opcode.CMP_EQ_STR, Opcode.CMP_NEQ_STR, Opcode.CMP_LT_STR, Opcode.CMP_LTE_STR,
|
||||||
|
Opcode.CMP_GT_STR, Opcode.CMP_GTE_STR,
|
||||||
|
Opcode.CMP_EQ_INT_OBJ, Opcode.CMP_NEQ_INT_OBJ, Opcode.CMP_LT_INT_OBJ, Opcode.CMP_LTE_INT_OBJ,
|
||||||
|
Opcode.CMP_GT_INT_OBJ, Opcode.CMP_GTE_INT_OBJ, Opcode.CMP_EQ_REAL_OBJ, Opcode.CMP_NEQ_REAL_OBJ,
|
||||||
|
Opcode.CMP_LT_REAL_OBJ, Opcode.CMP_LTE_REAL_OBJ, Opcode.CMP_GT_REAL_OBJ, Opcode.CMP_GTE_REAL_OBJ,
|
||||||
|
Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ,
|
||||||
|
Opcode.ADD_INT_OBJ, Opcode.SUB_INT_OBJ, Opcode.MUL_INT_OBJ, Opcode.DIV_INT_OBJ, Opcode.MOD_INT_OBJ,
|
||||||
|
Opcode.ADD_REAL_OBJ, Opcode.SUB_REAL_OBJ, Opcode.MUL_REAL_OBJ, Opcode.DIV_REAL_OBJ, Opcode.MOD_REAL_OBJ,
|
||||||
|
Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, Opcode.CONTAINS_OBJ,
|
||||||
|
Opcode.AND_BOOL, Opcode.OR_BOOL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.ASSIGN_OP_OBJ ->
|
||||||
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
|
||||||
|
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.LOAD_THIS ->
|
||||||
|
listOf(OperandKind.SLOT)
|
||||||
|
Opcode.LOAD_THIS_VARIANT ->
|
||||||
|
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||||
|
Opcode.JMP ->
|
||||||
|
listOf(OperandKind.IP)
|
||||||
|
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||||
|
Opcode.JMP_IF_EQ_INT, Opcode.JMP_IF_NEQ_INT,
|
||||||
|
Opcode.JMP_IF_LT_INT, Opcode.JMP_IF_LTE_INT,
|
||||||
|
Opcode.JMP_IF_GT_INT, Opcode.JMP_IF_GTE_INT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.IP)
|
||||||
|
Opcode.CALL_DIRECT ->
|
||||||
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.CALL_MEMBER_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.CALL_SLOT, Opcode.CALL_BRIDGE_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.CALL_DYNAMIC_MEMBER ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.GET_INDEX ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.SET_INDEX ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.MAKE_RANGE ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.LIST_LITERAL ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.GET_MEMBER_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||||
|
Opcode.SET_MEMBER_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||||
|
Opcode.GET_CLASS_SCOPE ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.SET_CLASS_SCOPE ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.GET_DYNAMIC_MEMBER ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.SET_DYNAMIC_MEMBER ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.ITER_PUSH ->
|
||||||
|
listOf(OperandKind.SLOT)
|
||||||
|
Opcode.ITER_POP, Opcode.ITER_CANCEL ->
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class OperandKind {
|
||||||
|
SLOT,
|
||||||
|
ADDR,
|
||||||
|
CONST,
|
||||||
|
IP,
|
||||||
|
COUNT,
|
||||||
|
ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createCmd(
|
||||||
|
op: Opcode,
|
||||||
|
operands: IntArray,
|
||||||
|
scopeSlotCount: Int,
|
||||||
|
localSlotCaptures: BooleanArray
|
||||||
|
): Cmd {
|
||||||
|
fun isFastLocal(slot: Int): Boolean {
|
||||||
|
if (slot < scopeSlotCount) return false
|
||||||
|
val localIndex = slot - scopeSlotCount
|
||||||
|
return localSlotCaptures.getOrNull(localIndex) != true
|
||||||
|
}
|
||||||
|
return when (op) {
|
||||||
|
Opcode.NOP -> CmdNop()
|
||||||
|
Opcode.MOVE_OBJ -> CmdMoveObj(operands[0], operands[1])
|
||||||
|
Opcode.MOVE_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdMoveIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdMoveInt(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.MOVE_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdMoveRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdMoveReal(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.MOVE_BOOL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdMoveBoolLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdMoveBool(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.CONST_OBJ -> CmdConstObj(operands[0], operands[1])
|
||||||
|
Opcode.CONST_INT -> if (isFastLocal(operands[1])) {
|
||||||
|
CmdConstIntLocal(operands[0], operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdConstInt(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
|
||||||
|
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
|
||||||
|
Opcode.CONST_NULL -> CmdConstNull(operands[0])
|
||||||
|
Opcode.MAKE_LAMBDA_FN -> CmdMakeLambda(operands[0], operands[1])
|
||||||
|
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
|
||||||
|
Opcode.UNBOX_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdUnboxIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdUnboxIntObj(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.UNBOX_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdUnboxRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdUnboxRealObj(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
|
||||||
|
Opcode.GET_OBJ_CLASS -> CmdGetObjClass(operands[0], operands[1])
|
||||||
|
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.LOAD_THIS -> CmdLoadThis(operands[0])
|
||||||
|
Opcode.LOAD_THIS_VARIANT -> CmdLoadThisVariant(operands[0], operands[1])
|
||||||
|
Opcode.MAKE_RANGE -> CmdMakeRange(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
|
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
|
||||||
|
Opcode.MAKE_QUALIFIED_VIEW -> CmdMakeQualifiedView(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.RET_LABEL -> CmdRetLabel(operands[0], operands[1])
|
||||||
|
Opcode.THROW -> CmdThrow(operands[0], operands[1])
|
||||||
|
Opcode.RETHROW_PENDING -> CmdRethrowPending()
|
||||||
|
Opcode.RESOLVE_SCOPE_SLOT -> CmdResolveScopeSlot(operands[0], operands[1])
|
||||||
|
Opcode.DELEGATED_GET_LOCAL -> CmdDelegatedGetLocal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.DELEGATED_SET_LOCAL -> CmdDelegatedSetLocal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.BIND_DELEGATE_LOCAL -> CmdBindDelegateLocal(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1])
|
||||||
|
Opcode.STORE_OBJ_ADDR -> CmdStoreObjAddr(operands[0], operands[1])
|
||||||
|
Opcode.LOAD_INT_ADDR -> CmdLoadIntAddr(operands[0], operands[1])
|
||||||
|
Opcode.STORE_INT_ADDR -> CmdStoreIntAddr(operands[0], operands[1])
|
||||||
|
Opcode.LOAD_REAL_ADDR -> CmdLoadRealAddr(operands[0], operands[1])
|
||||||
|
Opcode.STORE_REAL_ADDR -> CmdStoreRealAddr(operands[0], operands[1])
|
||||||
|
Opcode.LOAD_BOOL_ADDR -> CmdLoadBoolAddr(operands[0], operands[1])
|
||||||
|
Opcode.STORE_BOOL_ADDR -> CmdStoreBoolAddr(operands[0], operands[1])
|
||||||
|
Opcode.INT_TO_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdIntToRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdIntToReal(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.REAL_TO_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdRealToIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdRealToInt(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.BOOL_TO_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdBoolToIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdBoolToInt(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.INT_TO_BOOL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdIntToBoolLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdIntToBool(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.ADD_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdAddIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdAddInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.SUB_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdSubIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdSubInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.MUL_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdMulIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdMulInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.DIV_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdDivIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdDivInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.MOD_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdModIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdModInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.NEG_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdNegIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdNegInt(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.INC_INT -> if (isFastLocal(operands[0])) {
|
||||||
|
CmdIncIntLocal(operands[0] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdIncInt(operands[0])
|
||||||
|
}
|
||||||
|
Opcode.DEC_INT -> if (isFastLocal(operands[0])) {
|
||||||
|
CmdDecIntLocal(operands[0] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdDecInt(operands[0])
|
||||||
|
}
|
||||||
|
Opcode.ADD_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdAddRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdAddReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.SUB_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdSubRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdSubReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.MUL_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdMulRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdMulReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.DIV_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdDivRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdDivReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.NEG_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdNegRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdNegReal(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.AND_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdAndIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdAndInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.OR_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdOrIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdOrInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.XOR_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdXorIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdXorInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.SHL_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdShlIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdShlInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.SHR_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdShrIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdShrInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.USHR_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdUshrIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdUshrInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.INV_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdInvIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdInvInt(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpEqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpEqInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_NEQ_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpNeqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpNeqInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LT_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLtInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LTE_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLteInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GT_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGtInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GTE_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGteInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpEqRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpEqReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_NEQ_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpNeqRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpNeqReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LT_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLtRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLtReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LTE_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLteRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLteReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GT_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGtRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGtReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GTE_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGteRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGteReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_BOOL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpEqBoolLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpEqBool(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_NEQ_BOOL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpNeqBoolLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpNeqBool(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_INT_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpEqIntRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpEqIntReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_REAL_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpEqRealIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpEqRealInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LT_INT_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLtIntRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLtIntReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LT_REAL_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLtRealIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLtRealInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LTE_INT_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLteIntRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLteIntReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LTE_REAL_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLteRealIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLteRealInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GT_INT_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGtIntRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGtIntReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GT_REAL_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGtRealIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGtRealInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GTE_INT_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGteIntRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGteIntReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GTE_REAL_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGteRealIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGteRealInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_NEQ_INT_REAL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpNeqIntRealLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpNeqIntReal(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_NEQ_REAL_INT -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpNeqRealIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpNeqRealInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_OBJ -> CmdCmpEqObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_NEQ_OBJ -> CmdCmpNeqObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_REF_EQ_OBJ -> CmdCmpRefEqObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_REF_NEQ_OBJ -> CmdCmpRefNeqObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_EQ_STR -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpEqStrLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpEqStr(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_NEQ_STR -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpNeqStrLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpNeqStr(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LT_STR -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLtStrLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLtStr(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LTE_STR -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLteStrLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLteStr(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GT_STR -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGtStrLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGtStr(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GTE_STR -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGteStrLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGteStr(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpEqIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpEqIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_NEQ_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpNeqIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpNeqIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LT_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLtIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLtIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LTE_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLteIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLteIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GT_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGtIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGtIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GTE_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGteIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGteIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpEqRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpEqRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_NEQ_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpNeqRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpNeqRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LT_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLtRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLtRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LTE_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpLteRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLteRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GT_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGtRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGtRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GTE_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdCmpGteRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGteRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.NOT_BOOL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1])) {
|
||||||
|
CmdNotBoolLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdNotBool(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.AND_BOOL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdAndBoolLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdAndBool(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.OR_BOOL -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdOrBoolLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdOrBool(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LT_OBJ -> CmdCmpLtObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_LTE_OBJ -> CmdCmpLteObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GT_OBJ -> CmdCmpGtObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GTE_OBJ -> CmdCmpGteObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.ADD_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdAddIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdAddIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.SUB_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdSubIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdSubIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.MUL_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdMulIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdMulIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.DIV_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdDivIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdDivIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.MOD_INT_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdModIntObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdModIntObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.ADD_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdAddRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdAddRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.SUB_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdSubRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdSubRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.MUL_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdMulRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdMulRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.DIV_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdDivRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdDivRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.MOD_REAL_OBJ -> if (isFastLocal(operands[0]) && isFastLocal(operands[1]) && isFastLocal(operands[2])) {
|
||||||
|
CmdModRealObjLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdModRealObj(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.ADD_OBJ -> CmdAddObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SUB_OBJ -> CmdSubObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.MUL_OBJ -> CmdMulObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.DIV_OBJ -> CmdDivObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.MOD_OBJ -> CmdModObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CONTAINS_OBJ -> CmdContainsObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.ASSIGN_OP_OBJ -> CmdAssignOpObj(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
|
Opcode.JMP -> CmdJmp(operands[0])
|
||||||
|
Opcode.JMP_IF_TRUE -> if (operands[0] >= scopeSlotCount) {
|
||||||
|
CmdJmpIfTrueLocal(operands[0] - scopeSlotCount, operands[1])
|
||||||
|
} else {
|
||||||
|
CmdJmpIfTrue(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.JMP_IF_FALSE -> if (operands[0] >= scopeSlotCount) {
|
||||||
|
CmdJmpIfFalseLocal(operands[0] - scopeSlotCount, operands[1])
|
||||||
|
} else {
|
||||||
|
CmdJmpIfFalse(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.JMP_IF_EQ_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||||
|
CmdJmpIfEqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||||
|
} else {
|
||||||
|
CmdJmpIfEqInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.JMP_IF_NEQ_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||||
|
CmdJmpIfNeqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||||
|
} else {
|
||||||
|
CmdJmpIfNeqInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.JMP_IF_LT_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||||
|
CmdJmpIfLtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||||
|
} else {
|
||||||
|
CmdJmpIfLtInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.JMP_IF_LTE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||||
|
CmdJmpIfLteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||||
|
} else {
|
||||||
|
CmdJmpIfLteInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.JMP_IF_GT_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||||
|
CmdJmpIfGtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||||
|
} else {
|
||||||
|
CmdJmpIfGtInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.JMP_IF_GTE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||||
|
CmdJmpIfGteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||||
|
} else {
|
||||||
|
CmdJmpIfGteInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.RET -> CmdRet(operands[0])
|
||||||
|
Opcode.RET_VOID -> CmdRetVoid()
|
||||||
|
Opcode.PUSH_SCOPE -> CmdPushScope(operands[0])
|
||||||
|
Opcode.POP_SCOPE -> CmdPopScope()
|
||||||
|
Opcode.PUSH_SLOT_PLAN -> CmdPushSlotPlan(operands[0])
|
||||||
|
Opcode.POP_SLOT_PLAN -> CmdPopSlotPlan()
|
||||||
|
Opcode.PUSH_TRY -> CmdPushTry(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.POP_TRY -> CmdPopTry()
|
||||||
|
Opcode.CLEAR_PENDING_THROWABLE -> CmdClearPendingThrowable()
|
||||||
|
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
||||||
|
Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
|
||||||
|
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
|
||||||
|
Opcode.DECL_ENUM -> CmdDeclEnum(operands[0], operands[1])
|
||||||
|
Opcode.DECL_FUNCTION -> CmdDeclFunction(operands[0], operands[1])
|
||||||
|
Opcode.DECL_CLASS -> CmdDeclClass(operands[0], operands[1])
|
||||||
|
Opcode.DECL_CLASS_FIELD -> CmdDeclClassField(operands[0], operands[1])
|
||||||
|
Opcode.DECL_CLASS_DELEGATED -> CmdDeclClassDelegated(operands[0], operands[1])
|
||||||
|
Opcode.DECL_CLASS_INSTANCE_INIT -> CmdDeclClassInstanceInit(operands[0], operands[1])
|
||||||
|
Opcode.DECL_CLASS_INSTANCE_FIELD -> CmdDeclClassInstanceField(operands[0], operands[1])
|
||||||
|
Opcode.DECL_CLASS_INSTANCE_PROPERTY -> CmdDeclClassInstanceProperty(operands[0], operands[1])
|
||||||
|
Opcode.DECL_CLASS_INSTANCE_DELEGATED -> CmdDeclClassInstanceDelegated(operands[0], operands[1])
|
||||||
|
Opcode.DECL_INSTANCE_FIELD -> CmdDeclInstanceField(operands[0], operands[1])
|
||||||
|
Opcode.DECL_INSTANCE_PROPERTY -> CmdDeclInstanceProperty(operands[0], operands[1])
|
||||||
|
Opcode.DECL_INSTANCE_DELEGATED -> CmdDeclInstanceDelegated(operands[0], operands[1])
|
||||||
|
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
||||||
|
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1])
|
||||||
|
Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
|
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.CALL_BRIDGE_SLOT -> CmdCallBridgeSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.CALL_DYNAMIC_MEMBER -> CmdCallDynamicMember(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
|
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.GET_CLASS_SCOPE -> CmdGetClassScope(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SET_CLASS_SCOPE -> CmdSetClassScope(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.GET_DYNAMIC_MEMBER -> CmdGetDynamicMember(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SET_DYNAMIC_MEMBER -> CmdSetDynamicMember(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.ITER_PUSH -> CmdIterPush(operands[0])
|
||||||
|
Opcode.ITER_POP -> CmdIterPop()
|
||||||
|
Opcode.ITER_CANCEL -> CmdIterCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,631 @@
|
|||||||
|
/*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
object CmdDisassembler {
|
||||||
|
fun disassemble(fn: CmdFunction): String {
|
||||||
|
val out = StringBuilder()
|
||||||
|
val cmds = fn.cmds
|
||||||
|
for (i in cmds.indices) {
|
||||||
|
val (op, opValues) = opAndOperands(fn, cmds[i])
|
||||||
|
val kinds = operandKinds(op)
|
||||||
|
val operands = ArrayList<String>(kinds.size)
|
||||||
|
for (k in kinds.indices) {
|
||||||
|
val v = opValues.getOrElse(k) { 0 }
|
||||||
|
when (kinds[k]) {
|
||||||
|
OperandKind.SLOT -> {
|
||||||
|
val name = when {
|
||||||
|
v < fn.scopeSlotCount -> fn.scopeSlotNames[v]
|
||||||
|
else -> {
|
||||||
|
val localIndex = v - fn.scopeSlotCount
|
||||||
|
fn.localSlotNames.getOrNull(localIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
operands += if (name != null) "s$v($name)" else "s$v"
|
||||||
|
}
|
||||||
|
OperandKind.ADDR -> operands += "a$v"
|
||||||
|
OperandKind.CONST -> operands += "k$v"
|
||||||
|
OperandKind.IP -> operands += "ip$v"
|
||||||
|
OperandKind.COUNT -> operands += "n$v"
|
||||||
|
OperandKind.ID -> operands += "#$v"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.append(i).append(": ").append(op.name)
|
||||||
|
if (operands.isNotEmpty()) {
|
||||||
|
out.append(' ').append(operands.joinToString(", "))
|
||||||
|
}
|
||||||
|
out.append('\n')
|
||||||
|
}
|
||||||
|
val captureConsts = fn.constants.withIndex().mapNotNull { (idx, constVal) ->
|
||||||
|
val table = constVal as? BytecodeConst.CaptureTable ?: return@mapNotNull null
|
||||||
|
idx to table
|
||||||
|
}
|
||||||
|
if (captureConsts.isNotEmpty()) {
|
||||||
|
out.append("consts:\n")
|
||||||
|
for ((idx, table) in captureConsts) {
|
||||||
|
val entries = if (table.entries.isEmpty()) {
|
||||||
|
"[]"
|
||||||
|
} else {
|
||||||
|
table.entries.joinToString(prefix = "[", postfix = "]") { entry ->
|
||||||
|
"${entry.ownerKind}#${entry.ownerScopeId}:${entry.ownerSlotId}@s${entry.slotIndex}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.append("k").append(idx).append(" CAPTURE_TABLE ").append(entries).append('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun opAndOperands(fn: CmdFunction, cmd: Cmd): Pair<Opcode, IntArray> {
|
||||||
|
return when (cmd) {
|
||||||
|
is CmdNop -> Opcode.NOP to intArrayOf()
|
||||||
|
is CmdMoveObj -> Opcode.MOVE_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdMoveInt -> Opcode.MOVE_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdMoveIntLocal -> Opcode.MOVE_INT to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdMoveRealLocal -> Opcode.MOVE_REAL to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdMoveBoolLocal -> Opcode.MOVE_BOOL to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdMoveReal -> Opcode.MOVE_REAL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdMoveBool -> Opcode.MOVE_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdConstObj -> Opcode.CONST_OBJ to intArrayOf(cmd.constId, cmd.dst)
|
||||||
|
is CmdConstInt -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst)
|
||||||
|
is CmdConstIntLocal -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdConstReal -> Opcode.CONST_REAL to intArrayOf(cmd.constId, cmd.dst)
|
||||||
|
is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst)
|
||||||
|
is CmdLoadThis -> Opcode.LOAD_THIS to intArrayOf(cmd.dst)
|
||||||
|
is CmdLoadThisVariant -> Opcode.LOAD_THIS_VARIANT to intArrayOf(cmd.typeId, cmd.dst)
|
||||||
|
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
|
||||||
|
is CmdMakeLambda -> Opcode.MAKE_LAMBDA_FN to intArrayOf(cmd.id, cmd.dst)
|
||||||
|
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdGetObjClass -> Opcode.GET_OBJ_CLASS to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
||||||
|
is CmdAssertIs -> Opcode.ASSERT_IS to intArrayOf(cmd.objSlot, cmd.typeSlot)
|
||||||
|
is CmdMakeQualifiedView -> Opcode.MAKE_QUALIFIED_VIEW to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
||||||
|
is CmdRangeIntBounds -> Opcode.RANGE_INT_BOUNDS to intArrayOf(cmd.src, cmd.startSlot, cmd.endSlot, cmd.okSlot)
|
||||||
|
is CmdMakeRange -> Opcode.MAKE_RANGE to intArrayOf(
|
||||||
|
cmd.startSlot,
|
||||||
|
cmd.endSlot,
|
||||||
|
cmd.inclusiveSlot,
|
||||||
|
cmd.stepSlot,
|
||||||
|
cmd.dst
|
||||||
|
)
|
||||||
|
is CmdResolveScopeSlot -> Opcode.RESOLVE_SCOPE_SLOT to intArrayOf(cmd.scopeSlot, cmd.addrSlot)
|
||||||
|
is CmdDelegatedGetLocal -> Opcode.DELEGATED_GET_LOCAL to intArrayOf(cmd.delegateSlot, cmd.nameId, cmd.dst)
|
||||||
|
is CmdDelegatedSetLocal -> Opcode.DELEGATED_SET_LOCAL to intArrayOf(cmd.delegateSlot, cmd.nameId, cmd.valueSlot)
|
||||||
|
is CmdBindDelegateLocal -> Opcode.BIND_DELEGATE_LOCAL to intArrayOf(cmd.delegateSlot, cmd.nameId, cmd.accessId, cmd.dst)
|
||||||
|
is CmdLoadObjAddr -> Opcode.LOAD_OBJ_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
|
is CmdStoreObjAddr -> Opcode.STORE_OBJ_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||||
|
is CmdLoadIntAddr -> Opcode.LOAD_INT_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
|
is CmdStoreIntAddr -> Opcode.STORE_INT_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||||
|
is CmdLoadRealAddr -> Opcode.LOAD_REAL_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
|
is CmdStoreRealAddr -> Opcode.STORE_REAL_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||||
|
is CmdLoadBoolAddr -> Opcode.LOAD_BOOL_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
|
is CmdStoreBoolAddr -> Opcode.STORE_BOOL_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||||
|
is CmdIntToReal -> Opcode.INT_TO_REAL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdIntToRealLocal -> Opcode.INT_TO_REAL to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdRealToInt -> Opcode.REAL_TO_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdRealToIntLocal -> Opcode.REAL_TO_INT to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdBoolToInt -> Opcode.BOOL_TO_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdBoolToIntLocal -> Opcode.BOOL_TO_INT to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdIntToBool -> Opcode.INT_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdIntToBoolLocal -> Opcode.INT_TO_BOOL to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdAddInt -> Opcode.ADD_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdAddIntLocal -> Opcode.ADD_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdSubInt -> Opcode.SUB_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdSubIntLocal -> Opcode.SUB_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdMulInt -> Opcode.MUL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdMulIntLocal -> Opcode.MUL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdDivInt -> Opcode.DIV_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdDivIntLocal -> Opcode.DIV_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdModInt -> Opcode.MOD_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdModIntLocal -> Opcode.MOD_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdNegInt -> Opcode.NEG_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdNegIntLocal -> Opcode.NEG_INT to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdIncInt -> Opcode.INC_INT to intArrayOf(cmd.slot)
|
||||||
|
is CmdIncIntLocal -> Opcode.INC_INT to intArrayOf(cmd.slot + fn.scopeSlotCount)
|
||||||
|
is CmdDecInt -> Opcode.DEC_INT to intArrayOf(cmd.slot)
|
||||||
|
is CmdDecIntLocal -> Opcode.DEC_INT to intArrayOf(cmd.slot + fn.scopeSlotCount)
|
||||||
|
is CmdAddReal -> Opcode.ADD_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdAddRealLocal -> Opcode.ADD_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdSubReal -> Opcode.SUB_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdSubRealLocal -> Opcode.SUB_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdMulReal -> Opcode.MUL_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdMulRealLocal -> Opcode.MUL_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdDivReal -> Opcode.DIV_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdDivRealLocal -> Opcode.DIV_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdNegReal -> Opcode.NEG_REAL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdNegRealLocal -> Opcode.NEG_REAL to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdAndInt -> Opcode.AND_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdAndIntLocal -> Opcode.AND_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdOrInt -> Opcode.OR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdOrIntLocal -> Opcode.OR_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdXorInt -> Opcode.XOR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdXorIntLocal -> Opcode.XOR_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdShlInt -> Opcode.SHL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdShlIntLocal -> Opcode.SHL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdShrInt -> Opcode.SHR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdShrIntLocal -> Opcode.SHR_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdUshrInt -> Opcode.USHR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdUshrIntLocal -> Opcode.USHR_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdInvInt -> Opcode.INV_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdInvIntLocal -> Opcode.INV_INT to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpEqInt -> Opcode.CMP_EQ_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqIntLocal -> Opcode.CMP_EQ_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpNeqInt -> Opcode.CMP_NEQ_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqIntLocal -> Opcode.CMP_NEQ_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLtInt -> Opcode.CMP_LT_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtIntLocal -> Opcode.CMP_LT_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLteInt -> Opcode.CMP_LTE_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteIntLocal -> Opcode.CMP_LTE_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGtInt -> Opcode.CMP_GT_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtIntLocal -> Opcode.CMP_GT_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGteInt -> Opcode.CMP_GTE_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteIntLocal -> Opcode.CMP_GTE_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpEqReal -> Opcode.CMP_EQ_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqRealLocal -> Opcode.CMP_EQ_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpNeqReal -> Opcode.CMP_NEQ_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqRealLocal -> Opcode.CMP_NEQ_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLtReal -> Opcode.CMP_LT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtRealLocal -> Opcode.CMP_LT_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLteReal -> Opcode.CMP_LTE_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteRealLocal -> Opcode.CMP_LTE_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGtReal -> Opcode.CMP_GT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtRealLocal -> Opcode.CMP_GT_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGteReal -> Opcode.CMP_GTE_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteRealLocal -> Opcode.CMP_GTE_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpEqBool -> Opcode.CMP_EQ_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqBoolLocal -> Opcode.CMP_EQ_BOOL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpNeqBool -> Opcode.CMP_NEQ_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqBoolLocal -> Opcode.CMP_NEQ_BOOL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpEqIntReal -> Opcode.CMP_EQ_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqIntRealLocal -> Opcode.CMP_EQ_INT_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpEqRealInt -> Opcode.CMP_EQ_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqRealIntLocal -> Opcode.CMP_EQ_REAL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLtIntReal -> Opcode.CMP_LT_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtIntRealLocal -> Opcode.CMP_LT_INT_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLtRealInt -> Opcode.CMP_LT_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtRealIntLocal -> Opcode.CMP_LT_REAL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLteIntReal -> Opcode.CMP_LTE_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteIntRealLocal -> Opcode.CMP_LTE_INT_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLteRealInt -> Opcode.CMP_LTE_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteRealIntLocal -> Opcode.CMP_LTE_REAL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGtIntReal -> Opcode.CMP_GT_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtIntRealLocal -> Opcode.CMP_GT_INT_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGtRealInt -> Opcode.CMP_GT_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtRealIntLocal -> Opcode.CMP_GT_REAL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGteIntReal -> Opcode.CMP_GTE_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteIntRealLocal -> Opcode.CMP_GTE_INT_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGteRealInt -> Opcode.CMP_GTE_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteRealIntLocal -> Opcode.CMP_GTE_REAL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpNeqIntReal -> Opcode.CMP_NEQ_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqIntRealLocal -> Opcode.CMP_NEQ_INT_REAL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpNeqRealInt -> Opcode.CMP_NEQ_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqRealIntLocal -> Opcode.CMP_NEQ_REAL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdJmpIfEqInt -> Opcode.JMP_IF_EQ_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||||
|
is CmdJmpIfEqIntLocal -> Opcode.JMP_IF_EQ_INT to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.target
|
||||||
|
)
|
||||||
|
is CmdJmpIfNeqInt -> Opcode.JMP_IF_NEQ_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||||
|
is CmdJmpIfNeqIntLocal -> Opcode.JMP_IF_NEQ_INT to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.target
|
||||||
|
)
|
||||||
|
is CmdJmpIfLtInt -> Opcode.JMP_IF_LT_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||||
|
is CmdJmpIfLtIntLocal -> Opcode.JMP_IF_LT_INT to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.target
|
||||||
|
)
|
||||||
|
is CmdJmpIfLteInt -> Opcode.JMP_IF_LTE_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||||
|
is CmdJmpIfLteIntLocal -> Opcode.JMP_IF_LTE_INT to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.target
|
||||||
|
)
|
||||||
|
is CmdJmpIfGtInt -> Opcode.JMP_IF_GT_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||||
|
is CmdJmpIfGtIntLocal -> Opcode.JMP_IF_GT_INT to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.target
|
||||||
|
)
|
||||||
|
is CmdJmpIfGteInt -> Opcode.JMP_IF_GTE_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||||
|
is CmdJmpIfGteIntLocal -> Opcode.JMP_IF_GTE_INT to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.target
|
||||||
|
)
|
||||||
|
is CmdCmpEqObj -> Opcode.CMP_EQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqObj -> Opcode.CMP_NEQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpRefEqObj -> Opcode.CMP_REF_EQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpRefNeqObj -> Opcode.CMP_REF_NEQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqStr -> Opcode.CMP_EQ_STR to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqStrLocal -> Opcode.CMP_EQ_STR to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpNeqStr -> Opcode.CMP_NEQ_STR to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqStrLocal -> Opcode.CMP_NEQ_STR to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpLtStr -> Opcode.CMP_LT_STR to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtStrLocal -> Opcode.CMP_LT_STR to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpLteStr -> Opcode.CMP_LTE_STR to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteStrLocal -> Opcode.CMP_LTE_STR to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpGtStr -> Opcode.CMP_GT_STR to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtStrLocal -> Opcode.CMP_GT_STR to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpGteStr -> Opcode.CMP_GTE_STR to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteStrLocal -> Opcode.CMP_GTE_STR to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpEqIntObj -> Opcode.CMP_EQ_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqIntObjLocal -> Opcode.CMP_EQ_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpNeqIntObj -> Opcode.CMP_NEQ_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqIntObjLocal -> Opcode.CMP_NEQ_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpLtIntObj -> Opcode.CMP_LT_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtIntObjLocal -> Opcode.CMP_LT_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpLteIntObj -> Opcode.CMP_LTE_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteIntObjLocal -> Opcode.CMP_LTE_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpGtIntObj -> Opcode.CMP_GT_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtIntObjLocal -> Opcode.CMP_GT_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpGteIntObj -> Opcode.CMP_GTE_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteIntObjLocal -> Opcode.CMP_GTE_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpEqRealObj -> Opcode.CMP_EQ_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqRealObjLocal -> Opcode.CMP_EQ_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpNeqRealObj -> Opcode.CMP_NEQ_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqRealObjLocal -> Opcode.CMP_NEQ_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpLtRealObj -> Opcode.CMP_LT_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtRealObjLocal -> Opcode.CMP_LT_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpLteRealObj -> Opcode.CMP_LTE_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteRealObjLocal -> Opcode.CMP_LTE_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpGtRealObj -> Opcode.CMP_GT_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtRealObjLocal -> Opcode.CMP_GT_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpGteRealObj -> Opcode.CMP_GTE_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteRealObjLocal -> Opcode.CMP_GTE_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdNotBool -> Opcode.NOT_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdNotBoolLocal -> Opcode.NOT_BOOL to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdAndBool -> Opcode.AND_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdAndBoolLocal -> Opcode.AND_BOOL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdOrBool -> Opcode.OR_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdOrBoolLocal -> Opcode.OR_BOOL to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdUnboxIntObj -> Opcode.UNBOX_INT_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdUnboxIntObjLocal -> Opcode.UNBOX_INT_OBJ to intArrayOf(
|
||||||
|
cmd.src + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdUnboxRealObj -> Opcode.UNBOX_REAL_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdUnboxRealObjLocal -> Opcode.UNBOX_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.src + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdCmpLtObj -> Opcode.CMP_LT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteObj -> Opcode.CMP_LTE_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtObj -> Opcode.CMP_GT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteObj -> Opcode.CMP_GTE_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdAddIntObj -> Opcode.ADD_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdAddIntObjLocal -> Opcode.ADD_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdSubIntObj -> Opcode.SUB_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdSubIntObjLocal -> Opcode.SUB_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdMulIntObj -> Opcode.MUL_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdMulIntObjLocal -> Opcode.MUL_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdDivIntObj -> Opcode.DIV_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdDivIntObjLocal -> Opcode.DIV_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdModIntObj -> Opcode.MOD_INT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdModIntObjLocal -> Opcode.MOD_INT_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdAddRealObj -> Opcode.ADD_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdAddRealObjLocal -> Opcode.ADD_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdSubRealObj -> Opcode.SUB_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdSubRealObjLocal -> Opcode.SUB_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdMulRealObj -> Opcode.MUL_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdMulRealObjLocal -> Opcode.MUL_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdDivRealObj -> Opcode.DIV_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdDivRealObjLocal -> Opcode.DIV_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdModRealObj -> Opcode.MOD_REAL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdModRealObjLocal -> Opcode.MOD_REAL_OBJ to intArrayOf(
|
||||||
|
cmd.a + fn.scopeSlotCount,
|
||||||
|
cmd.b + fn.scopeSlotCount,
|
||||||
|
cmd.dst + fn.scopeSlotCount
|
||||||
|
)
|
||||||
|
is CmdAddObj -> Opcode.ADD_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdSubObj -> Opcode.SUB_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdMulObj -> Opcode.MUL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdDivObj -> Opcode.DIV_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdModObj -> Opcode.MOD_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdContainsObj -> Opcode.CONTAINS_OBJ to intArrayOf(cmd.target, cmd.value, cmd.dst)
|
||||||
|
is CmdAssignOpObj -> Opcode.ASSIGN_OP_OBJ to intArrayOf(cmd.opId, cmd.targetSlot, cmd.valueSlot, cmd.dst, cmd.nameId)
|
||||||
|
is CmdJmp -> Opcode.JMP to intArrayOf(cmd.target)
|
||||||
|
is CmdJmpIfTrue -> Opcode.JMP_IF_TRUE to intArrayOf(cmd.cond, cmd.target)
|
||||||
|
is CmdJmpIfTrueLocal -> Opcode.JMP_IF_TRUE to intArrayOf(cmd.cond + fn.scopeSlotCount, cmd.target)
|
||||||
|
is CmdJmpIfFalse -> Opcode.JMP_IF_FALSE to intArrayOf(cmd.cond, cmd.target)
|
||||||
|
is CmdJmpIfFalseLocal -> Opcode.JMP_IF_FALSE to intArrayOf(cmd.cond + fn.scopeSlotCount, cmd.target)
|
||||||
|
is CmdRet -> Opcode.RET to intArrayOf(cmd.slot)
|
||||||
|
is CmdRetLabel -> Opcode.RET_LABEL to intArrayOf(cmd.labelId, cmd.slot)
|
||||||
|
is CmdRetVoid -> Opcode.RET_VOID to intArrayOf()
|
||||||
|
is CmdThrow -> Opcode.THROW to intArrayOf(cmd.posId, cmd.slot)
|
||||||
|
is CmdRethrowPending -> Opcode.RETHROW_PENDING to intArrayOf()
|
||||||
|
is CmdPushScope -> Opcode.PUSH_SCOPE to intArrayOf(cmd.planId)
|
||||||
|
is CmdPopScope -> Opcode.POP_SCOPE to intArrayOf()
|
||||||
|
is CmdPushSlotPlan -> Opcode.PUSH_SLOT_PLAN to intArrayOf(cmd.planId)
|
||||||
|
is CmdPopSlotPlan -> Opcode.POP_SLOT_PLAN to intArrayOf()
|
||||||
|
is CmdPushTry -> Opcode.PUSH_TRY to intArrayOf(cmd.exceptionSlot, cmd.catchIp, cmd.finallyIp)
|
||||||
|
is CmdPopTry -> Opcode.POP_TRY to intArrayOf()
|
||||||
|
is CmdClearPendingThrowable -> Opcode.CLEAR_PENDING_THROWABLE to intArrayOf()
|
||||||
|
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclEnum -> Opcode.DECL_ENUM to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclFunction -> Opcode.DECL_FUNCTION to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclClass -> Opcode.DECL_CLASS to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclClassField -> Opcode.DECL_CLASS_FIELD to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclClassDelegated -> Opcode.DECL_CLASS_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclClassInstanceInit -> Opcode.DECL_CLASS_INSTANCE_INIT to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclClassInstanceField -> Opcode.DECL_CLASS_INSTANCE_FIELD to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclClassInstanceProperty -> Opcode.DECL_CLASS_INSTANCE_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclClassInstanceDelegated -> Opcode.DECL_CLASS_INSTANCE_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclInstanceField -> Opcode.DECL_INSTANCE_FIELD to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclInstanceProperty -> Opcode.DECL_INSTANCE_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclInstanceDelegated -> Opcode.DECL_INSTANCE_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdCallBridgeSlot -> Opcode.CALL_BRIDGE_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdCallDynamicMember -> Opcode.CALL_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
|
||||||
|
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
|
||||||
|
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
|
||||||
|
is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst)
|
||||||
|
is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
|
||||||
|
is CmdGetClassScope -> Opcode.GET_CLASS_SCOPE to intArrayOf(cmd.classSlot, cmd.nameId, cmd.dst)
|
||||||
|
is CmdSetClassScope -> Opcode.SET_CLASS_SCOPE to intArrayOf(cmd.classSlot, cmd.nameId, cmd.valueSlot)
|
||||||
|
is CmdGetDynamicMember -> Opcode.GET_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.dst)
|
||||||
|
is CmdSetDynamicMember -> Opcode.SET_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.valueSlot)
|
||||||
|
is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot)
|
||||||
|
is CmdIterPop -> Opcode.ITER_POP to intArrayOf()
|
||||||
|
is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf()
|
||||||
|
else -> error("Unsupported cmd in disassembler: ${cmd::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class OperandKind {
|
||||||
|
SLOT,
|
||||||
|
ADDR,
|
||||||
|
CONST,
|
||||||
|
IP,
|
||||||
|
COUNT,
|
||||||
|
ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||||
|
return when (op) {
|
||||||
|
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN, Opcode.POP_TRY,
|
||||||
|
Opcode.CLEAR_PENDING_THROWABLE, Opcode.RETHROW_PENDING,
|
||||||
|
Opcode.ITER_POP, Opcode.ITER_CANCEL -> emptyList()
|
||||||
|
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
||||||
|
Opcode.UNBOX_INT_OBJ, Opcode.UNBOX_REAL_OBJ,
|
||||||
|
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
||||||
|
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.OBJ_TO_BOOL, Opcode.GET_OBJ_CLASS, Opcode.ASSERT_IS ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.CHECK_IS, Opcode.MAKE_QUALIFIED_VIEW ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.RANGE_INT_BOUNDS ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.MAKE_RANGE ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.RET_LABEL, Opcode.THROW ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.RESOLVE_SCOPE_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.DELEGATED_GET_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.DELEGATED_SET_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.BIND_DELEGATE_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR ->
|
||||||
|
listOf(OperandKind.ADDR, OperandKind.SLOT)
|
||||||
|
Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.CONST_NULL ->
|
||||||
|
listOf(OperandKind.SLOT)
|
||||||
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL,
|
||||||
|
Opcode.MAKE_LAMBDA_FN ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
|
listOf(OperandKind.CONST)
|
||||||
|
Opcode.PUSH_TRY ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
||||||
|
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
||||||
|
Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS, Opcode.DECL_CLASS_FIELD,
|
||||||
|
Opcode.DECL_CLASS_DELEGATED, Opcode.DECL_CLASS_INSTANCE_INIT, Opcode.DECL_CLASS_INSTANCE_FIELD,
|
||||||
|
Opcode.DECL_CLASS_INSTANCE_PROPERTY, Opcode.DECL_CLASS_INSTANCE_DELEGATED, Opcode.DECL_INSTANCE_FIELD,
|
||||||
|
Opcode.DECL_INSTANCE_PROPERTY, Opcode.DECL_INSTANCE_DELEGATED,
|
||||||
|
Opcode.ASSIGN_DESTRUCTURE ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||||
|
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
||||||
|
Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
||||||
|
Opcode.CMP_EQ_INT, Opcode.CMP_NEQ_INT, Opcode.CMP_LT_INT, Opcode.CMP_LTE_INT,
|
||||||
|
Opcode.CMP_GT_INT, Opcode.CMP_GTE_INT,
|
||||||
|
Opcode.CMP_EQ_REAL, Opcode.CMP_NEQ_REAL, Opcode.CMP_LT_REAL, Opcode.CMP_LTE_REAL,
|
||||||
|
Opcode.CMP_GT_REAL, Opcode.CMP_GTE_REAL,
|
||||||
|
Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL,
|
||||||
|
Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT,
|
||||||
|
Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT,
|
||||||
|
Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT,
|
||||||
|
Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ,
|
||||||
|
Opcode.CMP_EQ_STR, Opcode.CMP_NEQ_STR, Opcode.CMP_LT_STR, Opcode.CMP_LTE_STR,
|
||||||
|
Opcode.CMP_GT_STR, Opcode.CMP_GTE_STR,
|
||||||
|
Opcode.CMP_EQ_INT_OBJ, Opcode.CMP_NEQ_INT_OBJ, Opcode.CMP_LT_INT_OBJ, Opcode.CMP_LTE_INT_OBJ,
|
||||||
|
Opcode.CMP_GT_INT_OBJ, Opcode.CMP_GTE_INT_OBJ, Opcode.CMP_EQ_REAL_OBJ, Opcode.CMP_NEQ_REAL_OBJ,
|
||||||
|
Opcode.CMP_LT_REAL_OBJ, Opcode.CMP_LTE_REAL_OBJ, Opcode.CMP_GT_REAL_OBJ, Opcode.CMP_GTE_REAL_OBJ,
|
||||||
|
Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ,
|
||||||
|
Opcode.ADD_INT_OBJ, Opcode.SUB_INT_OBJ, Opcode.MUL_INT_OBJ, Opcode.DIV_INT_OBJ, Opcode.MOD_INT_OBJ,
|
||||||
|
Opcode.ADD_REAL_OBJ, Opcode.SUB_REAL_OBJ, Opcode.MUL_REAL_OBJ, Opcode.DIV_REAL_OBJ, Opcode.MOD_REAL_OBJ,
|
||||||
|
Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, Opcode.CONTAINS_OBJ,
|
||||||
|
Opcode.AND_BOOL, Opcode.OR_BOOL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.ASSIGN_OP_OBJ ->
|
||||||
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
|
||||||
|
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH, Opcode.LOAD_THIS ->
|
||||||
|
listOf(OperandKind.SLOT)
|
||||||
|
Opcode.LOAD_THIS_VARIANT ->
|
||||||
|
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||||
|
Opcode.JMP ->
|
||||||
|
listOf(OperandKind.IP)
|
||||||
|
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||||
|
Opcode.JMP_IF_EQ_INT, Opcode.JMP_IF_NEQ_INT,
|
||||||
|
Opcode.JMP_IF_LT_INT, Opcode.JMP_IF_LTE_INT,
|
||||||
|
Opcode.JMP_IF_GT_INT, Opcode.JMP_IF_GTE_INT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.IP)
|
||||||
|
Opcode.CALL_DIRECT ->
|
||||||
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.CALL_SLOT, Opcode.CALL_BRIDGE_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.CALL_MEMBER_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.CALL_DYNAMIC_MEMBER ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.GET_INDEX ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.SET_INDEX ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.LIST_LITERAL ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
Opcode.GET_MEMBER_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||||
|
Opcode.SET_MEMBER_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||||
|
Opcode.GET_CLASS_SCOPE ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.SET_CLASS_SCOPE ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.GET_DYNAMIC_MEMBER ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.SET_DYNAMIC_MEMBER ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
data class CmdFunction(
|
||||||
|
val name: String,
|
||||||
|
val localCount: Int,
|
||||||
|
val addrCount: Int,
|
||||||
|
val returnLabels: Set<String>,
|
||||||
|
val scopeSlotCount: Int,
|
||||||
|
val scopeSlotIndices: IntArray,
|
||||||
|
val scopeSlotNames: Array<String?>,
|
||||||
|
val scopeSlotIsModule: BooleanArray,
|
||||||
|
val localSlotNames: Array<String?>,
|
||||||
|
val localSlotMutables: BooleanArray,
|
||||||
|
val localSlotDelegated: BooleanArray,
|
||||||
|
val localSlotCaptures: BooleanArray,
|
||||||
|
val constants: List<BytecodeConst>,
|
||||||
|
val cmds: Array<Cmd>,
|
||||||
|
val posByIp: Array<net.sergeych.lyng.Pos?>,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" }
|
||||||
|
require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames size mismatch" }
|
||||||
|
require(scopeSlotIsModule.size == scopeSlotCount) { "scopeSlotIsModule size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotDelegated.size) { "localSlot delegation size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotCaptures.size) { "localSlot capture size mismatch" }
|
||||||
|
require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" }
|
||||||
|
require(addrCount >= 0) { "addrCount must be non-negative" }
|
||||||
|
if (posByIp.isNotEmpty()) {
|
||||||
|
require(posByIp.size == cmds.size) { "posByIp size mismatch" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun localSlotPlanByName(): Map<String, Int> {
|
||||||
|
val result = LinkedHashMap<String, Int>()
|
||||||
|
for (i in localSlotNames.indices) {
|
||||||
|
val name = localSlotNames[i] ?: continue
|
||||||
|
val existing = result[name]
|
||||||
|
if (existing == null) {
|
||||||
|
result[name] = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val existingIsCapture = localSlotCaptures.getOrNull(existing) == true
|
||||||
|
val currentIsCapture = localSlotCaptures.getOrNull(i) == true
|
||||||
|
if (existingIsCapture && !currentIsCapture) {
|
||||||
|
result[name] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
data class ForcedLocalSlotInfo(
|
||||||
|
val index: Int,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val isDelegated: Boolean,
|
||||||
|
)
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
enum class CaptureOwnerFrameKind { MODULE, LOCAL }
|
||||||
|
|
||||||
|
data class LambdaCaptureEntry(
|
||||||
|
val ownerKind: CaptureOwnerFrameKind,
|
||||||
|
val ownerScopeId: Int,
|
||||||
|
val ownerSlotId: Int,
|
||||||
|
val ownerName: String,
|
||||||
|
val ownerIsMutable: Boolean,
|
||||||
|
val ownerIsDelegated: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BytecodeCaptureEntry(
|
||||||
|
val ownerKind: CaptureOwnerFrameKind,
|
||||||
|
val ownerScopeId: Int,
|
||||||
|
val ownerSlotId: Int,
|
||||||
|
val slotIndex: Int,
|
||||||
|
)
|
||||||
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
enum class Opcode(val code: Int) {
|
||||||
|
NOP(0x00),
|
||||||
|
MOVE_OBJ(0x01),
|
||||||
|
MOVE_INT(0x02),
|
||||||
|
MOVE_REAL(0x03),
|
||||||
|
MOVE_BOOL(0x04),
|
||||||
|
CONST_OBJ(0x05),
|
||||||
|
CONST_INT(0x06),
|
||||||
|
CONST_REAL(0x07),
|
||||||
|
CONST_BOOL(0x08),
|
||||||
|
CONST_NULL(0x09),
|
||||||
|
BOX_OBJ(0x0A),
|
||||||
|
RANGE_INT_BOUNDS(0x0B),
|
||||||
|
MAKE_RANGE(0x0C),
|
||||||
|
LOAD_THIS(0x0D),
|
||||||
|
LOAD_THIS_VARIANT(0x0F),
|
||||||
|
|
||||||
|
INT_TO_REAL(0x10),
|
||||||
|
REAL_TO_INT(0x11),
|
||||||
|
BOOL_TO_INT(0x12),
|
||||||
|
INT_TO_BOOL(0x13),
|
||||||
|
OBJ_TO_BOOL(0x14),
|
||||||
|
CHECK_IS(0x15),
|
||||||
|
ASSERT_IS(0x16),
|
||||||
|
MAKE_QUALIFIED_VIEW(0x17),
|
||||||
|
MAKE_LAMBDA_FN(0x18),
|
||||||
|
GET_OBJ_CLASS(0x19),
|
||||||
|
|
||||||
|
ADD_INT(0x20),
|
||||||
|
SUB_INT(0x21),
|
||||||
|
MUL_INT(0x22),
|
||||||
|
DIV_INT(0x23),
|
||||||
|
MOD_INT(0x24),
|
||||||
|
NEG_INT(0x25),
|
||||||
|
INC_INT(0x26),
|
||||||
|
DEC_INT(0x27),
|
||||||
|
|
||||||
|
ADD_REAL(0x30),
|
||||||
|
SUB_REAL(0x31),
|
||||||
|
MUL_REAL(0x32),
|
||||||
|
DIV_REAL(0x33),
|
||||||
|
NEG_REAL(0x34),
|
||||||
|
|
||||||
|
AND_INT(0x40),
|
||||||
|
OR_INT(0x41),
|
||||||
|
XOR_INT(0x42),
|
||||||
|
SHL_INT(0x43),
|
||||||
|
SHR_INT(0x44),
|
||||||
|
USHR_INT(0x45),
|
||||||
|
INV_INT(0x46),
|
||||||
|
|
||||||
|
CMP_EQ_INT(0x50),
|
||||||
|
CMP_NEQ_INT(0x51),
|
||||||
|
CMP_LT_INT(0x52),
|
||||||
|
CMP_LTE_INT(0x53),
|
||||||
|
CMP_GT_INT(0x54),
|
||||||
|
CMP_GTE_INT(0x55),
|
||||||
|
CMP_EQ_REAL(0x56),
|
||||||
|
CMP_NEQ_REAL(0x57),
|
||||||
|
CMP_LT_REAL(0x58),
|
||||||
|
CMP_LTE_REAL(0x59),
|
||||||
|
CMP_GT_REAL(0x5A),
|
||||||
|
CMP_GTE_REAL(0x5B),
|
||||||
|
CMP_EQ_BOOL(0x5C),
|
||||||
|
CMP_NEQ_BOOL(0x5D),
|
||||||
|
|
||||||
|
CMP_EQ_INT_REAL(0x60),
|
||||||
|
CMP_EQ_REAL_INT(0x61),
|
||||||
|
CMP_LT_INT_REAL(0x62),
|
||||||
|
CMP_LT_REAL_INT(0x63),
|
||||||
|
CMP_LTE_INT_REAL(0x64),
|
||||||
|
CMP_LTE_REAL_INT(0x65),
|
||||||
|
CMP_GT_INT_REAL(0x66),
|
||||||
|
CMP_GT_REAL_INT(0x67),
|
||||||
|
CMP_GTE_INT_REAL(0x68),
|
||||||
|
CMP_GTE_REAL_INT(0x69),
|
||||||
|
CMP_NEQ_INT_REAL(0x6A),
|
||||||
|
CMP_NEQ_REAL_INT(0x6B),
|
||||||
|
CMP_EQ_OBJ(0x6C),
|
||||||
|
CMP_NEQ_OBJ(0x6D),
|
||||||
|
CMP_REF_EQ_OBJ(0x6E),
|
||||||
|
CMP_REF_NEQ_OBJ(0x6F),
|
||||||
|
CMP_EQ_STR(0xD6),
|
||||||
|
CMP_NEQ_STR(0xD7),
|
||||||
|
CMP_LT_STR(0xD8),
|
||||||
|
CMP_LTE_STR(0xD9),
|
||||||
|
CMP_GT_STR(0xDA),
|
||||||
|
CMP_GTE_STR(0xDB),
|
||||||
|
CMP_EQ_INT_OBJ(0xDC),
|
||||||
|
CMP_NEQ_INT_OBJ(0xDD),
|
||||||
|
CMP_LT_INT_OBJ(0xDE),
|
||||||
|
CMP_LTE_INT_OBJ(0xDF),
|
||||||
|
CMP_GT_INT_OBJ(0xE0),
|
||||||
|
CMP_GTE_INT_OBJ(0xE1),
|
||||||
|
CMP_EQ_REAL_OBJ(0xE2),
|
||||||
|
CMP_NEQ_REAL_OBJ(0xE3),
|
||||||
|
CMP_LT_REAL_OBJ(0xE4),
|
||||||
|
CMP_LTE_REAL_OBJ(0xE5),
|
||||||
|
CMP_GT_REAL_OBJ(0xE6),
|
||||||
|
CMP_GTE_REAL_OBJ(0xE7),
|
||||||
|
UNBOX_INT_OBJ(0xF2),
|
||||||
|
UNBOX_REAL_OBJ(0xF3),
|
||||||
|
ADD_INT_OBJ(0xE8),
|
||||||
|
SUB_INT_OBJ(0xE9),
|
||||||
|
MUL_INT_OBJ(0xEA),
|
||||||
|
DIV_INT_OBJ(0xEB),
|
||||||
|
MOD_INT_OBJ(0xEC),
|
||||||
|
ADD_REAL_OBJ(0xED),
|
||||||
|
SUB_REAL_OBJ(0xEE),
|
||||||
|
MUL_REAL_OBJ(0xEF),
|
||||||
|
DIV_REAL_OBJ(0xF0),
|
||||||
|
MOD_REAL_OBJ(0xF1),
|
||||||
|
|
||||||
|
NOT_BOOL(0x70),
|
||||||
|
AND_BOOL(0x71),
|
||||||
|
OR_BOOL(0x72),
|
||||||
|
CMP_LT_OBJ(0x73),
|
||||||
|
CMP_LTE_OBJ(0x74),
|
||||||
|
CMP_GT_OBJ(0x75),
|
||||||
|
CMP_GTE_OBJ(0x76),
|
||||||
|
ADD_OBJ(0x77),
|
||||||
|
SUB_OBJ(0x78),
|
||||||
|
MUL_OBJ(0x79),
|
||||||
|
DIV_OBJ(0x7A),
|
||||||
|
MOD_OBJ(0x7B),
|
||||||
|
CONTAINS_OBJ(0x7C),
|
||||||
|
ASSIGN_OP_OBJ(0x7D),
|
||||||
|
|
||||||
|
JMP(0x80),
|
||||||
|
JMP_IF_TRUE(0x81),
|
||||||
|
JMP_IF_FALSE(0x82),
|
||||||
|
JMP_IF_EQ_INT(0xD0),
|
||||||
|
JMP_IF_NEQ_INT(0xD1),
|
||||||
|
JMP_IF_LT_INT(0xD2),
|
||||||
|
JMP_IF_LTE_INT(0xD3),
|
||||||
|
JMP_IF_GT_INT(0xD4),
|
||||||
|
JMP_IF_GTE_INT(0xD5),
|
||||||
|
RET(0x83),
|
||||||
|
RET_VOID(0x84),
|
||||||
|
RET_LABEL(0xBA),
|
||||||
|
PUSH_SCOPE(0x85),
|
||||||
|
POP_SCOPE(0x86),
|
||||||
|
PUSH_SLOT_PLAN(0x87),
|
||||||
|
POP_SLOT_PLAN(0x88),
|
||||||
|
DECL_LOCAL(0x89),
|
||||||
|
DECL_EXT_PROPERTY(0x8A),
|
||||||
|
DECL_DELEGATED(0x8B),
|
||||||
|
DECL_DESTRUCTURE(0x8C),
|
||||||
|
PUSH_TRY(0x8D),
|
||||||
|
POP_TRY(0x8E),
|
||||||
|
CLEAR_PENDING_THROWABLE(0x8F),
|
||||||
|
|
||||||
|
CALL_DIRECT(0x90),
|
||||||
|
ASSIGN_DESTRUCTURE(0x91),
|
||||||
|
CALL_MEMBER_SLOT(0x92),
|
||||||
|
CALL_SLOT(0x93),
|
||||||
|
CALL_BRIDGE_SLOT(0x94),
|
||||||
|
|
||||||
|
GET_INDEX(0xA2),
|
||||||
|
SET_INDEX(0xA3),
|
||||||
|
LIST_LITERAL(0xA5),
|
||||||
|
GET_MEMBER_SLOT(0xA8),
|
||||||
|
SET_MEMBER_SLOT(0xA9),
|
||||||
|
GET_CLASS_SCOPE(0xAA),
|
||||||
|
SET_CLASS_SCOPE(0xAB),
|
||||||
|
GET_DYNAMIC_MEMBER(0xAC),
|
||||||
|
SET_DYNAMIC_MEMBER(0xAD),
|
||||||
|
CALL_DYNAMIC_MEMBER(0xAE),
|
||||||
|
|
||||||
|
RESOLVE_SCOPE_SLOT(0xB1),
|
||||||
|
LOAD_OBJ_ADDR(0xB2),
|
||||||
|
STORE_OBJ_ADDR(0xB3),
|
||||||
|
LOAD_INT_ADDR(0xB4),
|
||||||
|
STORE_INT_ADDR(0xB5),
|
||||||
|
LOAD_REAL_ADDR(0xB6),
|
||||||
|
STORE_REAL_ADDR(0xB7),
|
||||||
|
LOAD_BOOL_ADDR(0xB8),
|
||||||
|
STORE_BOOL_ADDR(0xB9),
|
||||||
|
THROW(0xBB),
|
||||||
|
RETHROW_PENDING(0xBC),
|
||||||
|
DECL_ENUM(0xBE),
|
||||||
|
ITER_PUSH(0xBF),
|
||||||
|
ITER_POP(0xC0),
|
||||||
|
ITER_CANCEL(0xC1),
|
||||||
|
DELEGATED_GET_LOCAL(0xC2),
|
||||||
|
DELEGATED_SET_LOCAL(0xC3),
|
||||||
|
BIND_DELEGATE_LOCAL(0xC4),
|
||||||
|
DECL_FUNCTION(0xC5),
|
||||||
|
DECL_CLASS(0xC6),
|
||||||
|
DECL_CLASS_FIELD(0xC7),
|
||||||
|
DECL_CLASS_DELEGATED(0xC8),
|
||||||
|
DECL_CLASS_INSTANCE_INIT(0xC9),
|
||||||
|
DECL_CLASS_INSTANCE_FIELD(0xCA),
|
||||||
|
DECL_CLASS_INSTANCE_PROPERTY(0xCB),
|
||||||
|
DECL_CLASS_INSTANCE_DELEGATED(0xCC),
|
||||||
|
DECL_INSTANCE_FIELD(0xCD),
|
||||||
|
DECL_INSTANCE_PROPERTY(0xCE),
|
||||||
|
DECL_INSTANCE_DELEGATED(0xCF),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val byCode: Map<Int, Opcode> = values().associateBy { it.code }
|
||||||
|
fun fromCode(code: Int): Opcode? = byCode[code]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
|
internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
||||||
|
val localNames = frame.fn.localSlotNames
|
||||||
|
if (localNames.isEmpty()) return
|
||||||
|
val base = frame.fn.scopeSlotCount
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
val slotType = frame.getLocalSlotTypeCode(i)
|
||||||
|
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) continue
|
||||||
|
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) continue
|
||||||
|
val record = scope.getLocalRecordDirect(name)
|
||||||
|
?: scope.chainLookupIgnoreClosure(name, followClosure = true)
|
||||||
|
?: continue
|
||||||
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
|
scope.resolve(record, name)
|
||||||
|
} else {
|
||||||
|
record.value
|
||||||
|
}
|
||||||
|
if (value is net.sergeych.lyng.FrameSlotRef && value.refersTo(frame.frame, base + i)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
frame.setObjUnchecked(base + i, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* 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.bytecode
|
||||||
|
|
||||||
|
enum class SlotType(val code: Byte) {
|
||||||
|
UNKNOWN(0),
|
||||||
|
OBJ(1),
|
||||||
|
INT(2),
|
||||||
|
REAL(3),
|
||||||
|
BOOL(4),
|
||||||
|
}
|
||||||
@ -41,9 +41,9 @@ private val fallbackKeywordIds = setOf(
|
|||||||
// boolean operators
|
// boolean operators
|
||||||
"and", "or", "not",
|
"and", "or", "not",
|
||||||
// declarations & modifiers
|
// declarations & modifiers
|
||||||
"fun", "fn", "class", "interface", "enum", "val", "var", "import", "package",
|
"fun", "fn", "class", "interface", "enum", "val", "var", "type", "import", "package",
|
||||||
"abstract", "closed", "override", "public", "lazy", "dynamic",
|
"abstract", "closed", "override", "public", "lazy", "dynamic",
|
||||||
"private", "protected", "static", "open", "extern", "init", "get", "set", "by",
|
"private", "protected", "static", "open", "extern", "init", "get", "set", "by", "step",
|
||||||
// control flow and misc
|
// control flow and misc
|
||||||
"if", "else", "when", "while", "do", "for", "try", "catch", "finally",
|
"if", "else", "when", "while", "do", "for", "try", "catch", "finally",
|
||||||
"throw", "return", "break", "continue", "this", "null", "true", "false", "unset"
|
"throw", "return", "break", "continue", "this", "null", "true", "false", "unset"
|
||||||
@ -74,7 +74,7 @@ private fun kindOf(type: Type, value: String): HighlightKind? = when (type) {
|
|||||||
Type.COMMA, Type.SEMICOLON, Type.COLON -> HighlightKind.Punctuation
|
Type.COMMA, Type.SEMICOLON, Type.COLON -> HighlightKind.Punctuation
|
||||||
|
|
||||||
// textual control keywords
|
// textual control keywords
|
||||||
Type.IN, Type.NOTIN, Type.IS, Type.NOTIS, Type.AS, Type.ASNULL, Type.BY, Type.OBJECT,
|
Type.IN, Type.NOTIN, Type.IS, Type.NOTIS, Type.AS, Type.ASNULL, Type.BY, Type.STEP, Type.OBJECT,
|
||||||
Type.AND, Type.OR, Type.NOT -> HighlightKind.Keyword
|
Type.AND, Type.OR, Type.NOT -> HighlightKind.Keyword
|
||||||
|
|
||||||
// labels / annotations
|
// labels / annotations
|
||||||
@ -242,6 +242,7 @@ private fun applyEnumConstantHeuristics(
|
|||||||
var j = i + 1
|
var j = i + 1
|
||||||
// skip optional whitespace/newlines tokens are separate types, so we just check IDs and braces
|
// skip optional whitespace/newlines tokens are separate types, so we just check IDs and braces
|
||||||
if (j < tokens.size && tokens[j].type == Type.ID) j++ else { i++; continue }
|
if (j < tokens.size && tokens[j].type == Type.ID) j++ else { i++; continue }
|
||||||
|
if (j < tokens.size && tokens[j].type == Type.STAR) j++
|
||||||
if (j < tokens.size && tokens[j].type == Type.LBRACE) {
|
if (j < tokens.size && tokens[j].type == Type.LBRACE) {
|
||||||
j++
|
j++
|
||||||
while (j < tokens.size) {
|
while (j < tokens.size) {
|
||||||
|
|||||||
@ -102,6 +102,7 @@ object BuiltinDocRegistry : BuiltinDocSource {
|
|||||||
// Register built-in lazy seeds
|
// Register built-in lazy seeds
|
||||||
init {
|
init {
|
||||||
registerLazy("lyng.stdlib") { buildStdlibDocs() }
|
registerLazy("lyng.stdlib") { buildStdlibDocs() }
|
||||||
|
registerLazy("lyng.time") { buildTimeDocs() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -658,7 +659,32 @@ private fun buildStdlibDocs(): List<MiniDecl> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
decls += mod.build()
|
decls += mod.build()
|
||||||
|
|
||||||
return decls
|
return decls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildTimeDocs(): List<MiniDecl> {
|
||||||
|
val mod = ModuleDocsBuilder("lyng.time")
|
||||||
|
mod.classDoc(name = "Instant", doc = "Point in time (epoch-based).", bases = listOf(type("Obj"))) {
|
||||||
|
field(name = "distantFuture", doc = "An instant in the distant future.", type = type("lyng.Instant"), isStatic = true)
|
||||||
|
field(name = "distantPast", doc = "An instant in the distant past.", type = type("lyng.Instant"), isStatic = true)
|
||||||
|
method(name = "now", doc = "Return the current instant.", returns = type("lyng.Instant"), isStatic = true)
|
||||||
|
field(name = "epochSeconds", doc = "Seconds since Unix epoch.", type = type("lyng.Real"))
|
||||||
|
field(name = "epochWholeSeconds", doc = "Full seconds since Unix epoch.", type = type("lyng.Int"))
|
||||||
|
field(name = "nanosecondsOfSecond", doc = "Nanoseconds within the current second.", type = type("lyng.Int"))
|
||||||
|
method(name = "toRFC3339", doc = "Format as RFC3339 string.", returns = type("lyng.String"))
|
||||||
|
method(name = "toDateTime", doc = "Convert to localized DateTime.", params = listOf(ParamDoc("timeZone")), returns = type("lyng.DateTime"))
|
||||||
|
method(name = "truncateToMinute", doc = "Truncate to minute.", returns = type("lyng.Instant"))
|
||||||
|
method(name = "truncateToSecond", doc = "Truncate to second.", returns = type("lyng.Instant"))
|
||||||
|
method(name = "truncateToMillisecond", doc = "Truncate to millisecond.", returns = type("lyng.Instant"))
|
||||||
|
method(name = "truncateToMicrosecond", doc = "Truncate to microsecond.", returns = type("lyng.Instant"))
|
||||||
|
}
|
||||||
|
mod.classDoc(name = "DateTime", doc = "Localized date and time.", bases = listOf(type("Obj"))) {
|
||||||
|
method(name = "toInstant", doc = "Convert back to absolute Instant.", returns = type("lyng.Instant"))
|
||||||
|
}
|
||||||
|
mod.classDoc(name = "Duration", doc = "Time duration.", bases = listOf(type("Obj")))
|
||||||
|
mod.funDoc(name = "delay", doc = "Suspend execution.", params = listOf(ParamDoc("duration")))
|
||||||
|
return mod.build()
|
||||||
|
}
|
||||||
|
|
||||||
// (Registration for external modules is provided by their own libraries)
|
// (Registration for external modules is provided by their own libraries)
|
||||||
|
|||||||
@ -36,7 +36,7 @@ data class CompletionItem(
|
|||||||
val priority: Double = 0.0,
|
val priority: Double = 0.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class Kind { Function, Class_, Enum, Value, Method, Field }
|
enum class Kind { Function, Class_, Enum, TypeAlias, Value, Method, Field }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Platform-free, lenient import provider that never fails on unknown packages.
|
* Platform-free, lenient import provider that never fails on unknown packages.
|
||||||
@ -74,29 +74,30 @@ object CompletionEngineLight {
|
|||||||
val word = DocLookupUtils.wordRangeAt(text, caret)
|
val word = DocLookupUtils.wordRangeAt(text, caret)
|
||||||
val memberDot = DocLookupUtils.findDotLeft(text, word?.first ?: caret)
|
val memberDot = DocLookupUtils.findDotLeft(text, word?.first ?: caret)
|
||||||
if (memberDot != null) {
|
if (memberDot != null) {
|
||||||
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, memberDot, imported, binding)
|
||||||
val inferredCls = (DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding) ?: DocLookupUtils.guessReceiverClass(text, memberDot, imported, mini))
|
val inferredCls = (DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding) ?: DocLookupUtils.guessReceiverClass(text, memberDot, imported, mini))
|
||||||
// 0) Try chained member call return type inference
|
// 0) Try chained member call return type inference
|
||||||
DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding)?.let { cls ->
|
DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding)?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
DocLookupUtils.guessReturnClassFromMemberCallBefore(text, memberDot, imported, mini)?.let { cls ->
|
DocLookupUtils.guessReturnClassFromMemberCallBefore(text, memberDot, imported, mini)?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
// 0a) Top-level call before dot
|
// 0a) Top-level call before dot
|
||||||
DocLookupUtils.guessReturnClassFromTopLevelCallBefore(text, memberDot, imported, mini)?.let { cls ->
|
DocLookupUtils.guessReturnClassFromTopLevelCallBefore(text, memberDot, imported, mini)?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
// 0b) Across-known-callees (Iterable/Iterator/List preference)
|
// 0b) Across-known-callees (Iterable/Iterator/List preference)
|
||||||
DocLookupUtils.guessReturnClassAcrossKnownCallees(text, memberDot, imported, mini)?.let { cls ->
|
DocLookupUtils.guessReturnClassAcrossKnownCallees(text, memberDot, imported, mini)?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
// 1) Receiver inference fallback
|
// 1) Receiver inference fallback
|
||||||
(DocLookupUtils.guessReceiverClassViaMini(mini, text, memberDot, imported, binding) ?: DocLookupUtils.guessReceiverClass(text, memberDot, imported, mini))?.let { cls ->
|
(DocLookupUtils.guessReceiverClassViaMini(mini, text, memberDot, imported, binding) ?: DocLookupUtils.guessReceiverClass(text, memberDot, imported, mini))?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
// In member context and unknown receiver/return type: show nothing (no globals after dot)
|
// In member context and unknown receiver/return type: show nothing (no globals after dot)
|
||||||
@ -106,10 +107,20 @@ object CompletionEngineLight {
|
|||||||
// Global identifiers: params > local decls > imported > stdlib; Functions > Classes > Values; alphabetical
|
// Global identifiers: params > local decls > imported > stdlib; Functions > Classes > Values; alphabetical
|
||||||
offerParamsInScope(out, prefix, mini, text, caret)
|
offerParamsInScope(out, prefix, mini, text, caret)
|
||||||
|
|
||||||
val locals = DocLookupUtils.extractLocalsAt(text, caret)
|
val localsFromBinding = DocLookupUtils.collectLocalsFromBinding(mini, binding, caret)
|
||||||
for (name in locals) {
|
if (localsFromBinding.isNotEmpty()) {
|
||||||
if (name.startsWith(prefix, true)) {
|
for (sym in localsFromBinding) {
|
||||||
out.add(CompletionItem(name, Kind.Value, priority = 150.0))
|
if (sym.name.startsWith(prefix, true)) {
|
||||||
|
val t = sym.type?.let { ": $it" }
|
||||||
|
out.add(CompletionItem(sym.name, Kind.Value, typeText = t, priority = 150.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val locals = DocLookupUtils.extractLocalsAt(text, caret)
|
||||||
|
for (name in locals) {
|
||||||
|
if (name.startsWith(prefix, true)) {
|
||||||
|
out.add(CompletionItem(name, Kind.Value, priority = 150.0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,9 +129,11 @@ object CompletionEngineLight {
|
|||||||
val classes = decls.filterIsInstance<MiniClassDecl>().sortedBy { it.name.lowercase() }
|
val classes = decls.filterIsInstance<MiniClassDecl>().sortedBy { it.name.lowercase() }
|
||||||
val enums = decls.filterIsInstance<MiniEnumDecl>().sortedBy { it.name.lowercase() }
|
val enums = decls.filterIsInstance<MiniEnumDecl>().sortedBy { it.name.lowercase() }
|
||||||
val vals = decls.filterIsInstance<MiniValDecl>().sortedBy { it.name.lowercase() }
|
val vals = decls.filterIsInstance<MiniValDecl>().sortedBy { it.name.lowercase() }
|
||||||
|
val aliases = decls.filterIsInstance<MiniTypeAliasDecl>().sortedBy { it.name.lowercase() }
|
||||||
funs.forEach { offerDeclAdd(out, prefix, it) }
|
funs.forEach { offerDeclAdd(out, prefix, it) }
|
||||||
classes.forEach { offerDeclAdd(out, prefix, it) }
|
classes.forEach { offerDeclAdd(out, prefix, it) }
|
||||||
enums.forEach { offerDeclAdd(out, prefix, it) }
|
enums.forEach { offerDeclAdd(out, prefix, it) }
|
||||||
|
aliases.forEach { offerDeclAdd(out, prefix, it) }
|
||||||
vals.forEach { offerDeclAdd(out, prefix, it) }
|
vals.forEach { offerDeclAdd(out, prefix, it) }
|
||||||
|
|
||||||
// Imported and builtin
|
// Imported and builtin
|
||||||
@ -135,9 +148,11 @@ object CompletionEngineLight {
|
|||||||
val classes = decls.filterIsInstance<MiniClassDecl>().sortedBy { it.name.lowercase() }
|
val classes = decls.filterIsInstance<MiniClassDecl>().sortedBy { it.name.lowercase() }
|
||||||
val enums = decls.filterIsInstance<MiniEnumDecl>().sortedBy { it.name.lowercase() }
|
val enums = decls.filterIsInstance<MiniEnumDecl>().sortedBy { it.name.lowercase() }
|
||||||
val vals = decls.filterIsInstance<MiniValDecl>().sortedBy { it.name.lowercase() }
|
val vals = decls.filterIsInstance<MiniValDecl>().sortedBy { it.name.lowercase() }
|
||||||
|
val aliases = decls.filterIsInstance<MiniTypeAliasDecl>().sortedBy { it.name.lowercase() }
|
||||||
funs.forEach { if (externalAdded < budget) { offerDeclAdd(out, prefix, it); externalAdded++ } }
|
funs.forEach { if (externalAdded < budget) { offerDeclAdd(out, prefix, it); externalAdded++ } }
|
||||||
classes.forEach { if (externalAdded < budget) { offerDeclAdd(out, prefix, it); externalAdded++ } }
|
classes.forEach { if (externalAdded < budget) { offerDeclAdd(out, prefix, it); externalAdded++ } }
|
||||||
enums.forEach { if (externalAdded < budget) { offerDeclAdd(out, prefix, it); externalAdded++ } }
|
enums.forEach { if (externalAdded < budget) { offerDeclAdd(out, prefix, it); externalAdded++ } }
|
||||||
|
aliases.forEach { if (externalAdded < budget) { offerDeclAdd(out, prefix, it); externalAdded++ } }
|
||||||
vals.forEach { if (externalAdded < budget) { offerDeclAdd(out, prefix, it); externalAdded++ } }
|
vals.forEach { if (externalAdded < budget) { offerDeclAdd(out, prefix, it); externalAdded++ } }
|
||||||
if (out.size >= cap || externalAdded >= budget) break
|
if (out.size >= cap || externalAdded >= budget) break
|
||||||
}
|
}
|
||||||
@ -196,6 +211,9 @@ object CompletionEngineLight {
|
|||||||
is MiniMemberValDecl -> {
|
is MiniMemberValDecl -> {
|
||||||
add(CompletionItem(m.name, if (m.mutable) Kind.Value else Kind.Field, typeText = typeOf(m.type), priority = 100.0))
|
add(CompletionItem(m.name, if (m.mutable) Kind.Value else Kind.Field, typeText = typeOf(m.type), priority = 100.0))
|
||||||
}
|
}
|
||||||
|
is MiniMemberTypeAliasDecl -> {
|
||||||
|
add(CompletionItem(m.name, Kind.TypeAlias, typeText = typeOf(m.target), priority = 100.0))
|
||||||
|
}
|
||||||
is MiniInitDecl -> {}
|
is MiniInitDecl -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,12 +243,13 @@ object CompletionEngineLight {
|
|||||||
}
|
}
|
||||||
is MiniClassDecl -> add(CompletionItem(d.name, Kind.Class_))
|
is MiniClassDecl -> add(CompletionItem(d.name, Kind.Class_))
|
||||||
is MiniEnumDecl -> add(CompletionItem(d.name, Kind.Enum))
|
is MiniEnumDecl -> add(CompletionItem(d.name, Kind.Enum))
|
||||||
|
is MiniTypeAliasDecl -> add(CompletionItem(d.name, Kind.TypeAlias, typeText = typeOf(d.target)))
|
||||||
is MiniValDecl -> add(CompletionItem(d.name, Kind.Value, typeText = typeOf(d.type)))
|
is MiniValDecl -> add(CompletionItem(d.name, Kind.Value, typeText = typeOf(d.type)))
|
||||||
// else -> add(CompletionItem(d.name, Kind.Value))
|
// else -> add(CompletionItem(d.name, Kind.Value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun offerMembersAdd(out: MutableList<CompletionItem>, prefix: String, imported: List<String>, className: String, mini: MiniScript? = null) {
|
private fun offerMembersAdd(out: MutableList<CompletionItem>, prefix: String, imported: List<String>, className: String, mini: MiniScript? = null, staticOnly: Boolean = false) {
|
||||||
val classes = DocLookupUtils.aggregateClasses(imported, mini)
|
val classes = DocLookupUtils.aggregateClasses(imported, mini)
|
||||||
val visited = mutableSetOf<String>()
|
val visited = mutableSetOf<String>()
|
||||||
val directMap = LinkedHashMap<String, MutableList<MiniMemberDecl>>()
|
val directMap = LinkedHashMap<String, MutableList<MiniMemberDecl>>()
|
||||||
@ -239,10 +258,15 @@ object CompletionEngineLight {
|
|||||||
fun addMembersOf(name: String, direct: Boolean) {
|
fun addMembersOf(name: String, direct: Boolean) {
|
||||||
val cls = classes[name] ?: return
|
val cls = classes[name] ?: return
|
||||||
val target = if (direct) directMap else inheritedMap
|
val target = if (direct) directMap else inheritedMap
|
||||||
for (cf in cls.ctorFields + cls.classFields) {
|
if (!staticOnly) {
|
||||||
target.getOrPut(cf.name) { mutableListOf() }.add(DocLookupUtils.toMemberVal(cf))
|
for (cf in cls.ctorFields + cls.classFields) {
|
||||||
|
target.getOrPut(cf.name) { mutableListOf() }.add(DocLookupUtils.toMemberVal(cf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (m in cls.members) {
|
||||||
|
if (staticOnly && !m.isStatic) continue
|
||||||
|
target.getOrPut(m.name) { mutableListOf() }.add(m)
|
||||||
}
|
}
|
||||||
for (m in cls.members) target.getOrPut(m.name) { mutableListOf() }.add(m)
|
|
||||||
for (b in cls.bases) if (visited.add(b)) addMembersOf(b, false)
|
for (b in cls.bases) if (visited.add(b)) addMembersOf(b, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +313,10 @@ object CompletionEngineLight {
|
|||||||
val ci = CompletionItem(name, Kind.Field, typeText = typeOf(chosen.type), priority = groupPriority)
|
val ci = CompletionItem(name, Kind.Field, typeText = typeOf(chosen.type), priority = groupPriority)
|
||||||
if (ci.name.startsWith(prefix, true)) out += ci
|
if (ci.name.startsWith(prefix, true)) out += ci
|
||||||
}
|
}
|
||||||
|
is MiniMemberTypeAliasDecl -> {
|
||||||
|
val ci = CompletionItem(name, Kind.TypeAlias, typeText = typeOf(rep.target), priority = groupPriority)
|
||||||
|
if (ci.name.startsWith(prefix, true)) out += ci
|
||||||
|
}
|
||||||
is MiniInitDecl -> {}
|
is MiniInitDecl -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,7 +326,7 @@ object CompletionEngineLight {
|
|||||||
emitGroup(inheritedMap, 0.0)
|
emitGroup(inheritedMap, 0.0)
|
||||||
|
|
||||||
// Supplement with extension members (both stdlib and local)
|
// Supplement with extension members (both stdlib and local)
|
||||||
run {
|
if (!staticOnly) run {
|
||||||
val already = (directMap.keys + inheritedMap.keys).toMutableSet()
|
val already = (directMap.keys + inheritedMap.keys).toMutableSet()
|
||||||
val extensions = DocLookupUtils.collectExtensionMemberNames(imported, className, mini)
|
val extensions = DocLookupUtils.collectExtensionMemberNames(imported, className, mini)
|
||||||
for (name in extensions) {
|
for (name in extensions) {
|
||||||
@ -317,6 +345,8 @@ object CompletionEngineLight {
|
|||||||
}
|
}
|
||||||
is MiniMemberValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type), priority = 50.0)
|
is MiniMemberValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type), priority = 50.0)
|
||||||
is MiniValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type), priority = 50.0)
|
is MiniValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type), priority = 50.0)
|
||||||
|
is MiniMemberTypeAliasDecl -> CompletionItem(name, Kind.TypeAlias, typeText = typeOf(m.target), priority = 50.0)
|
||||||
|
is MiniTypeAliasDecl -> CompletionItem(name, Kind.TypeAlias, typeText = typeOf(m.target), priority = 50.0)
|
||||||
else -> CompletionItem(name, Kind.Method, tailText = "()", typeText = null, priority = 50.0)
|
else -> CompletionItem(name, Kind.Method, tailText = "()", typeText = null, priority = 50.0)
|
||||||
}
|
}
|
||||||
if (ci.name.startsWith(prefix, true)) {
|
if (ci.name.startsWith(prefix, true)) {
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.sergeych.lyng.miniast
|
package net.sergeych.lyng.miniast
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.binding.BindingSnapshot
|
import net.sergeych.lyng.binding.BindingSnapshot
|
||||||
import net.sergeych.lyng.highlight.offsetOf
|
import net.sergeych.lyng.highlight.offsetOf
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ object DocLookupUtils {
|
|||||||
is MiniFunDecl -> "Function"
|
is MiniFunDecl -> "Function"
|
||||||
is MiniClassDecl -> "Class"
|
is MiniClassDecl -> "Class"
|
||||||
is MiniEnumDecl -> "Enum"
|
is MiniEnumDecl -> "Enum"
|
||||||
|
is MiniTypeAliasDecl -> "TypeAlias"
|
||||||
is MiniValDecl -> if (d.mutable) "Variable" else "Value"
|
is MiniValDecl -> if (d.mutable) "Variable" else "Value"
|
||||||
}
|
}
|
||||||
return d.name to kind
|
return d.name to kind
|
||||||
@ -87,6 +89,7 @@ object DocLookupUtils {
|
|||||||
val kind = when (m) {
|
val kind = when (m) {
|
||||||
is MiniMemberFunDecl -> "Function"
|
is MiniMemberFunDecl -> "Function"
|
||||||
is MiniMemberValDecl -> if (m.isStatic) "Value" else (if (m.mutable) "Variable" else "Value")
|
is MiniMemberValDecl -> if (m.isStatic) "Value" else (if (m.mutable) "Variable" else "Value")
|
||||||
|
is MiniMemberTypeAliasDecl -> "TypeAlias"
|
||||||
is MiniInitDecl -> "Initializer"
|
is MiniInitDecl -> "Initializer"
|
||||||
}
|
}
|
||||||
return m.name to kind
|
return m.name to kind
|
||||||
@ -119,6 +122,7 @@ object DocLookupUtils {
|
|||||||
return when (d) {
|
return when (d) {
|
||||||
is MiniValDecl -> d.type ?: if (text != null && imported != null) inferTypeRefForVal(d, text, imported, mini) else null
|
is MiniValDecl -> d.type ?: if (text != null && imported != null) inferTypeRefForVal(d, text, imported, mini) else null
|
||||||
is MiniFunDecl -> d.returnType
|
is MiniFunDecl -> d.returnType
|
||||||
|
is MiniTypeAliasDecl -> d.target
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,6 +146,7 @@ object DocLookupUtils {
|
|||||||
is MiniMemberValDecl -> m.type ?: if (text != null && imported != null) {
|
is MiniMemberValDecl -> m.type ?: if (text != null && imported != null) {
|
||||||
inferTypeRefFromInitRange(m.initRange, m.nameStart, text, imported, mini)
|
inferTypeRefFromInitRange(m.initRange, m.nameStart, text, imported, mini)
|
||||||
} else null
|
} else null
|
||||||
|
is MiniMemberTypeAliasDecl -> m.target
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@ -297,32 +302,42 @@ object DocLookupUtils {
|
|||||||
isExtern = false
|
isExtern = false
|
||||||
)
|
)
|
||||||
|
|
||||||
fun resolveMemberWithInheritance(importedModules: List<String>, className: String, member: String, localMini: MiniScript? = null): Pair<String, MiniNamedDecl>? {
|
fun resolveMemberWithInheritance(
|
||||||
|
importedModules: List<String>,
|
||||||
|
className: String,
|
||||||
|
member: String,
|
||||||
|
localMini: MiniScript? = null,
|
||||||
|
staticOnly: Boolean = false
|
||||||
|
): Pair<String, MiniNamedDecl>? {
|
||||||
val classes = aggregateClasses(importedModules, localMini)
|
val classes = aggregateClasses(importedModules, localMini)
|
||||||
fun dfs(name: String, visited: MutableSet<String>): Pair<String, MiniNamedDecl>? {
|
fun dfs(name: String, visited: MutableSet<String>): Pair<String, MiniNamedDecl>? {
|
||||||
if (!visited.add(name)) return null
|
if (!visited.add(name)) return null
|
||||||
val cls = classes[name]
|
val cls = classes[name]
|
||||||
if (cls != null) {
|
if (cls != null) {
|
||||||
cls.members.firstOrNull { it.name == member }?.let { return name to it }
|
cls.members.firstOrNull { it.name == member && (!staticOnly || it.isStatic) }?.let { return name to it }
|
||||||
cls.ctorFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
|
if (!staticOnly) {
|
||||||
cls.classFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
|
cls.ctorFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
|
||||||
|
cls.classFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
|
||||||
|
}
|
||||||
for (baseName in cls.bases) {
|
for (baseName in cls.bases) {
|
||||||
dfs(baseName, visited)?.let { return it }
|
dfs(baseName, visited)?.let { return it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 1) local extensions in this class or bases
|
if (!staticOnly) {
|
||||||
localMini?.declarations?.firstOrNull { d ->
|
// 1) local extensions in this class or bases
|
||||||
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
localMini?.declarations?.firstOrNull { d ->
|
||||||
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
|
||||||
}?.let { return name to it as MiniNamedDecl }
|
|
||||||
|
|
||||||
// 2) built-in extensions from BuiltinDocRegistry
|
|
||||||
for (mod in importedModules) {
|
|
||||||
val decls = BuiltinDocRegistry.docsForModule(mod)
|
|
||||||
decls.firstOrNull { d ->
|
|
||||||
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
||||||
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
||||||
}?.let { return name to it as MiniNamedDecl }
|
}?.let { return name to it as MiniNamedDecl }
|
||||||
|
|
||||||
|
// 2) built-in extensions from BuiltinDocRegistry
|
||||||
|
for (mod in importedModules) {
|
||||||
|
val decls = BuiltinDocRegistry.docsForModule(mod)
|
||||||
|
decls.firstOrNull { d ->
|
||||||
|
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
||||||
|
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
||||||
|
}?.let { return name to it as MiniNamedDecl }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@ -426,6 +441,7 @@ object DocLookupUtils {
|
|||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
val sym = binding.symbols.firstOrNull { it.id == ref.symbolId }
|
val sym = binding.symbols.firstOrNull { it.id == ref.symbolId }
|
||||||
if (sym != null) {
|
if (sym != null) {
|
||||||
|
simpleClassNameOfType(sym.type)?.let { return it }
|
||||||
val type = findTypeByRange(mini, sym.name, sym.declStart, text, imported)
|
val type = findTypeByRange(mini, sym.name, sym.declStart, text, imported)
|
||||||
simpleClassNameOf(type)?.let { return it }
|
simpleClassNameOf(type)?.let { return it }
|
||||||
}
|
}
|
||||||
@ -433,10 +449,14 @@ object DocLookupUtils {
|
|||||||
// Check if it's a declaration (e.g. static access to a class)
|
// Check if it's a declaration (e.g. static access to a class)
|
||||||
val sym = binding.symbols.firstOrNull { it.declStart == wordRange.first && it.name == ident }
|
val sym = binding.symbols.firstOrNull { it.declStart == wordRange.first && it.name == ident }
|
||||||
if (sym != null) {
|
if (sym != null) {
|
||||||
|
simpleClassNameOfType(sym.type)?.let { return it }
|
||||||
val type = findTypeByRange(mini, sym.name, sym.declStart, text, imported)
|
val type = findTypeByRange(mini, sym.name, sym.declStart, text, imported)
|
||||||
simpleClassNameOf(type)?.let { return it }
|
simpleClassNameOf(type)?.let { return it }
|
||||||
// if it's a class/enum, return its name directly
|
// if it's a class/enum, return its name directly
|
||||||
if (sym.kind == net.sergeych.lyng.binding.SymbolKind.Class || sym.kind == net.sergeych.lyng.binding.SymbolKind.Enum) return sym.name
|
if (sym.kind == net.sergeych.lyng.binding.SymbolKind.Class ||
|
||||||
|
sym.kind == net.sergeych.lyng.binding.SymbolKind.Enum ||
|
||||||
|
sym.kind == net.sergeych.lyng.binding.SymbolKind.TypeAlias
|
||||||
|
) return sym.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -451,6 +471,7 @@ object DocLookupUtils {
|
|||||||
return when (d) {
|
return when (d) {
|
||||||
is MiniClassDecl -> d.name
|
is MiniClassDecl -> d.name
|
||||||
is MiniEnumDecl -> d.name
|
is MiniEnumDecl -> d.name
|
||||||
|
is MiniTypeAliasDecl -> d.name
|
||||||
is MiniValDecl -> simpleClassNameOf(d.type ?: inferTypeRefForVal(d, text, imported, mini))
|
is MiniValDecl -> simpleClassNameOf(d.type ?: inferTypeRefForVal(d, text, imported, mini))
|
||||||
is MiniFunDecl -> simpleClassNameOf(d.returnType)
|
is MiniFunDecl -> simpleClassNameOf(d.returnType)
|
||||||
}
|
}
|
||||||
@ -565,6 +586,7 @@ object DocLookupUtils {
|
|||||||
val rt = when (mm) {
|
val rt = when (mm) {
|
||||||
is MiniMemberFunDecl -> mm.returnType
|
is MiniMemberFunDecl -> mm.returnType
|
||||||
is MiniMemberValDecl -> mm.type
|
is MiniMemberValDecl -> mm.type
|
||||||
|
is MiniMemberTypeAliasDecl -> mm.target
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
return simpleClassNameOf(rt)
|
return simpleClassNameOf(rt)
|
||||||
@ -580,8 +602,10 @@ object DocLookupUtils {
|
|||||||
val rt = when (m) {
|
val rt = when (m) {
|
||||||
is MiniMemberFunDecl -> m.returnType
|
is MiniMemberFunDecl -> m.returnType
|
||||||
is MiniMemberValDecl -> m.type
|
is MiniMemberValDecl -> m.type
|
||||||
|
is MiniMemberTypeAliasDecl -> m.target
|
||||||
is MiniFunDecl -> m.returnType
|
is MiniFunDecl -> m.returnType
|
||||||
is MiniValDecl -> m.type
|
is MiniValDecl -> m.type
|
||||||
|
is MiniTypeAliasDecl -> m.target
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
simpleClassNameOf(rt)
|
simpleClassNameOf(rt)
|
||||||
@ -921,6 +945,7 @@ object DocLookupUtils {
|
|||||||
return when (val m = resolved.second) {
|
return when (val m = resolved.second) {
|
||||||
is MiniMemberFunDecl -> m.returnType
|
is MiniMemberFunDecl -> m.returnType
|
||||||
is MiniMemberValDecl -> m.type ?: inferTypeRefFromInitRange(m.initRange, m.nameStart, fullText, imported, mini)
|
is MiniMemberValDecl -> m.type ?: inferTypeRefFromInitRange(m.initRange, m.nameStart, fullText, imported, mini)
|
||||||
|
is MiniMemberTypeAliasDecl -> m.target
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -943,6 +968,7 @@ object DocLookupUtils {
|
|||||||
is MiniEnumDecl -> syntheticTypeRef(d.name)
|
is MiniEnumDecl -> syntheticTypeRef(d.name)
|
||||||
is MiniValDecl -> d.type ?: inferTypeRefForVal(d, fullText, imported, mini)
|
is MiniValDecl -> d.type ?: inferTypeRefForVal(d, fullText, imported, mini)
|
||||||
is MiniFunDecl -> d.returnType
|
is MiniFunDecl -> d.returnType
|
||||||
|
is MiniTypeAliasDecl -> d.target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1025,6 +1051,18 @@ object DocLookupUtils {
|
|||||||
is MiniGenericType -> simpleClassNameOf(t.base)
|
is MiniGenericType -> simpleClassNameOf(t.base)
|
||||||
is MiniFunctionType -> null
|
is MiniFunctionType -> null
|
||||||
is MiniTypeVar -> null
|
is MiniTypeVar -> null
|
||||||
|
is MiniTypeUnion -> null
|
||||||
|
is MiniTypeIntersection -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun simpleClassNameOfType(type: String?): String? {
|
||||||
|
if (type.isNullOrBlank()) return null
|
||||||
|
var t = type.trim()
|
||||||
|
if (t.endsWith("?")) t = t.dropLast(1)
|
||||||
|
val first = t.split('|', '&').firstOrNull()?.trim() ?: return null
|
||||||
|
val base = first.substringBefore('<').trim()
|
||||||
|
val short = base.substringAfterLast('.').trim()
|
||||||
|
return short.ifBlank { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun typeOf(t: MiniTypeRef?): String = when (t) {
|
fun typeOf(t: MiniTypeRef?): String = when (t) {
|
||||||
@ -1035,9 +1073,95 @@ object DocLookupUtils {
|
|||||||
r + "(" + t.params.joinToString(", ") { typeOf(it) } + ") -> " + typeOf(t.returnType) + (if (t.nullable) "?" else "")
|
r + "(" + t.params.joinToString(", ") { typeOf(it) } + ") -> " + typeOf(t.returnType) + (if (t.nullable) "?" else "")
|
||||||
}
|
}
|
||||||
is MiniTypeVar -> t.name + (if (t.nullable) "?" else "")
|
is MiniTypeVar -> t.name + (if (t.nullable) "?" else "")
|
||||||
|
is MiniTypeUnion -> t.options.joinToString(" | ") { typeOf(it) } + (if (t.nullable) "?" else "")
|
||||||
|
is MiniTypeIntersection -> t.options.joinToString(" & ") { typeOf(it) } + (if (t.nullable) "?" else "")
|
||||||
null -> ""
|
null -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun collectLocalsFromBinding(mini: MiniScript?, binding: BindingSnapshot?, offset: Int): List<net.sergeych.lyng.binding.Symbol> {
|
||||||
|
if (mini == null || binding == null) return emptyList()
|
||||||
|
val src = mini.range.start.source
|
||||||
|
data class FnCtx(val nameStart: Pos, val body: MiniRange)
|
||||||
|
fun consider(nameStart: Pos, body: MiniRange?, best: FnCtx?): FnCtx? {
|
||||||
|
if (body == null) return best
|
||||||
|
val start = src.offsetOf(body.start)
|
||||||
|
val end = src.offsetOf(body.end)
|
||||||
|
if (offset < start || offset > end) return best
|
||||||
|
val len = end - start
|
||||||
|
val bestLen = best?.let { src.offsetOf(it.body.end) - src.offsetOf(it.body.start) } ?: Int.MAX_VALUE
|
||||||
|
return if (len < bestLen) FnCtx(nameStart, body) else best
|
||||||
|
}
|
||||||
|
|
||||||
|
var best: FnCtx? = null
|
||||||
|
for (d in mini.declarations) {
|
||||||
|
when (d) {
|
||||||
|
is MiniFunDecl -> best = consider(d.nameStart, d.body?.range, best)
|
||||||
|
is MiniClassDecl -> {
|
||||||
|
for (m in d.members) {
|
||||||
|
if (m is MiniMemberFunDecl) {
|
||||||
|
best = consider(m.nameStart, m.body?.range, best)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val fn = best ?: return emptyList()
|
||||||
|
val fnDeclStart = src.offsetOf(fn.nameStart)
|
||||||
|
val fnSym = binding.symbols.firstOrNull {
|
||||||
|
it.kind == net.sergeych.lyng.binding.SymbolKind.Function && it.declStart == fnDeclStart
|
||||||
|
} ?: return emptyList()
|
||||||
|
return binding.symbols.filter {
|
||||||
|
it.containerId == fnSym.id &&
|
||||||
|
(it.kind == net.sergeych.lyng.binding.SymbolKind.Parameter ||
|
||||||
|
it.kind == net.sergeych.lyng.binding.SymbolKind.Value ||
|
||||||
|
it.kind == net.sergeych.lyng.binding.SymbolKind.Variable) &&
|
||||||
|
it.declStart < offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isStaticReceiver(
|
||||||
|
mini: MiniScript?,
|
||||||
|
text: String,
|
||||||
|
dotPos: Int,
|
||||||
|
importedModules: List<String>,
|
||||||
|
binding: BindingSnapshot? = null
|
||||||
|
): Boolean {
|
||||||
|
val i = prevNonWs(text, dotPos - 1)
|
||||||
|
if (i < 0 || !isIdentChar(text[i])) return false
|
||||||
|
val wordRange = wordRangeAt(text, i + 1) ?: return false
|
||||||
|
val ident = text.substring(wordRange.first, wordRange.second)
|
||||||
|
if (ident == "this") return false
|
||||||
|
|
||||||
|
if (binding != null) {
|
||||||
|
val ref = binding.references.firstOrNull { wordRange.first >= it.start && wordRange.first < it.end }
|
||||||
|
val sym = ref?.let { r -> binding.symbols.firstOrNull { it.id == r.symbolId } }
|
||||||
|
?: binding.symbols.firstOrNull { it.declStart == wordRange.first && it.name == ident }
|
||||||
|
if (sym != null) {
|
||||||
|
return sym.kind == net.sergeych.lyng.binding.SymbolKind.Class ||
|
||||||
|
sym.kind == net.sergeych.lyng.binding.SymbolKind.Enum ||
|
||||||
|
sym.kind == net.sergeych.lyng.binding.SymbolKind.TypeAlias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mini != null) {
|
||||||
|
val src = mini.range.start.source
|
||||||
|
val decl = mini.declarations
|
||||||
|
.filter { it.name == ident && src.offsetOf(it.nameStart) < dotPos }
|
||||||
|
.maxByOrNull { src.offsetOf(it.nameStart) }
|
||||||
|
if (decl is MiniClassDecl || decl is MiniEnumDecl || decl is MiniTypeAliasDecl) return true
|
||||||
|
if (decl is MiniFunDecl || decl is MiniValDecl) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val classes = aggregateClasses(importedModules, mini)
|
||||||
|
if (classes.containsKey(ident)) return true
|
||||||
|
for (mod in importedModules) {
|
||||||
|
val aliases = BuiltinDocRegistry.docsForModule(mod).filterIsInstance<MiniTypeAliasDecl>()
|
||||||
|
if (aliases.any { it.name == ident }) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fun findDotLeft(text: String, offset: Int): Int? {
|
fun findDotLeft(text: String, offset: Int): Int? {
|
||||||
var i = (offset - 1).coerceAtLeast(0)
|
var i = (offset - 1).coerceAtLeast(0)
|
||||||
while (i >= 0 && text[i].isWhitespace()) i--
|
while (i >= 0 && text[i].isWhitespace()) i--
|
||||||
|
|||||||
@ -19,6 +19,7 @@ package net.sergeych.lyng.miniast
|
|||||||
|
|
||||||
import net.sergeych.lyng.ModuleScope
|
import net.sergeych.lyng.ModuleScope
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.ScopeFacade
|
||||||
import net.sergeych.lyng.Visibility
|
import net.sergeych.lyng.Visibility
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
@ -39,10 +40,11 @@ inline fun <reified T : Obj> Scope.addFnDoc(
|
|||||||
returns: TypeDoc? = null,
|
returns: TypeDoc? = null,
|
||||||
tags: Map<String, List<String>> = emptyMap(),
|
tags: Map<String, List<String>> = emptyMap(),
|
||||||
moduleName: String? = null,
|
moduleName: String? = null,
|
||||||
crossinline fn: suspend Scope.() -> T
|
callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||||
|
crossinline fn: suspend ScopeFacade.() -> T
|
||||||
) {
|
) {
|
||||||
// Register runtime function(s)
|
// Register runtime function(s)
|
||||||
addFn(*names) { fn() }
|
addFn(*names, callSignature = callSignature) { fn() }
|
||||||
// Determine module
|
// Determine module
|
||||||
val mod = moduleName ?: findModuleNameOrUnknown()
|
val mod = moduleName ?: findModuleNameOrUnknown()
|
||||||
// Register docs once per name
|
// Register docs once per name
|
||||||
@ -56,7 +58,7 @@ inline fun Scope.addVoidFnDoc(
|
|||||||
doc: String,
|
doc: String,
|
||||||
tags: Map<String, List<String>> = emptyMap(),
|
tags: Map<String, List<String>> = emptyMap(),
|
||||||
moduleName: String? = null,
|
moduleName: String? = null,
|
||||||
crossinline fn: suspend Scope.() -> Unit
|
crossinline fn: suspend ScopeFacade.() -> Unit
|
||||||
) {
|
) {
|
||||||
addFnDoc<ObjVoid>(
|
addFnDoc<ObjVoid>(
|
||||||
*names,
|
*names,
|
||||||
@ -97,7 +99,7 @@ fun ObjClass.addFnDoc(
|
|||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
tags: Map<String, List<String>> = emptyMap(),
|
tags: Map<String, List<String>> = emptyMap(),
|
||||||
moduleName: String? = null,
|
moduleName: String? = null,
|
||||||
code: suspend Scope.() -> Obj
|
code: suspend ScopeFacade.() -> Obj
|
||||||
) {
|
) {
|
||||||
// Register runtime method
|
// Register runtime method
|
||||||
addFn(name, isOpen, visibility, code = code)
|
addFn(name, isOpen, visibility, code = code)
|
||||||
@ -135,7 +137,7 @@ fun ObjClass.addClassFnDoc(
|
|||||||
isOpen: Boolean = false,
|
isOpen: Boolean = false,
|
||||||
tags: Map<String, List<String>> = emptyMap(),
|
tags: Map<String, List<String>> = emptyMap(),
|
||||||
moduleName: String? = null,
|
moduleName: String? = null,
|
||||||
code: suspend Scope.() -> Obj
|
code: suspend ScopeFacade.() -> Obj
|
||||||
) {
|
) {
|
||||||
addClassFn(name, isOpen, code)
|
addClassFn(name, isOpen, code)
|
||||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||||
@ -151,8 +153,8 @@ fun ObjClass.addPropertyDoc(
|
|||||||
type: TypeDoc? = null,
|
type: TypeDoc? = null,
|
||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
moduleName: String? = null,
|
moduleName: String? = null,
|
||||||
getter: (suspend Scope.() -> Obj)? = null,
|
getter: (suspend ScopeFacade.() -> Obj)? = null,
|
||||||
setter: (suspend Scope.(Obj) -> Unit)? = null
|
setter: (suspend ScopeFacade.(Obj) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
addProperty(name, getter, setter, visibility)
|
addProperty(name, getter, setter, visibility)
|
||||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||||
|
|||||||
@ -150,6 +150,18 @@ data class MiniTypeVar(
|
|||||||
val nullable: Boolean
|
val nullable: Boolean
|
||||||
) : MiniTypeRef
|
) : MiniTypeRef
|
||||||
|
|
||||||
|
data class MiniTypeUnion(
|
||||||
|
override val range: MiniRange,
|
||||||
|
val options: List<MiniTypeRef>,
|
||||||
|
val nullable: Boolean
|
||||||
|
) : MiniTypeRef
|
||||||
|
|
||||||
|
data class MiniTypeIntersection(
|
||||||
|
override val range: MiniRange,
|
||||||
|
val options: List<MiniTypeRef>,
|
||||||
|
val nullable: Boolean
|
||||||
|
) : MiniTypeRef
|
||||||
|
|
||||||
// Script and declarations (lean subset; can be extended later)
|
// Script and declarations (lean subset; can be extended later)
|
||||||
sealed interface MiniNamedDecl : MiniNode {
|
sealed interface MiniNamedDecl : MiniNode {
|
||||||
val name: String
|
val name: String
|
||||||
@ -229,6 +241,17 @@ data class MiniEnumDecl(
|
|||||||
val entryPositions: List<Pos> = emptyList()
|
val entryPositions: List<Pos> = emptyList()
|
||||||
) : MiniDecl
|
) : MiniDecl
|
||||||
|
|
||||||
|
data class MiniTypeAliasDecl(
|
||||||
|
override val range: MiniRange,
|
||||||
|
override val name: String,
|
||||||
|
val typeParams: List<String>,
|
||||||
|
val target: MiniTypeRef?,
|
||||||
|
override val doc: MiniDoc?,
|
||||||
|
override val nameStart: Pos,
|
||||||
|
override val isExtern: Boolean = false,
|
||||||
|
override val isStatic: Boolean = false,
|
||||||
|
) : MiniDecl
|
||||||
|
|
||||||
data class MiniCtorField(
|
data class MiniCtorField(
|
||||||
val name: String,
|
val name: String,
|
||||||
val mutable: Boolean,
|
val mutable: Boolean,
|
||||||
@ -278,6 +301,17 @@ data class MiniMemberValDecl(
|
|||||||
override val isExtern: Boolean = false,
|
override val isExtern: Boolean = false,
|
||||||
) : MiniMemberDecl
|
) : MiniMemberDecl
|
||||||
|
|
||||||
|
data class MiniMemberTypeAliasDecl(
|
||||||
|
override val range: MiniRange,
|
||||||
|
override val name: String,
|
||||||
|
val typeParams: List<String>,
|
||||||
|
val target: MiniTypeRef?,
|
||||||
|
override val doc: MiniDoc?,
|
||||||
|
override val nameStart: Pos,
|
||||||
|
override val isStatic: Boolean = false,
|
||||||
|
override val isExtern: Boolean = false,
|
||||||
|
) : MiniMemberDecl
|
||||||
|
|
||||||
data class MiniInitDecl(
|
data class MiniInitDecl(
|
||||||
override val range: MiniRange,
|
override val range: MiniRange,
|
||||||
override val nameStart: Pos,
|
override val nameStart: Pos,
|
||||||
@ -307,6 +341,7 @@ interface MiniAstSink {
|
|||||||
fun onInitDecl(node: MiniInitDecl) {}
|
fun onInitDecl(node: MiniInitDecl) {}
|
||||||
fun onClassDecl(node: MiniClassDecl) {}
|
fun onClassDecl(node: MiniClassDecl) {}
|
||||||
fun onEnumDecl(node: MiniEnumDecl) {}
|
fun onEnumDecl(node: MiniEnumDecl) {}
|
||||||
|
fun onTypeAliasDecl(node: MiniTypeAliasDecl) {}
|
||||||
|
|
||||||
fun onBlock(node: MiniBlock) {}
|
fun onBlock(node: MiniBlock) {}
|
||||||
fun onIdentifier(node: MiniIdentifier) {}
|
fun onIdentifier(node: MiniIdentifier) {}
|
||||||
@ -477,6 +512,41 @@ class MiniAstBuilder : MiniAstSink {
|
|||||||
lastDoc = null
|
lastDoc = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTypeAliasDecl(node: MiniTypeAliasDecl) {
|
||||||
|
val attach = node.copy(doc = node.doc ?: lastDoc)
|
||||||
|
val currentClass = classStack.lastOrNull()
|
||||||
|
if (currentClass != null && functionDepth == 0) {
|
||||||
|
val member = MiniMemberTypeAliasDecl(
|
||||||
|
range = attach.range,
|
||||||
|
name = attach.name,
|
||||||
|
typeParams = attach.typeParams,
|
||||||
|
target = attach.target,
|
||||||
|
doc = attach.doc,
|
||||||
|
nameStart = attach.nameStart,
|
||||||
|
isStatic = attach.isStatic,
|
||||||
|
isExtern = attach.isExtern
|
||||||
|
)
|
||||||
|
val existing = currentClass.members.filterIsInstance<MiniMemberTypeAliasDecl>()
|
||||||
|
.find { it.name == attach.name && it.nameStart == attach.nameStart }
|
||||||
|
val updatedMembers = if (existing != null) {
|
||||||
|
currentClass.members.map { if (it === existing) member else it }
|
||||||
|
} else {
|
||||||
|
currentClass.members + member
|
||||||
|
}
|
||||||
|
classStack.removeLast()
|
||||||
|
classStack.addLast(currentClass.copy(members = updatedMembers))
|
||||||
|
} else {
|
||||||
|
val existing = currentScript?.declarations?.find { it.name == attach.name && it.nameStart == attach.nameStart }
|
||||||
|
if (existing != null) {
|
||||||
|
val idx = currentScript?.declarations?.indexOf(existing) ?: -1
|
||||||
|
if (idx >= 0) currentScript?.declarations?.set(idx, attach)
|
||||||
|
} else {
|
||||||
|
currentScript?.declarations?.add(attach)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastDoc = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBlock(node: MiniBlock) {
|
override fun onBlock(node: MiniBlock) {
|
||||||
blocks.addLast(node)
|
blocks.addLast(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ensure stdlib Obj*-defined docs (like String methods added via ObjString.addFnDoc)
|
* Ensure stdlib Obj*-defined docs (like String methods added via ObjString.addFnDoc)
|
||||||
* are initialized before registry lookups for completion/quick docs.
|
* are initialized before registry lookups for completion/quick docs.
|
||||||
@ -33,6 +50,19 @@ object StdlibDocsBootstrap {
|
|||||||
val _range = net.sergeych.lyng.obj.ObjRange.type
|
val _range = net.sergeych.lyng.obj.ObjRange.type
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
val _buffer = net.sergeych.lyng.obj.ObjBuffer.type
|
val _buffer = net.sergeych.lyng.obj.ObjBuffer.type
|
||||||
|
|
||||||
|
// Also touch time module types so their docs (moduleName = "lyng.time") are registered
|
||||||
|
// This enables completion/quick docs for symbols imported via `import lyng.time` (e.g., Instant, DateTime, Duration)
|
||||||
|
try {
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val _instant = net.sergeych.lyng.obj.ObjInstant.type
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val _datetime = net.sergeych.lyng.obj.ObjDateTime.type
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val _duration = net.sergeych.lyng.obj.ObjDuration.type
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
// Optional; absence should not affect stdlib core
|
||||||
|
}
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
// Best-effort; absence should not break consumers
|
// Best-effort; absence should not break consumers
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -20,7 +20,6 @@ package net.sergeych.lyng.obj
|
|||||||
import net.sergeych.lyng.Compiler
|
import net.sergeych.lyng.Compiler
|
||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.ScriptError
|
|
||||||
|
|
||||||
// avoid KDOC bug: keep it
|
// avoid KDOC bug: keep it
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@ -37,10 +36,11 @@ private class LambdaRef(
|
|||||||
private val getterFn: suspend (Scope) -> ObjRecord,
|
private val getterFn: suspend (Scope) -> ObjRecord,
|
||||||
private val setterFn: (suspend (Pos, Scope, Obj) -> Unit)? = null
|
private val setterFn: (suspend (Pos, Scope, Obj) -> Unit)? = null
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord = getterFn(scope)
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
|
return scope.raiseIllegalState("bytecode-only execution is required; Accessor evaluation is disabled")
|
||||||
|
}
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
val s = setterFn ?: throw ScriptError(pos, "can't assign value")
|
scope.raiseIllegalState("bytecode-only execution is required; Accessor assignment is disabled")
|
||||||
s(pos, scope, newValue)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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 net.sergeych.lyng.ArgsDeclaration
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.bytecode.CmdFunction
|
||||||
|
import net.sergeych.lyng.bytecode.LambdaCaptureEntry
|
||||||
|
|
||||||
|
class LambdaFnRef(
|
||||||
|
valueFn: suspend (Scope) -> ObjRecord,
|
||||||
|
val bytecodeFn: CmdFunction?,
|
||||||
|
val paramSlotPlan: Map<String, Int>,
|
||||||
|
val argsDeclaration: ArgsDeclaration?,
|
||||||
|
val captureEntries: List<LambdaCaptureEntry>,
|
||||||
|
val preferredThisType: String?,
|
||||||
|
val wrapAsExtensionCallable: Boolean,
|
||||||
|
val returnLabels: Set<String>,
|
||||||
|
val pos: Pos,
|
||||||
|
) : ValueFnRef(valueFn)
|
||||||
@ -65,7 +65,8 @@ open class Obj {
|
|||||||
fun isInstanceOf(someClass: Obj) = someClass === objClass ||
|
fun isInstanceOf(someClass: Obj) = someClass === objClass ||
|
||||||
objClass.allParentsSet.contains(someClass) ||
|
objClass.allParentsSet.contains(someClass) ||
|
||||||
someClass == rootObjectType ||
|
someClass == rootObjectType ||
|
||||||
(someClass is ObjClass && objClass.allImplementingNames.contains(someClass.className))
|
(someClass is ObjClass && (objClass.allImplementingNames.contains(someClass.className) ||
|
||||||
|
objClass.className == someClass.className))
|
||||||
|
|
||||||
fun isInstanceOf(className: String) =
|
fun isInstanceOf(className: String) =
|
||||||
objClass.mro.any { it.className == className } ||
|
objClass.mro.any { it.className == className } ||
|
||||||
@ -124,17 +125,7 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Extensions in scope
|
// 2. Root object fallback
|
||||||
val extension = scope.findExtension(objClass, name)
|
|
||||||
if (extension != null) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Root object fallback
|
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") {
|
if (cls.className == "Obj") {
|
||||||
cls.members[name]?.let { rec ->
|
cls.members[name]?.let { rec ->
|
||||||
@ -151,6 +142,15 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scope.findExtension(objClass, name)?.let { ext ->
|
||||||
|
if (ext.type == ObjRecord.Type.Property) {
|
||||||
|
if (args.isEmpty()) {
|
||||||
|
return (ext.value as ObjProperty).callGetter(scope, this, ext.declaringClass)
|
||||||
|
}
|
||||||
|
} else if (ext.type != ObjRecord.Type.Delegated) {
|
||||||
|
return ext.value.invoke(scope, this, args, ext.declaringClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return onNotFoundResult?.invoke()
|
return onNotFoundResult?.invoke()
|
||||||
?: scope.raiseError(
|
?: scope.raiseError(
|
||||||
@ -181,7 +181,7 @@ open class Obj {
|
|||||||
|
|
||||||
open suspend fun equals(scope: Scope, other: Obj): Boolean {
|
open suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||||
if (other === this) return true
|
if (other === this) return true
|
||||||
val m = objClass.getInstanceMemberOrNull("equals") ?: scope.findExtension(objClass, "equals")
|
val m = objClass.getInstanceMemberOrNull("equals")
|
||||||
if (m != null) {
|
if (m != null) {
|
||||||
return invokeInstanceMethod(scope, "equals", Arguments(other)).toBool()
|
return invokeInstanceMethod(scope, "equals", Arguments(other)).toBool()
|
||||||
}
|
}
|
||||||
@ -265,13 +265,35 @@ open class Obj {
|
|||||||
open val objClass: ObjClass get() = rootObjectType
|
open val objClass: ObjClass get() = rootObjectType
|
||||||
|
|
||||||
open suspend fun plus(scope: Scope, other: Obj): Obj {
|
open suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "plus", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.plus(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "plus", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("plus for ${objClass.className}")
|
scope.raiseNotImplemented("plus for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun minus(scope: Scope, other: Obj): Obj {
|
open suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "minus", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.minus(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "minus", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("minus for ${objClass.className}")
|
scope.raiseNotImplemented("minus for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,19 +305,52 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun mul(scope: Scope, other: Obj): Obj {
|
open suspend fun mul(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "mul", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.mul(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "mul", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("mul for ${objClass.className}")
|
scope.raiseNotImplemented("mul for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun div(scope: Scope, other: Obj): Obj {
|
open suspend fun div(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "div", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.div(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "div", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("div for ${objClass.className}")
|
scope.raiseNotImplemented("div for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun mod(scope: Scope, other: Obj): Obj {
|
open suspend fun mod(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "mod", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.mod(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "mod", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("mod for ${objClass.className}")
|
scope.raiseNotImplemented("mod for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -375,7 +430,7 @@ open class Obj {
|
|||||||
* to generate it as 'this = this + other', reassigning its variable
|
* to generate it as 'this = this + other', reassigning its variable
|
||||||
*/
|
*/
|
||||||
open suspend fun plusAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun plusAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("plusAssign") ?: scope.findExtension(objClass, "plusAssign")
|
val m = objClass.getInstanceMemberOrNull("plusAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "plusAssign", Arguments(other))
|
invokeInstanceMethod(scope, "plusAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
@ -385,28 +440,28 @@ open class Obj {
|
|||||||
* `-=` operations, see [plusAssign]
|
* `-=` operations, see [plusAssign]
|
||||||
*/
|
*/
|
||||||
open suspend fun minusAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun minusAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("minusAssign") ?: scope.findExtension(objClass, "minusAssign")
|
val m = objClass.getInstanceMemberOrNull("minusAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "minusAssign", Arguments(other))
|
invokeInstanceMethod(scope, "minusAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun mulAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun mulAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("mulAssign") ?: scope.findExtension(objClass, "mulAssign")
|
val m = objClass.getInstanceMemberOrNull("mulAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "mulAssign", Arguments(other))
|
invokeInstanceMethod(scope, "mulAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun divAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun divAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("divAssign") ?: scope.findExtension(objClass, "divAssign")
|
val m = objClass.getInstanceMemberOrNull("divAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "divAssign", Arguments(other))
|
invokeInstanceMethod(scope, "divAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun modAssign(scope: Scope, other: Obj): Obj? {
|
open suspend fun modAssign(scope: Scope, other: Obj): Obj? {
|
||||||
val m = objClass.getInstanceMemberOrNull("modAssign") ?: scope.findExtension(objClass, "modAssign")
|
val m = objClass.getInstanceMemberOrNull("modAssign")
|
||||||
return if (m != null) {
|
return if (m != null) {
|
||||||
invokeInstanceMethod(scope, "modAssign", Arguments(other))
|
invokeInstanceMethod(scope, "modAssign", Arguments(other))
|
||||||
} else null
|
} else null
|
||||||
@ -447,7 +502,7 @@ open class Obj {
|
|||||||
caller.members[name]?.let { rec ->
|
caller.members[name]?.let { rec ->
|
||||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||||
val resolved = resolveRecord(scope, rec, name, caller)
|
val resolved = resolveRecord(scope, rec, name, caller)
|
||||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
if (resolved.type == ObjRecord.Type.Fun)
|
||||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, caller))
|
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, caller))
|
||||||
return resolved
|
return resolved
|
||||||
}
|
}
|
||||||
@ -461,22 +516,13 @@ open class Obj {
|
|||||||
if (rec != null && !rec.isAbstract) {
|
if (rec != null && !rec.isAbstract) {
|
||||||
val decl = rec.declaringClass ?: cls
|
val decl = rec.declaringClass ?: cls
|
||||||
val resolved = resolveRecord(scope, rec, name, decl)
|
val resolved = resolveRecord(scope, rec, name, decl)
|
||||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
if (resolved.type == ObjRecord.Type.Fun)
|
||||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
||||||
return resolved
|
return resolved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Extensions
|
// 2. Root fallback
|
||||||
val extension = scope.findExtension(objClass, name)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Root fallback
|
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") {
|
if (cls.className == "Obj") {
|
||||||
cls.members[name]?.let { rec ->
|
cls.members[name]?.let { rec ->
|
||||||
@ -485,12 +531,20 @@ open class Obj {
|
|||||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
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 ?: "?"})"))
|
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)
|
val resolved = resolveRecord(scope, rec, name, decl)
|
||||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
if (resolved.type == ObjRecord.Type.Fun)
|
||||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
||||||
return resolved
|
return resolved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scope.findExtension(objClass, name)?.let { ext ->
|
||||||
|
return if (ext.type == ObjRecord.Type.Property) {
|
||||||
|
val prop = ext.value as ObjProperty
|
||||||
|
ObjRecord(prop.callGetter(scope, this, ext.declaringClass), isMutable = false)
|
||||||
|
} else {
|
||||||
|
ext.copy(value = ext.value.invoke(scope, this, Arguments.EMPTY, ext.declaringClass))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scope.raiseError(
|
scope.raiseError(
|
||||||
"no such field: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}"
|
"no such field: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}"
|
||||||
@ -501,14 +555,17 @@ open class Obj {
|
|||||||
if (obj.type == ObjRecord.Type.Delegated) {
|
if (obj.type == ObjRecord.Type.Delegated) {
|
||||||
val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||||
val th = if (this === ObjVoid) ObjNull else this
|
val th = if (this === ObjVoid) ObjNull else this
|
||||||
if (del.objClass.getInstanceMemberOrNull("getValue") == null) {
|
val getValueRec = when (del) {
|
||||||
val wrapper = object : Statement() {
|
is ObjInstance -> del.methodRecordForKey("getValue")
|
||||||
override val pos: Pos = Pos.builtIn
|
?: del.instanceScope.objects["getValue"]
|
||||||
override suspend fun execute(s: Scope): Obj {
|
?: del.objClass.getInstanceMemberOrNull("getValue")
|
||||||
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
|
else -> del.objClass.getInstanceMemberOrNull("getValue")
|
||||||
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray()
|
}
|
||||||
return del.invokeInstanceMethod(s, "invoke", Arguments(*allArgs))
|
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
|
||||||
}
|
val wrapper = ObjExternCallable.fromBridge {
|
||||||
|
val th2 = if (thisObj === ObjVoid) ObjNull else thisObj
|
||||||
|
val allArgs = (listOf(th2, ObjString(name)) + args.list).toTypedArray()
|
||||||
|
del.invokeInstanceMethod(requireScope(), "invoke", Arguments(*allArgs))
|
||||||
}
|
}
|
||||||
return obj.copy(
|
return obj.copy(
|
||||||
value = wrapper,
|
value = wrapper,
|
||||||
@ -516,14 +573,11 @@ open class Obj {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name)))
|
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name)))
|
||||||
return obj.copy(
|
return obj.copy(value = res, type = ObjRecord.Type.Other)
|
||||||
value = res,
|
|
||||||
type = ObjRecord.Type.Other
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
val value = obj.value
|
val value = obj.value
|
||||||
if (value is ObjProperty || obj.type == ObjRecord.Type.Property) {
|
if (value is ObjProperty || obj.type == ObjRecord.Type.Property) {
|
||||||
val prop = if (value is ObjProperty) value else (value as? Statement)?.execute(scope.createChildScope(scope.pos, newThisObj = this)) as? ObjProperty
|
val prop = (value as? ObjProperty)
|
||||||
?: scope.raiseError("Expected ObjProperty for property member $name, got ${value::class}")
|
?: scope.raiseError("Expected ObjProperty for property member $name, got ${value::class}")
|
||||||
val res = prop.callGetter(scope, this, decl)
|
val res = prop.callGetter(scope, this, decl)
|
||||||
return ObjRecord(res, obj.isMutable)
|
return ObjRecord(res, obj.isMutable)
|
||||||
@ -558,11 +612,7 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 2. Extensions
|
// 2. Root fallback
|
||||||
if (field == null) {
|
|
||||||
field = scope.findExtension(objClass, name)
|
|
||||||
}
|
|
||||||
// 3. Root fallback
|
|
||||||
if (field == null) {
|
if (field == null) {
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") {
|
if (cls.className == "Obj") {
|
||||||
@ -609,16 +659,19 @@ open class Obj {
|
|||||||
scope.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj =
|
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj {
|
||||||
if (PerfFlags.SCOPE_POOL)
|
val usePool = PerfFlags.SCOPE_POOL && this !is Statement
|
||||||
|
return if (usePool) {
|
||||||
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
||||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||||
callOn(child)
|
callOn(child)
|
||||||
}
|
}
|
||||||
else
|
} else {
|
||||||
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
||||||
if (declaringClass != null) it.currentClassCtx = declaringClass
|
if (declaringClass != null) it.currentClassCtx = declaringClass
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||||
callOn(
|
callOn(
|
||||||
@ -687,7 +740,7 @@ open class Obj {
|
|||||||
returns = type("lyng.String"),
|
returns = type("lyng.String"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
thisObj.toString(this, true)
|
toStringOf(thisObj, true)
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "inspect",
|
name = "inspect",
|
||||||
@ -695,7 +748,7 @@ open class Obj {
|
|||||||
returns = type("lyng.String"),
|
returns = type("lyng.String"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
thisObj.inspect(this).toObj()
|
inspect(thisObj).toObj()
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "contains",
|
name = "contains",
|
||||||
@ -704,7 +757,7 @@ open class Obj {
|
|||||||
returns = type("lyng.Bool"),
|
returns = type("lyng.Bool"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
ObjBool(thisObj.contains(this, args.firstAndOnly()))
|
ObjBool(thisObj.contains(requireScope(), args.firstAndOnly()))
|
||||||
}
|
}
|
||||||
// utilities
|
// utilities
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
@ -713,7 +766,7 @@ open class Obj {
|
|||||||
params = listOf(ParamDoc("block")),
|
params = listOf(ParamDoc("block")),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
|
call(args.firstAndOnly(), Arguments(thisObj))
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "apply",
|
name = "apply",
|
||||||
@ -722,10 +775,12 @@ open class Obj {
|
|||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
val body = args.firstAndOnly()
|
val body = args.firstAndOnly()
|
||||||
|
val scope = requireScope()
|
||||||
(thisObj as? ObjInstance)?.let {
|
(thisObj as? ObjInstance)?.let {
|
||||||
body.callOn(ApplyScope(this, it.instanceScope))
|
body.callOn(ApplyScope(scope, it.instanceScope))
|
||||||
} ?: run {
|
} ?: run {
|
||||||
body.callOn(this)
|
val appliedScope = scope.createChildScope(newThisObj = thisObj)
|
||||||
|
body.callOn(ApplyScope(scope, appliedScope))
|
||||||
}
|
}
|
||||||
thisObj
|
thisObj
|
||||||
}
|
}
|
||||||
@ -735,7 +790,7 @@ open class Obj {
|
|||||||
params = listOf(ParamDoc("block")),
|
params = listOf(ParamDoc("block")),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
|
call(args.firstAndOnly(), Arguments(thisObj))
|
||||||
thisObj
|
thisObj
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
@ -744,25 +799,33 @@ open class Obj {
|
|||||||
params = listOf(ParamDoc("block")),
|
params = listOf(ParamDoc("block")),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
args.firstAndOnly().callOn(this)
|
call(args.firstAndOnly())
|
||||||
}
|
}
|
||||||
addFn("getAt") {
|
addFn("getAt") {
|
||||||
requireExactCount(1)
|
requireExactCount(1)
|
||||||
thisObj.getAt(this, requiredArg<Obj>(0))
|
thisObj.getAt(requireScope(), requiredArg<Obj>(0))
|
||||||
}
|
}
|
||||||
addFn("putAt") {
|
addFn("putAt") {
|
||||||
requireExactCount(2)
|
requireExactCount(2)
|
||||||
val newValue = args[1]
|
val newValue = args[1]
|
||||||
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
thisObj.putAt(requireScope(), requiredArg<Obj>(0), newValue)
|
||||||
newValue
|
newValue
|
||||||
}
|
}
|
||||||
|
addFnDoc(
|
||||||
|
name = "toJson",
|
||||||
|
doc = "Encodes this object to JSON.",
|
||||||
|
returns = type("lyng.String"),
|
||||||
|
moduleName = "lyng.stdlib"
|
||||||
|
) {
|
||||||
|
thisObj.toJson(requireScope()).toString().toObj()
|
||||||
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "toJsonString",
|
name = "toJsonString",
|
||||||
doc = "Encodes this object to a JSON string.",
|
doc = "Encodes this object to a JSON string.",
|
||||||
returns = type("lyng.String"),
|
returns = type("lyng.String"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
thisObj.toJson(this).toString().toObj()
|
thisObj.toJson(requireScope()).toString().toObj()
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "clamp",
|
name = "clamp",
|
||||||
@ -774,12 +837,12 @@ open class Obj {
|
|||||||
|
|
||||||
var result = thisObj
|
var result = thisObj
|
||||||
if (range.start != null && !range.start.isNull) {
|
if (range.start != null && !range.start.isNull) {
|
||||||
if (result.compareTo(this, range.start) < 0) {
|
if (result.compareTo(requireScope(), range.start) < 0) {
|
||||||
result = range.start
|
result = range.start
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (range.end != null && !range.end.isNull) {
|
if (range.end != null && !range.end.isNull) {
|
||||||
val cmp = range.end.compareTo(this, result)
|
val cmp = range.end.compareTo(requireScope(), result)
|
||||||
if (range.isEndInclusive) {
|
if (range.isEndInclusive) {
|
||||||
if (cmp < 0) result = range.end
|
if (cmp < 0) result = range.end
|
||||||
} else {
|
} else {
|
||||||
@ -914,7 +977,14 @@ object ObjNull : Obj() {
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("unset")
|
@SerialName("unset")
|
||||||
object ObjUnset : Obj() {
|
object ObjUnset : Obj() {
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
val resolved = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
return if (resolved === this) 0 else -1
|
||||||
|
}
|
||||||
override fun equals(other: Any?): Boolean = other === this
|
override fun equals(other: Any?): Boolean = other === this
|
||||||
override fun toString(): String = "Unset"
|
override fun toString(): String = "Unset"
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ val ObjArray by lazy {
|
|||||||
doc = "Iterator over elements of this array using its indexer.",
|
doc = "Iterator over elements of this array using its indexer.",
|
||||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) { ObjArrayIterator(thisObj).also { it.init(this) } }
|
) { ObjArrayIterator(thisObj).also { it.init(requireScope()) } }
|
||||||
|
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "contains",
|
name = "contains",
|
||||||
@ -42,9 +42,10 @@ val ObjArray by lazy {
|
|||||||
isOpen = true,
|
isOpen = true,
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
|
val scope = requireScope()
|
||||||
val obj = args.firstAndOnly()
|
val obj = args.firstAndOnly()
|
||||||
for (i in 0..<thisObj.invokeInstanceMethod(this, "size").toInt()) {
|
for (i in 0..<thisObj.invokeInstanceMethod(scope, "size").toInt()) {
|
||||||
if (thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0) return@addFnDoc ObjTrue
|
if (thisObj.getAt(scope, ObjInt(i.toLong())).compareTo(scope, obj) == 0) return@addFnDoc ObjTrue
|
||||||
}
|
}
|
||||||
ObjFalse
|
ObjFalse
|
||||||
}
|
}
|
||||||
@ -55,10 +56,11 @@ val ObjArray by lazy {
|
|||||||
type = type("lyng.Any"),
|
type = type("lyng.Any"),
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib",
|
||||||
getter = {
|
getter = {
|
||||||
|
val scope = requireScope()
|
||||||
this.thisObj.invokeInstanceMethod(
|
this.thisObj.invokeInstanceMethod(
|
||||||
this,
|
scope,
|
||||||
"getAt",
|
"getAt",
|
||||||
(this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
|
(this.thisObj.invokeInstanceMethod(scope, "size").toInt() - 1).toObj()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -68,7 +70,10 @@ val ObjArray by lazy {
|
|||||||
doc = "Index of the last element (size - 1).",
|
doc = "Index of the last element (size - 1).",
|
||||||
type = type("lyng.Int"),
|
type = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib",
|
||||||
getter = { (this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
|
getter = {
|
||||||
|
val scope = requireScope()
|
||||||
|
(this.thisObj.invokeInstanceMethod(scope, "size").toInt() - 1).toObj()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
addPropertyDoc(
|
addPropertyDoc(
|
||||||
@ -76,7 +81,10 @@ val ObjArray by lazy {
|
|||||||
doc = "Range of valid indices for this array.",
|
doc = "Range of valid indices for this array.",
|
||||||
type = type("lyng.Range"),
|
type = type("lyng.Range"),
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib",
|
||||||
getter = { ObjRange(0.toObj(), this.thisObj.invokeInstanceMethod(this, "size"), false) }
|
getter = {
|
||||||
|
val scope = requireScope()
|
||||||
|
ObjRange(0.toObj(), this.thisObj.invokeInstanceMethod(scope, "size"), false)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
@ -86,15 +94,16 @@ val ObjArray by lazy {
|
|||||||
returns = type("lyng.Int"),
|
returns = type("lyng.Int"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
|
val scope = requireScope()
|
||||||
val target = args.firstAndOnly()
|
val target = args.firstAndOnly()
|
||||||
var low = 0
|
var low = 0
|
||||||
var high = thisObj.invokeInstanceMethod(this, "size").toInt() - 1
|
var high = thisObj.invokeInstanceMethod(scope, "size").toInt() - 1
|
||||||
|
|
||||||
while (low <= high) {
|
while (low <= high) {
|
||||||
val mid = (low + high) / 2
|
val mid = (low + high) / 2
|
||||||
val midVal = thisObj.getAt(this, ObjInt(mid.toLong()))
|
val midVal = thisObj.getAt(scope, ObjInt(mid.toLong()))
|
||||||
|
|
||||||
val cmp = midVal.compareTo(this, target)
|
val cmp = midVal.compareTo(scope, target)
|
||||||
when {
|
when {
|
||||||
cmp == 0 -> return@addFnDoc (mid).toObj()
|
cmp == 0 -> return@addFnDoc (mid).toObj()
|
||||||
cmp > 0 -> high = mid - 1
|
cmp > 0 -> high = mid - 1
|
||||||
|
|||||||
@ -38,8 +38,8 @@ class ObjArrayIterator(val array: Obj) : Obj() {
|
|||||||
addFn("next") {
|
addFn("next") {
|
||||||
val self = thisAs<ObjArrayIterator>()
|
val self = thisAs<ObjArrayIterator>()
|
||||||
if (self.nextIndex < self.lastIndex) {
|
if (self.nextIndex < self.lastIndex) {
|
||||||
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
|
self.array.invokeInstanceMethod(requireScope(), "getAt", (self.nextIndex++).toObj())
|
||||||
} else raiseError(ObjIterationFinishedException(this))
|
} else raiseError(ObjIterationFinishedException(requireScope()))
|
||||||
}
|
}
|
||||||
addFn("hasNext") {
|
addFn("hasNext") {
|
||||||
val self = thisAs<ObjArrayIterator>()
|
val self = thisAs<ObjArrayIterator>()
|
||||||
|
|||||||
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -73,6 +73,8 @@ data class ObjBool(val value: Boolean) : Obj() {
|
|||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder,lynonType: LynonType?): Obj {
|
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder,lynonType: LynonType?): Obj {
|
||||||
return ObjBool(decoder.unpackBoolean())
|
return ObjBool(decoder.unpackBoolean())
|
||||||
}
|
}
|
||||||
|
}.apply {
|
||||||
|
isClosed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,7 +61,7 @@ val ObjClassType by lazy {
|
|||||||
val names = mutableListOf<Obj>()
|
val names = mutableListOf<Obj>()
|
||||||
for (c in cls.mro) {
|
for (c in cls.mro) {
|
||||||
for ((n, rec) in c.members) {
|
for ((n, rec) in c.members) {
|
||||||
if (rec.value !is Statement && seen.add(n)) names += ObjString(n)
|
if (rec.type != ObjRecord.Type.Fun && seen.add(n)) names += ObjString(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ObjList(names.toMutableList())
|
ObjList(names.toMutableList())
|
||||||
@ -79,7 +79,7 @@ val ObjClassType by lazy {
|
|||||||
val names = mutableListOf<Obj>()
|
val names = mutableListOf<Obj>()
|
||||||
for (c in cls.mro) {
|
for (c in cls.mro) {
|
||||||
for ((n, rec) in c.members) {
|
for ((n, rec) in c.members) {
|
||||||
if (rec.value is Statement && seen.add(n)) names += ObjString(n)
|
if (rec.type == ObjRecord.Type.Fun && seen.add(n)) names += ObjString(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ObjList(names.toMutableList())
|
ObjList(names.toMutableList())
|
||||||
@ -109,6 +109,7 @@ open class ObjClass(
|
|||||||
var isAnonymous: Boolean = false
|
var isAnonymous: Boolean = false
|
||||||
|
|
||||||
var isAbstract: Boolean = false
|
var isAbstract: Boolean = false
|
||||||
|
var isClosed: Boolean = false
|
||||||
|
|
||||||
// Stable identity and simple structural version for PICs
|
// Stable identity and simple structural version for PICs
|
||||||
val classId: Long = ClassIdGen.nextId()
|
val classId: Long = ClassIdGen.nextId()
|
||||||
@ -118,39 +119,45 @@ open class ObjClass(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of public member names to their effective storage keys in instanceScope.objects.
|
* Map of public member names to their effective storage keys in instanceScope.objects.
|
||||||
* This is pre-calculated to avoid MRO traversal and string concatenation during common access.
|
* Cached and invalidated by layoutVersion to reflect newly added members.
|
||||||
*/
|
*/
|
||||||
val publicMemberResolution: Map<String, String> by lazy {
|
private var publicMemberResolutionVersion: Int = -1
|
||||||
val res = mutableMapOf<String, String>()
|
private var publicMemberResolutionCache: Map<String, String> = emptyMap()
|
||||||
// Traverse MRO in REVERSED order so that child classes override parent classes in the map.
|
val publicMemberResolution: Map<String, String>
|
||||||
for (cls in mro.reversed()) {
|
get() {
|
||||||
if (cls.className == "Obj") continue
|
if (publicMemberResolutionVersion == layoutVersion) return publicMemberResolutionCache
|
||||||
for ((name, rec) in cls.members) {
|
val res = mutableMapOf<String, String>()
|
||||||
if (rec.visibility == Visibility.Public) {
|
// Traverse MRO in REVERSED order so that child classes override parent classes in the map.
|
||||||
val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
for (cls in mro.reversed()) {
|
||||||
res[name] = key
|
if (cls.className == "Obj") continue
|
||||||
}
|
for ((name, rec) in cls.members) {
|
||||||
}
|
if (rec.visibility == Visibility.Public) {
|
||||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||||
if (rec.visibility == Visibility.Public && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
|
res[name] = key
|
||||||
val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
}
|
||||||
res[name] = key
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
|
if (rec.visibility == Visibility.Public && (rec.type == ObjRecord.Type.Fun || rec.type == ObjRecord.Type.Delegated)) {
|
||||||
|
val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||||
|
res[name] = key
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
publicMemberResolutionCache = res
|
||||||
|
publicMemberResolutionVersion = layoutVersion
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
val classNameObj by lazy { ObjString(className) }
|
val classNameObj by lazy { ObjString(className) }
|
||||||
|
|
||||||
var constructorMeta: ArgsDeclaration? = null
|
var constructorMeta: ArgsDeclaration? = null
|
||||||
var instanceConstructor: Statement? = null
|
var instanceConstructor: Obj? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per-instance initializers collected from class body (for instance fields). These are executed
|
* Per-instance initializers collected from class body (for instance fields). These are executed
|
||||||
* during construction in the instance scope of the object, once per class in the hierarchy.
|
* during construction in the instance scope of the object, once per class in the hierarchy.
|
||||||
*/
|
*/
|
||||||
val instanceInitializers: MutableList<Statement> = mutableListOf()
|
val instanceInitializers: MutableList<Obj> = mutableListOf()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the scope for class methods, initialize class vars, etc.
|
* the scope for class methods, initialize class vars, etc.
|
||||||
@ -269,6 +276,11 @@ open class ObjClass(
|
|||||||
internal data class FieldSlot(val slot: Int, val record: ObjRecord)
|
internal data class FieldSlot(val slot: Int, val record: ObjRecord)
|
||||||
internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass)
|
internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass)
|
||||||
internal data class MethodSlot(val slot: Int, val record: ObjRecord)
|
internal data class MethodSlot(val slot: Int, val record: ObjRecord)
|
||||||
|
private var nextFieldId: Int = 0
|
||||||
|
private var nextMethodId: Int = 0
|
||||||
|
private val fieldIdMap: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
private val methodIdMap: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
private var methodIdSeeded: Boolean = false
|
||||||
private var fieldSlotLayoutVersion: Int = -1
|
private var fieldSlotLayoutVersion: Int = -1
|
||||||
private var fieldSlotMap: Map<String, FieldSlot> = emptyMap()
|
private var fieldSlotMap: Map<String, FieldSlot> = emptyMap()
|
||||||
private var fieldSlotCount: Int = 0
|
private var fieldSlotCount: Int = 0
|
||||||
@ -278,22 +290,40 @@ open class ObjClass(
|
|||||||
private var methodSlotMap: Map<String, MethodSlot> = emptyMap()
|
private var methodSlotMap: Map<String, MethodSlot> = emptyMap()
|
||||||
private var methodSlotCount: Int = 0
|
private var methodSlotCount: Int = 0
|
||||||
|
|
||||||
|
/** Kotlin bridge class-level storage (no name lookup). */
|
||||||
|
internal var kotlinClassData: Any? = null
|
||||||
|
|
||||||
|
/** Kotlin bridge instance init hooks. */
|
||||||
|
internal var bridgeInitHooks: MutableList<suspend (net.sergeych.lyng.ScopeFacade, ObjInstance) -> Unit>? = null
|
||||||
|
|
||||||
|
internal var instanceTemplateBuilt: Boolean = false
|
||||||
|
|
||||||
private fun ensureFieldSlots(): Map<String, FieldSlot> {
|
private fun ensureFieldSlots(): Map<String, FieldSlot> {
|
||||||
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
|
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
|
||||||
val res = mutableMapOf<String, FieldSlot>()
|
val res = mutableMapOf<String, FieldSlot>()
|
||||||
var idx = 0
|
var maxId = -1
|
||||||
for (cls in mro) {
|
for (cls in mro) {
|
||||||
for ((name, rec) in cls.members) {
|
for ((name, rec) in cls.members) {
|
||||||
if (rec.isAbstract) continue
|
if (rec.isAbstract) continue
|
||||||
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
|
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
|
||||||
val key = cls.mangledName(name)
|
val key = cls.mangledName(name)
|
||||||
if (res.containsKey(key)) continue
|
if (res.containsKey(key)) continue
|
||||||
res[key] = FieldSlot(idx, rec)
|
val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||||
idx += 1
|
res[key] = FieldSlot(fieldId, rec)
|
||||||
|
if (fieldId > maxId) maxId = fieldId
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
|
if (rec.isAbstract) return@forEach
|
||||||
|
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
|
||||||
|
val key = cls.mangledName(name)
|
||||||
|
if (res.containsKey(key)) return@forEach
|
||||||
|
val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||||
|
res[key] = FieldSlot(fieldId, rec)
|
||||||
|
if (fieldId > maxId) maxId = fieldId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fieldSlotMap = res
|
fieldSlotMap = res
|
||||||
fieldSlotCount = idx
|
fieldSlotCount = maxId + 1
|
||||||
fieldSlotLayoutVersion = layoutVersion
|
fieldSlotLayoutVersion = layoutVersion
|
||||||
return fieldSlotMap
|
return fieldSlotMap
|
||||||
}
|
}
|
||||||
@ -302,7 +332,6 @@ open class ObjClass(
|
|||||||
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
|
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
|
||||||
val res = mutableMapOf<String, ResolvedMember>()
|
val res = mutableMapOf<String, ResolvedMember>()
|
||||||
for (cls in mro) {
|
for (cls in mro) {
|
||||||
if (cls.className == "Obj") break
|
|
||||||
for ((name, rec) in cls.members) {
|
for ((name, rec) in cls.members) {
|
||||||
if (rec.isAbstract) continue
|
if (rec.isAbstract) continue
|
||||||
if (res.containsKey(name)) continue
|
if (res.containsKey(name)) continue
|
||||||
@ -324,35 +353,36 @@ open class ObjClass(
|
|||||||
private fun ensureMethodSlots(): Map<String, MethodSlot> {
|
private fun ensureMethodSlots(): Map<String, MethodSlot> {
|
||||||
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
|
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
|
||||||
val res = mutableMapOf<String, MethodSlot>()
|
val res = mutableMapOf<String, MethodSlot>()
|
||||||
var idx = 0
|
var maxId = -1
|
||||||
for (cls in mro) {
|
for (cls in mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
for ((name, rec) in cls.members) {
|
for ((name, rec) in cls.members) {
|
||||||
if (rec.isAbstract) continue
|
if (rec.isAbstract) continue
|
||||||
if (rec.value !is Statement &&
|
if (rec.type != ObjRecord.Type.Delegated &&
|
||||||
rec.type != ObjRecord.Type.Delegated &&
|
|
||||||
rec.type != ObjRecord.Type.Fun &&
|
rec.type != ObjRecord.Type.Fun &&
|
||||||
rec.type != ObjRecord.Type.Property) {
|
rec.type != ObjRecord.Type.Property) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||||
if (res.containsKey(key)) continue
|
if (res.containsKey(key)) continue
|
||||||
res[key] = MethodSlot(idx, rec)
|
val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
idx += 1
|
res[key] = MethodSlot(methodId, rec)
|
||||||
|
if (methodId > maxId) maxId = methodId
|
||||||
}
|
}
|
||||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
if (rec.isAbstract) return@forEach
|
if (rec.isAbstract) return@forEach
|
||||||
if (rec.value !is Statement &&
|
if (rec.type != ObjRecord.Type.Delegated &&
|
||||||
rec.type != ObjRecord.Type.Delegated &&
|
rec.type != ObjRecord.Type.Property &&
|
||||||
rec.type != ObjRecord.Type.Property) return@forEach
|
rec.type != ObjRecord.Type.Fun) return@forEach
|
||||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||||
if (res.containsKey(key)) return@forEach
|
if (res.containsKey(key)) return@forEach
|
||||||
res[key] = MethodSlot(idx, rec)
|
val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
idx += 1
|
res[key] = MethodSlot(methodId, rec)
|
||||||
|
if (methodId > maxId) maxId = methodId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
methodSlotMap = res
|
methodSlotMap = res
|
||||||
methodSlotCount = idx
|
methodSlotCount = maxId + 1
|
||||||
methodSlotLayoutVersion = layoutVersion
|
methodSlotLayoutVersion = layoutVersion
|
||||||
return methodSlotMap
|
return methodSlotMap
|
||||||
}
|
}
|
||||||
@ -368,6 +398,19 @@ open class ObjClass(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots()
|
internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots()
|
||||||
|
internal fun fieldRecordForId(fieldId: Int): ObjRecord? {
|
||||||
|
ensureFieldSlots()
|
||||||
|
fieldSlotMap.values.firstOrNull { it.slot == fieldId }?.record?.let { return it }
|
||||||
|
// Fallback: resolve by id through name mapping if slot map is stale.
|
||||||
|
val name = fieldIdMap.entries.firstOrNull { it.value == fieldId }?.key
|
||||||
|
if (name != null) {
|
||||||
|
for (cls in mro) {
|
||||||
|
cls.members[name]?.let { return it }
|
||||||
|
cls.classScope?.objects?.get(name)?.let { return it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name]
|
internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name]
|
||||||
internal fun methodSlotCount(): Int {
|
internal fun methodSlotCount(): Int {
|
||||||
ensureMethodSlots()
|
ensureMethodSlots()
|
||||||
@ -378,6 +421,157 @@ open class ObjClass(
|
|||||||
return methodSlotMap[key]
|
return methodSlotMap[key]
|
||||||
}
|
}
|
||||||
internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots()
|
internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots()
|
||||||
|
internal fun methodRecordForId(methodId: Int): ObjRecord? {
|
||||||
|
ensureMethodSlots()
|
||||||
|
methodSlotMap.values.firstOrNull { it.slot == methodId }?.record?.let { return it }
|
||||||
|
// Fallback to scanning the MRO in case a parent method id was added after slot cache creation.
|
||||||
|
for (cls in mro) {
|
||||||
|
for ((_, rec) in cls.members) {
|
||||||
|
if (rec.methodId == methodId) return rec
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (_, rec) ->
|
||||||
|
if (rec.methodId == methodId) return rec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Final fallback: resolve by id through name mapping if slot map is stale.
|
||||||
|
val name = methodIdMap.entries.firstOrNull { it.value == methodId }?.key
|
||||||
|
if (name != null) {
|
||||||
|
for (cls in mro) {
|
||||||
|
cls.members[name]?.let { return it }
|
||||||
|
cls.classScope?.objects?.get(name)?.let { return it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun instanceFieldIdMap(): Map<String, Int> {
|
||||||
|
val result = mutableMapOf<String, Int>()
|
||||||
|
for (cls in mro) {
|
||||||
|
if (cls.className == "Obj") break
|
||||||
|
for ((name, rec) in cls.members) {
|
||||||
|
if (rec.isAbstract) continue
|
||||||
|
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
|
||||||
|
if (rec.visibility == Visibility.Private) continue
|
||||||
|
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||||
|
if (!result.containsKey(name)) result[name] = id
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
|
if (rec.isAbstract) return@forEach
|
||||||
|
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
|
||||||
|
if (rec.visibility == Visibility.Private) return@forEach
|
||||||
|
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||||
|
if (!result.containsKey(name)) result[name] = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun instanceMethodIdMap(includeAbstract: Boolean = false): Map<String, Int> {
|
||||||
|
val result = mutableMapOf<String, Int>()
|
||||||
|
for (cls in mro) {
|
||||||
|
for ((name, rec) in cls.members) {
|
||||||
|
if (!includeAbstract && rec.isAbstract) continue
|
||||||
|
if (rec.visibility == Visibility.Private) continue
|
||||||
|
if (rec.type != ObjRecord.Type.Fun &&
|
||||||
|
rec.type != ObjRecord.Type.Property &&
|
||||||
|
rec.type != ObjRecord.Type.Delegated) continue
|
||||||
|
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
|
if (!result.containsKey(name)) result[name] = id
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
|
if (!includeAbstract && rec.isAbstract) return@forEach
|
||||||
|
if (rec.visibility == Visibility.Private) return@forEach
|
||||||
|
if (rec.type != ObjRecord.Type.Fun &&
|
||||||
|
rec.type != ObjRecord.Type.Property &&
|
||||||
|
rec.type != ObjRecord.Type.Delegated) return@forEach
|
||||||
|
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
|
if (!result.containsKey(name)) result[name] = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignFieldId(name: String, rec: ObjRecord): Int {
|
||||||
|
val existingId = rec.fieldId
|
||||||
|
if (existingId != null) {
|
||||||
|
fieldIdMap[name] = existingId
|
||||||
|
return existingId
|
||||||
|
}
|
||||||
|
val id = fieldIdMap[name] ?: run {
|
||||||
|
val next = nextFieldId++
|
||||||
|
fieldIdMap[name] = next
|
||||||
|
// Field id map affects slot layout; invalidate caches when a new id is assigned.
|
||||||
|
layoutVersion += 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assignMethodId(name: String, rec: ObjRecord): Int {
|
||||||
|
ensureMethodIdSeeded()
|
||||||
|
val existingId = rec.methodId
|
||||||
|
if (existingId != null) {
|
||||||
|
methodIdMap[name] = existingId
|
||||||
|
if (existingId >= nextMethodId) {
|
||||||
|
nextMethodId = existingId + 1
|
||||||
|
}
|
||||||
|
return existingId
|
||||||
|
}
|
||||||
|
val id = methodIdMap[name] ?: run {
|
||||||
|
val next = nextMethodId++
|
||||||
|
methodIdMap[name] = next
|
||||||
|
// Method id map affects slot layout; invalidate caches when a new id is assigned.
|
||||||
|
layoutVersion += 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
if (id >= nextMethodId) {
|
||||||
|
nextMethodId = id + 1
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ensureMethodIdForBridge(name: String, rec: ObjRecord): Int = assignMethodId(name, rec)
|
||||||
|
|
||||||
|
private fun ensureMethodIdSeeded() {
|
||||||
|
if (methodIdSeeded) return
|
||||||
|
var maxId = -1
|
||||||
|
for (cls in mroParents) {
|
||||||
|
for ((name, rec) in cls.members) {
|
||||||
|
if (rec.type != ObjRecord.Type.Fun &&
|
||||||
|
rec.type != ObjRecord.Type.Property &&
|
||||||
|
rec.type != ObjRecord.Type.Delegated
|
||||||
|
) continue
|
||||||
|
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
|
if (!methodIdMap.containsKey(name)) methodIdMap[name] = id
|
||||||
|
if (id > maxId) maxId = id
|
||||||
|
}
|
||||||
|
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||||
|
if (rec.type != ObjRecord.Type.Fun &&
|
||||||
|
rec.type != ObjRecord.Type.Property &&
|
||||||
|
rec.type != ObjRecord.Type.Delegated
|
||||||
|
) return@forEach
|
||||||
|
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||||
|
if (!methodIdMap.containsKey(name)) methodIdMap[name] = id
|
||||||
|
if (id > maxId) maxId = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextMethodId <= maxId) {
|
||||||
|
nextMethodId = maxId + 1
|
||||||
|
}
|
||||||
|
methodIdSeeded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun replaceMemberForBridge(name: String, newRecord: ObjRecord) {
|
||||||
|
members[name] = newRecord
|
||||||
|
layoutVersion += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun replaceClassScopeMemberForBridge(name: String, newRecord: ObjRecord) {
|
||||||
|
initClassScope()
|
||||||
|
classScope!!.objects[name] = newRecord
|
||||||
|
layoutVersion += 1
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String = className
|
override fun toString(): String = className
|
||||||
|
|
||||||
@ -396,7 +590,7 @@ open class ObjClass(
|
|||||||
for (cls in mro) {
|
for (cls in mro) {
|
||||||
// 1) members-defined methods and fields
|
// 1) members-defined methods and fields
|
||||||
for ((k, v) in cls.members) {
|
for ((k, v) in cls.members) {
|
||||||
if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField)) {
|
if (!v.isAbstract && (v.type == ObjRecord.Type.Fun || v.type == ObjRecord.Type.Property || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField)) {
|
||||||
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
|
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
|
||||||
if (!res.containsKey(key)) {
|
if (!res.containsKey(key)) {
|
||||||
res[key] = v
|
res[key] = v
|
||||||
@ -407,7 +601,7 @@ open class ObjClass(
|
|||||||
cls.classScope?.objects?.forEach { (k, rec) ->
|
cls.classScope?.objects?.forEach { (k, rec) ->
|
||||||
// ONLY copy methods and delegated members from class scope to instance scope.
|
// ONLY copy methods and delegated members from class scope to instance scope.
|
||||||
// Fields in class scope are static fields and must NOT be per-instance.
|
// Fields in class scope are static fields and must NOT be per-instance.
|
||||||
if (!rec.isAbstract && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
|
if (!rec.isAbstract && (rec.type == ObjRecord.Type.Fun || rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
||||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
|
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
|
||||||
// if not already present, copy reference for dispatch
|
// if not already present, copy reference for dispatch
|
||||||
if (!res.containsKey(key)) {
|
if (!res.containsKey(key)) {
|
||||||
@ -416,6 +610,7 @@ open class ObjClass(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
instanceTemplateBuilt = true
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,6 +634,11 @@ open class ObjClass(
|
|||||||
val stableParent = classScope ?: scope.parent
|
val stableParent = classScope ?: scope.parent
|
||||||
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
|
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
|
||||||
instance.instanceScope.currentClassCtx = null
|
instance.instanceScope.currentClassCtx = null
|
||||||
|
val classCaptureRecords = classScope?.captureRecords
|
||||||
|
if (classCaptureRecords != null) {
|
||||||
|
instance.instanceScope.captureRecords = classCaptureRecords
|
||||||
|
instance.instanceScope.captureNames = classScope?.captureNames
|
||||||
|
}
|
||||||
val fieldSlots = fieldSlotMap()
|
val fieldSlots = fieldSlotMap()
|
||||||
if (fieldSlots.isNotEmpty()) {
|
if (fieldSlots.isNotEmpty()) {
|
||||||
instance.initFieldSlots(fieldSlotCount())
|
instance.initFieldSlots(fieldSlotCount())
|
||||||
@ -493,6 +693,14 @@ open class ObjClass(
|
|||||||
args: Arguments?,
|
args: Arguments?,
|
||||||
runConstructors: Boolean
|
runConstructors: Boolean
|
||||||
) {
|
) {
|
||||||
|
bridgeInitHooks?.let { hooks ->
|
||||||
|
if (hooks.isNotEmpty()) {
|
||||||
|
val facade = instance.instanceScope.asFacade()
|
||||||
|
for (hook in hooks) {
|
||||||
|
hook(facade, instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val visited = hashSetOf<ObjClass>()
|
val visited = hashSetOf<ObjClass>()
|
||||||
initClassInternal(instance, visited, this, args, isRoot = true, runConstructors = runConstructors)
|
initClassInternal(instance, visited, this, args, isRoot = true, runConstructors = runConstructors)
|
||||||
}
|
}
|
||||||
@ -578,7 +786,11 @@ open class ObjClass(
|
|||||||
instance.instanceScope.currentClassCtx = c
|
instance.instanceScope.currentClassCtx = c
|
||||||
try {
|
try {
|
||||||
for (initStmt in c.instanceInitializers) {
|
for (initStmt in c.instanceInitializers) {
|
||||||
initStmt.execute(instance.instanceScope)
|
if (initStmt is net.sergeych.lyng.Statement) {
|
||||||
|
executeBytecodeWithSeed(instance.instanceScope, initStmt, "instance init")
|
||||||
|
} else {
|
||||||
|
initStmt.callOn(instance.instanceScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
instance.instanceScope.currentClassCtx = savedCtx
|
instance.instanceScope.currentClassCtx = savedCtx
|
||||||
@ -589,7 +801,7 @@ open class ObjClass(
|
|||||||
c.instanceConstructor?.let { ctor ->
|
c.instanceConstructor?.let { ctor ->
|
||||||
val execScope =
|
val execScope =
|
||||||
instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance)
|
instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance)
|
||||||
ctor.execute(execScope)
|
ctor.callOn(execScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -612,12 +824,15 @@ open class ObjClass(
|
|||||||
isOverride: Boolean = false,
|
isOverride: Boolean = false,
|
||||||
isTransient: Boolean = false,
|
isTransient: Boolean = false,
|
||||||
type: ObjRecord.Type = ObjRecord.Type.Field,
|
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||||
|
fieldId: Int? = null,
|
||||||
|
methodId: Int? = null,
|
||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
// Validation of override rules: only for non-system declarations
|
// Validation of override rules: only for non-system declarations
|
||||||
|
var existing: ObjRecord? = null
|
||||||
|
var actualOverride = false
|
||||||
if (pos != Pos.builtIn) {
|
if (pos != Pos.builtIn) {
|
||||||
// Only consider TRUE instance members from ancestors for overrides
|
// Only consider TRUE instance members from ancestors for overrides
|
||||||
val existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
|
existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
|
||||||
var actualOverride = false
|
|
||||||
if (existing != null && existing.declaringClass != this) {
|
if (existing != null && existing.declaringClass != this) {
|
||||||
// If the existing member is private in the ancestor, it's not visible for overriding.
|
// 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.
|
// It should be treated as a new member in this class.
|
||||||
@ -648,6 +863,56 @@ open class ObjClass(
|
|||||||
throw ScriptError(pos, "$name is already defined in $objClass")
|
throw ScriptError(pos, "$name is already defined in $objClass")
|
||||||
|
|
||||||
// Install/override in this class
|
// Install/override in this class
|
||||||
|
val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
|
||||||
|
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
|
||||||
|
fieldIdMap[name] = nextFieldId
|
||||||
|
nextFieldId++
|
||||||
|
fieldIdMap[name]!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fieldId
|
||||||
|
}
|
||||||
|
val inheritedCandidate = run {
|
||||||
|
var found: ObjRecord? = null
|
||||||
|
for (cls in mro) {
|
||||||
|
if (cls === this) continue
|
||||||
|
if (cls.className == "Obj") break
|
||||||
|
cls.members[name]?.let {
|
||||||
|
found = it
|
||||||
|
return@run found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
found
|
||||||
|
}
|
||||||
|
if (type == ObjRecord.Type.Fun ||
|
||||||
|
type == ObjRecord.Type.Property ||
|
||||||
|
type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
ensureMethodIdSeeded()
|
||||||
|
}
|
||||||
|
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
|
||||||
|
type == ObjRecord.Type.Property ||
|
||||||
|
type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
val inherited = if (actualOverride) {
|
||||||
|
existing?.methodId
|
||||||
|
} else {
|
||||||
|
val candidate = inheritedCandidate
|
||||||
|
if (candidate != null &&
|
||||||
|
candidate.declaringClass != this &&
|
||||||
|
(candidate.visibility.isPublic || canAccessMember(candidate.visibility, candidate.declaringClass, this, name))
|
||||||
|
) {
|
||||||
|
candidate.methodId
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
|
||||||
|
methodIdMap[name] = nextMethodId
|
||||||
|
nextMethodId++
|
||||||
|
methodIdMap[name]!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
methodId
|
||||||
|
}
|
||||||
val rec = ObjRecord(
|
val rec = ObjRecord(
|
||||||
initialValue, isMutable, visibility, writeVisibility,
|
initialValue, isMutable, visibility, writeVisibility,
|
||||||
declaringClass = declaringClass,
|
declaringClass = declaringClass,
|
||||||
@ -655,7 +920,10 @@ open class ObjClass(
|
|||||||
isClosed = isClosed,
|
isClosed = isClosed,
|
||||||
isOverride = isOverride,
|
isOverride = isOverride,
|
||||||
isTransient = isTransient,
|
isTransient = isTransient,
|
||||||
type = type
|
type = type,
|
||||||
|
memberName = name,
|
||||||
|
fieldId = effectiveFieldId,
|
||||||
|
methodId = effectiveMethodId
|
||||||
)
|
)
|
||||||
members[name] = rec
|
members[name] = rec
|
||||||
// Structural change: bump layout version for PIC invalidation
|
// Structural change: bump layout version for PIC invalidation
|
||||||
@ -676,13 +944,52 @@ open class ObjClass(
|
|||||||
writeVisibility: Visibility? = null,
|
writeVisibility: Visibility? = null,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
isTransient: Boolean = false,
|
isTransient: Boolean = false,
|
||||||
type: ObjRecord.Type = ObjRecord.Type.Field
|
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||||
|
fieldId: Int? = null,
|
||||||
|
methodId: Int? = null
|
||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
initClassScope()
|
initClassScope()
|
||||||
val existing = classScope!!.objects[name]
|
val existing = classScope!!.objects[name]
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
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, isTransient = isTransient)
|
val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
|
||||||
|
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
|
||||||
|
fieldIdMap[name] = nextFieldId
|
||||||
|
nextFieldId++
|
||||||
|
fieldIdMap[name]!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fieldId
|
||||||
|
}
|
||||||
|
if (type == ObjRecord.Type.Fun ||
|
||||||
|
type == ObjRecord.Type.Property ||
|
||||||
|
type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
ensureMethodIdSeeded()
|
||||||
|
}
|
||||||
|
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
|
||||||
|
type == ObjRecord.Type.Property ||
|
||||||
|
type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
methodId ?: methodIdMap[name]?.let { it } ?: run {
|
||||||
|
methodIdMap[name] = nextMethodId
|
||||||
|
nextMethodId++
|
||||||
|
methodIdMap[name]!!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
methodId
|
||||||
|
}
|
||||||
|
val rec = classScope!!.addItem(
|
||||||
|
name,
|
||||||
|
isMutable,
|
||||||
|
initialValue,
|
||||||
|
visibility,
|
||||||
|
writeVisibility,
|
||||||
|
recordType = type,
|
||||||
|
isTransient = isTransient,
|
||||||
|
fieldId = effectiveFieldId,
|
||||||
|
methodId = effectiveMethodId
|
||||||
|
)
|
||||||
// Structural change: bump layout version for PIC invalidation
|
// Structural change: bump layout version for PIC invalidation
|
||||||
layoutVersion += 1
|
layoutVersion += 1
|
||||||
return rec
|
return rec
|
||||||
@ -698,13 +1005,15 @@ open class ObjClass(
|
|||||||
isClosed: Boolean = false,
|
isClosed: Boolean = false,
|
||||||
isOverride: Boolean = false,
|
isOverride: Boolean = false,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
code: (suspend Scope.() -> Obj)? = null
|
methodId: Int? = null,
|
||||||
|
code: (suspend net.sergeych.lyng.ScopeFacade.() -> Obj)? = null
|
||||||
) {
|
) {
|
||||||
val stmt = code?.let { statement { it() } } ?: ObjNull
|
val stmt = code?.let { ObjExternCallable.fromBridge { it() } } ?: ObjNull
|
||||||
createField(
|
createField(
|
||||||
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
||||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||||
type = ObjRecord.Type.Fun
|
type = ObjRecord.Type.Fun,
|
||||||
|
methodId = methodId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -712,8 +1021,8 @@ open class ObjClass(
|
|||||||
|
|
||||||
fun addProperty(
|
fun addProperty(
|
||||||
name: String,
|
name: String,
|
||||||
getter: (suspend Scope.() -> Obj)? = null,
|
getter: (suspend net.sergeych.lyng.ScopeFacade.() -> Obj)? = null,
|
||||||
setter: (suspend Scope.(Obj) -> Unit)? = null,
|
setter: (suspend net.sergeych.lyng.ScopeFacade.(Obj) -> Unit)? = null,
|
||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
writeVisibility: Visibility? = null,
|
writeVisibility: Visibility? = null,
|
||||||
declaringClass: ObjClass? = this,
|
declaringClass: ObjClass? = this,
|
||||||
@ -721,21 +1030,23 @@ open class ObjClass(
|
|||||||
isClosed: Boolean = false,
|
isClosed: Boolean = false,
|
||||||
isOverride: Boolean = false,
|
isOverride: Boolean = false,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
prop: ObjProperty? = null
|
prop: ObjProperty? = null,
|
||||||
|
methodId: Int? = null
|
||||||
) {
|
) {
|
||||||
val g = getter?.let { statement { it() } }
|
val g = getter?.let { ObjExternCallable.fromBridge { it() } }
|
||||||
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
|
val s = setter?.let { ObjExternCallable.fromBridge { it(requiredArg(0)); ObjVoid } }
|
||||||
val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s)
|
val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s)
|
||||||
createField(
|
createField(
|
||||||
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
|
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
|
||||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||||
type = ObjRecord.Type.Property
|
type = ObjRecord.Type.Property,
|
||||||
|
methodId = methodId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
||||||
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend net.sergeych.lyng.ScopeFacade.() -> Obj) {
|
||||||
createClassField(name, statement { code() }, isOpen, type = ObjRecord.Type.Fun)
|
createClassField(name, ObjExternCallable.fromBridge { code() }, isOpen, type = ObjRecord.Type.Fun)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import kotlinx.datetime.*
|
|||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
|
||||||
import net.sergeych.lyng.miniast.addClassFnDoc
|
import net.sergeych.lyng.miniast.addClassFnDoc
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addFnDoc
|
||||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
@ -47,14 +46,18 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
|
|||||||
if (rec != null) {
|
if (rec != null) {
|
||||||
if (rec.type == ObjRecord.Type.Property) {
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
val prop = rec.value as? ObjProperty
|
val prop = rec.value as? ObjProperty
|
||||||
?: (rec.value as? Statement)?.execute(scope) as? ObjProperty
|
|
||||||
if (prop != null) {
|
if (prop != null) {
|
||||||
return ObjRecord(prop.callGetter(scope, this, rec.declaringClass ?: cls), rec.isMutable)
|
return ObjRecord(prop.callGetter(scope, this, rec.declaringClass ?: cls), rec.isMutable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rec.type == ObjRecord.Type.Fun || rec.value is Statement) {
|
if (rec.type == ObjRecord.Type.Fun) {
|
||||||
val s = rec.value as Statement
|
val target = rec.value
|
||||||
return ObjRecord(net.sergeych.lyng.statement { s.execute(this.createChildScope(newThisObj = this@ObjDateTime)) }, rec.isMutable)
|
return ObjRecord(
|
||||||
|
ObjExternCallable.fromBridge {
|
||||||
|
call(target, args, newThisObj = this@ObjDateTime)
|
||||||
|
},
|
||||||
|
rec.isMutable
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return resolveRecord(scope, rec, name, rec.declaringClass ?: cls)
|
return resolveRecord(scope, rec, name, rec.declaringClass ?: cls)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,9 +18,7 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
import net.sergeych.lyng.Arguments
|
||||||
import net.sergeych.lyng.ClosureScope
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
|
||||||
|
|
||||||
class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
|
class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
|
||||||
override val objClass: ObjClass get() = type
|
override val objClass: ObjClass get() = type
|
||||||
@ -52,7 +50,7 @@ class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
|
|||||||
* Object that delegates all its field access/invocation operations to a callback. It is used to implement dynamic
|
* Object that delegates all its field access/invocation operations to a callback. It is used to implement dynamic
|
||||||
* objects using "dynamic" keyword.
|
* objects using "dynamic" keyword.
|
||||||
*/
|
*/
|
||||||
open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: Statement? = null) : Obj() {
|
open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = null) : Obj() {
|
||||||
|
|
||||||
override val objClass: ObjClass get() = type
|
override val objClass: ObjClass get() = type
|
||||||
// Capture the lexical scope used to build this dynamic so callbacks can see outer locals
|
// Capture the lexical scope used to build this dynamic so callbacks can see outer locals
|
||||||
@ -63,8 +61,8 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St
|
|||||||
* with method invocation which is implemented separately in [invokeInstanceMethod] below.
|
* with method invocation which is implemented separately in [invokeInstanceMethod] below.
|
||||||
*/
|
*/
|
||||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
return readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name))))?.let {
|
return readCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name))))?.let {
|
||||||
if (writeCallback != null)
|
if (writeCallback != null)
|
||||||
it.asMutable
|
it.asMutable
|
||||||
else
|
else
|
||||||
@ -83,33 +81,33 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St
|
|||||||
args: Arguments,
|
args: Arguments,
|
||||||
onNotFoundResult: (suspend () -> Obj?)?
|
onNotFoundResult: (suspend () -> Obj?)?
|
||||||
): Obj {
|
): Obj {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
val over = readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name))))
|
val over = readCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name))))
|
||||||
return over?.invoke(scope, scope.thisObj, args)
|
return over?.invoke(scope, scope.thisObj, args)
|
||||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
writeCallback?.execute(execBase.createChildScope(Arguments(ObjString(name), newValue)))
|
writeCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name), newValue)))
|
||||||
?: super.writeField(scope, name, newValue)
|
?: super.writeField(scope, name, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
return readCallback?.execute(execBase.createChildScope(Arguments(index)))
|
return readCallback?.callOn(execBase.createChildScope(Arguments(index)))
|
||||||
?: super.getAt(scope, index)
|
?: super.getAt(scope, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
writeCallback?.execute(execBase.createChildScope(Arguments(index, newValue)))
|
writeCallback?.callOn(execBase.createChildScope(Arguments(index, newValue)))
|
||||||
?: super.putAt(scope, index, newValue)
|
?: super.putAt(scope, index, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
suspend fun create(scope: Scope, builder: Statement): ObjDynamic {
|
suspend fun create(scope: Scope, builder: Obj): ObjDynamic {
|
||||||
val delegate = ObjDynamic()
|
val delegate = ObjDynamic()
|
||||||
val context = ObjDynamicContext(delegate)
|
val context = ObjDynamicContext(delegate)
|
||||||
// Capture the function's lexical scope (scope) so callbacks can see outer locals like parameters.
|
// Capture the function's lexical scope (scope) so callbacks can see outer locals like parameters.
|
||||||
@ -117,11 +115,15 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St
|
|||||||
val buildScope = scope.createChildScope(newThisObj = context)
|
val buildScope = scope.createChildScope(newThisObj = context)
|
||||||
// Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames
|
// Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames
|
||||||
delegate.builderScope = scope.snapshotForClosure()
|
delegate.builderScope = scope.snapshotForClosure()
|
||||||
builder.execute(buildScope)
|
builder.callOn(buildScope)
|
||||||
return delegate
|
return delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
val type = object : ObjClass("Delegate") {}.apply {
|
val type = object : ObjClass("Delegate") {}.apply {
|
||||||
|
addFn("getValue") { raiseError("Delegate.getValue is not implemented") }
|
||||||
|
addFn("setValue") { raiseError("Delegate.setValue is not implemented") }
|
||||||
|
addFn("invoke") { raiseError("Delegate.invoke is not implemented") }
|
||||||
|
addFn("bind") { raiseError("Delegate.bind is not implemented") }
|
||||||
// addClassConst("IndexGetName", operatorGetName)
|
// addClassConst("IndexGetName", operatorGetName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user