abstract classes, interfaces, MI auto implementation and fine-grained visibility
This commit is contained in:
parent
96e1ffc7d5
commit
11eadc1d9f
@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
|
|
||||||
|
- Language: Abstract Classes and Interfaces
|
||||||
|
- Support for `abstract` modifier on classes, methods, and variables.
|
||||||
|
- Introduced `interface` as a synonym for `abstract class`, supporting full state (constructors, fields, `init` blocks) and implementation by parts via MI.
|
||||||
|
- New `closed` modifier (antonym to `open`) to prevent overriding class members.
|
||||||
|
- Refined `override` logic: mandatory keyword when re-declaring members that exist in the ancestor chain (MRO).
|
||||||
|
- MI Satisfaction: Abstract requirements are automatically satisfied by matching concrete members found later in the C3 MRO chain without requiring explicit proxy methods.
|
||||||
|
- Integration: Updated highlighters (lynglib, lyngweb, IDEA plugin), IDEA completion, and Grazie grammar checking.
|
||||||
|
- Documentation: Updated `docs/OOP.md` with sections on "Abstract Classes and Members", "Interfaces", and "Overriding and Virtual Dispatch".
|
||||||
|
|
||||||
- Language: Class properties with accessors
|
- Language: Class properties with accessors
|
||||||
- Support for `val` (read-only) and `var` (read-write) properties in classes.
|
- Support for `val` (read-only) and `var` (read-write) properties in classes.
|
||||||
- Syntax: `val name [ : Type ] get() { body }` or `var name [ : Type ] get() { body } set(value) { body }`.
|
- Syntax: `val name [ : Type ] get() { body }` or `var name [ : Type ] get() { body } set(value) { body }`.
|
||||||
|
|||||||
138
docs/OOP.md
138
docs/OOP.md
@ -50,7 +50,7 @@ Properties allow you to define member accessors that look like fields but execut
|
|||||||
|
|
||||||
Properties are declared using `val` (read-only) or `var` (read-write) followed by a name and `get()`/`set()` blocks:
|
Properties are declared using `val` (read-only) or `var` (read-write) followed by a name and `get()`/`set()` blocks:
|
||||||
|
|
||||||
```kotlin
|
```lyng
|
||||||
class Person(private var _age: Int) {
|
class Person(private var _age: Int) {
|
||||||
// Read-only property
|
// Read-only property
|
||||||
val ageCategory
|
val ageCategory
|
||||||
@ -76,7 +76,7 @@ assertEquals("Adult", p.ageCategory)
|
|||||||
|
|
||||||
For simple accessors and methods, you can use the `=` shorthand for a more elegant and laconic form:
|
For simple accessors and methods, you can use the `=` shorthand for a more elegant and laconic form:
|
||||||
|
|
||||||
```kotlin
|
```lyng
|
||||||
class Circle(val radius: Real) {
|
class Circle(val radius: Real) {
|
||||||
val area get() = π * radius * radius
|
val area get() = π * radius * radius
|
||||||
val circumference get() = 2 * π * radius
|
val circumference get() = 2 * π * radius
|
||||||
@ -104,7 +104,7 @@ class Counter {
|
|||||||
|
|
||||||
When you want to define a property that is computed only once (on demand) and then remembered, use the built-in `cached` function. This is more efficient than a regular property with `get()` if the computation is expensive, as it avoids re-calculating the value on every access.
|
When you want to define a property that is computed only once (on demand) and then remembered, use the built-in `cached` function. This is more efficient than a regular property with `get()` if the computation is expensive, as it avoids re-calculating the value on every access.
|
||||||
|
|
||||||
```kotlin
|
```lyng
|
||||||
class DataService(val id: Int) {
|
class DataService(val id: Int) {
|
||||||
// The lambda passed to cached is only executed once, the first time data() is called.
|
// The lambda passed to cached is only executed once, the first time data() is called.
|
||||||
val data = cached {
|
val data = cached {
|
||||||
@ -179,7 +179,7 @@ Note that unlike **Kotlin**, which uses `=` for named arguments, Lyng uses `:` t
|
|||||||
|
|
||||||
You can declare a `val` field without an immediate initializer if you provide an assignment for it within an `init` block or the class body. This is useful when the initial value depends on logic that cannot be expressed in a single expression.
|
You can declare a `val` field without an immediate initializer if you provide an assignment for it within an `init` block or the class body. This is useful when the initial value depends on logic that cannot be expressed in a single expression.
|
||||||
|
|
||||||
```kotlin
|
```lyng
|
||||||
class DataProcessor(data: Object) {
|
class DataProcessor(data: Object) {
|
||||||
val result: Object
|
val result: Object
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ Key rules for late-init `val`:
|
|||||||
|
|
||||||
The `Unset` singleton represents a field that has been declared but not yet initialized. While it can be compared and converted to a string, most other operations on it are forbidden to prevent accidental use of uninitialized data.
|
The `Unset` singleton represents a field that has been declared but not yet initialized. While it can be compared and converted to a string, most other operations on it are forbidden to prevent accidental use of uninitialized data.
|
||||||
|
|
||||||
```kotlin
|
```lyng
|
||||||
class T {
|
class T {
|
||||||
val x
|
val x
|
||||||
fun check() {
|
fun check() {
|
||||||
@ -237,7 +237,7 @@ Functions defined inside a class body are methods, and unless declared
|
|||||||
|
|
||||||
Lyng supports declaring a class with multiple direct base classes. The syntax is:
|
Lyng supports declaring a class with multiple direct base classes. The syntax is:
|
||||||
|
|
||||||
```
|
```lyng
|
||||||
class Foo(val a) {
|
class Foo(val a) {
|
||||||
var tag = "F"
|
var tag = "F"
|
||||||
fun runA() { "ResultA:" + a }
|
fun runA() { "ResultA:" + a }
|
||||||
@ -286,16 +286,16 @@ Key rules and features:
|
|||||||
|
|
||||||
- Syntax
|
- Syntax
|
||||||
- `class Derived(args) : Base1(b1Args), Base2(b2Args)`
|
- `class Derived(args) : Base1(b1Args), Base2(b2Args)`
|
||||||
- Each direct base may receive constructor arguments specified in the header. Only direct bases receive header args; indirect bases must either be default‑constructible or receive their args through their direct child (future extensions may add more control).
|
- Each direct base may receive constructor arguments specified in the header. Only direct bases receive header args; indirect bases must either be default‑constructible or receive their args through their direct child.
|
||||||
|
|
||||||
- Resolution order (C3 MRO — active)
|
- Resolution order (C3 MRO)
|
||||||
- Member lookup is deterministic and follows C3 linearization (Python‑like), which provides a monotonic, predictable order for complex hierarchies and diamonds.
|
- Member lookup is deterministic and follows C3 linearization (Python‑like), which provides a monotonic, predictable order for complex hierarchies and diamonds.
|
||||||
- Intuition: for `class D() : B(), C()` where `B()` and `C()` both derive from `A()`, the C3 order is `D → B → C → A`.
|
- Intuition: for `class D() : B(), C()` where `B()` and `C()` both derive from `A()`, the C3 order is `D → B → C → A`.
|
||||||
- The first visible match along this order wins.
|
- The first visible match along this order wins.
|
||||||
|
|
||||||
- Qualified dispatch
|
- Qualified dispatch
|
||||||
- Inside a class body, use `this@Type.member(...)` to start lookup at the specified ancestor.
|
- Inside a class body, use `this@Type.member(...)` to start lookup at the specified ancestor.
|
||||||
- For arbitrary receivers, use casts: `(expr as Type).member(...)` or `(expr as? Type)?.member(...)` (safe‑call `?.` is already available in Lyng).
|
- For arbitrary receivers, use casts: `(expr as Type).member(...)` or `(expr as? Type)?.member(...)`.
|
||||||
- Qualified access does not relax visibility.
|
- Qualified access does not relax visibility.
|
||||||
|
|
||||||
- Field inheritance (`val`/`var`) and collisions
|
- Field inheritance (`val`/`var`) and collisions
|
||||||
@ -312,10 +312,120 @@ Key rules and features:
|
|||||||
- `private`: accessible only inside the declaring class body; not visible in subclasses and cannot be accessed via `this@Type` or casts.
|
- `private`: accessible only inside the declaring class body; not visible in subclasses and cannot be accessed via `this@Type` or casts.
|
||||||
- `protected`: accessible in the declaring class and in any of its transitive subclasses (including MI), but not from unrelated contexts; qualification/casts do not bypass it.
|
- `protected`: accessible in the declaring class and in any of its transitive subclasses (including MI), but not from unrelated contexts; qualification/casts do not bypass it.
|
||||||
|
|
||||||
- Diagnostics
|
## Abstract Classes and Members
|
||||||
- When a member/field is not found, error messages include the receiver class name and the considered linearization order, with suggestions to disambiguate using `this@Type` or casts if appropriate.
|
|
||||||
- Qualifying with a non‑ancestor in `this@Type` reports a clear error mentioning the receiver lineage.
|
An `abstract` class is a class that cannot be instantiated and is intended to be inherited by other classes. It can contain `abstract` members that have no implementation and must be implemented by concrete subclasses.
|
||||||
- `as`/`as?` cast errors mention the actual and target types.
|
|
||||||
|
### Abstract Classes
|
||||||
|
|
||||||
|
To declare an abstract class, use the `abstract` modifier:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
abstract class Shape {
|
||||||
|
abstract fun area(): Real
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Abstract classes can have constructors, fields, and concrete methods, just like regular classes.
|
||||||
|
|
||||||
|
### Abstract Members
|
||||||
|
|
||||||
|
Methods and variables (`val`/`var`) can be marked as `abstract`. Abstract members must not have a body or initializer.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
abstract class Base {
|
||||||
|
abstract fun foo(): Int
|
||||||
|
abstract var bar: String
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Safety**: `abstract` members cannot be `private`, as they must be visible to subclasses for implementation.
|
||||||
|
- **Contract of Capability**: An `abstract val/var` represents a requirement for a capability. It can be implemented by either a **field** (storage) or a **property** (logic) in a subclass.
|
||||||
|
|
||||||
|
## Interfaces
|
||||||
|
|
||||||
|
An `interface` in Lyng is a synonym for an `abstract class`. Following the principle that Lyng's Multiple Inheritance system is powerful enough to handle stateful contracts, interfaces support everything classes do, including constructors, fields, and `init` blocks.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
interface Named(val name: String) {
|
||||||
|
fun greet() { "Hello, " + name }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Person(name) : Named(name)
|
||||||
|
```
|
||||||
|
|
||||||
|
Using `interface` instead of `abstract class` is a matter of semantic intent, signaling that the class is primarily intended to be used as a contract in MI.
|
||||||
|
|
||||||
|
### Implementation by Parts
|
||||||
|
|
||||||
|
One of the most powerful benefits of Lyng's Multiple Inheritance and C3 MRO is the ability to satisfy an interface's requirements "by parts" from different parent classes. Since an `interface` can have state and requirements, a subclass can inherit these requirements and satisfy them using members inherited from other parents in the MRO chain.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
// Interface with state (id) and abstract requirements
|
||||||
|
interface Character(val id) {
|
||||||
|
abstract var health
|
||||||
|
abstract var mana
|
||||||
|
|
||||||
|
fun isAlive() = health > 0
|
||||||
|
fun status() = name + " (#" + id + "): " + health + " HP, " + mana + " MP"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent class 1: provides health
|
||||||
|
class HealthPool(var health)
|
||||||
|
|
||||||
|
// Parent class 2: provides mana and name
|
||||||
|
class ManaPool(var mana) {
|
||||||
|
val name = "Hero"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Composite class: implements Character by combining HealthPool and ManaPool
|
||||||
|
class Warrior(id, h, m) : HealthPool(h), ManaPool(m), Character(id)
|
||||||
|
|
||||||
|
val w = Warrior(1, 100, 50)
|
||||||
|
assertEquals("Hero (#1): 100 HP, 50 MP", w.status())
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, `Warrior` inherits from `HealthPool`, `ManaPool`, and `Character`. The abstract requirements `health` and `mana` from `Character` are automatically satisfied by the matching members inherited from `HealthPool` and `ManaPool`. The `status()` method also successfully finds the `name` field from `ManaPool`. This pattern allows for highly modular and reusable "trait-like" classes that can be combined to fulfill complex contracts without boilerplate proxy methods.
|
||||||
|
|
||||||
|
## Overriding and Virtual Dispatch
|
||||||
|
|
||||||
|
When a class defines a member that already exists in one of its parents, it is called **overriding**.
|
||||||
|
|
||||||
|
### The `override` Keyword
|
||||||
|
|
||||||
|
In Lyng, the `override` keyword is **mandatory when declaring a member** that exists in the ancestor chain (MRO).
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class Parent {
|
||||||
|
fun foo() = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child : Parent() {
|
||||||
|
override fun foo() = 2 // Mandatory override keyword
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Implicit Satisfaction**: If a class inherits an abstract requirement and a matching implementation from different parents, the requirement is satisfied automatically without needing an explicit `override` proxy.
|
||||||
|
- **No Accidental Overrides**: If you define a member that happens to match a parent's member but you didn't use `override`, the compiler will throw an error. This prevents the "Fragile Base Class" problem.
|
||||||
|
- **Private Members**: Private members in parent classes are NOT part of the virtual interface and cannot be overridden. Defining a member with the same name in a subclass is allowed without `override` and is treated as a new, independent member.
|
||||||
|
|
||||||
|
### Visibility Widening
|
||||||
|
|
||||||
|
A subclass can increase the visibility of an overridden member (e.g., `protected` → `public`), but it is strictly forbidden from narrowing it (e.g., `public` → `protected`).
|
||||||
|
|
||||||
|
### The `closed` Modifier
|
||||||
|
|
||||||
|
To prevent a member from being overridden in subclasses, use the `closed` modifier (equivalent to `final` in other languages).
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class Critical {
|
||||||
|
closed fun secureStep() { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Attempting to override a `closed` member results in a compile-time error.
|
||||||
|
|
||||||
Compatibility notes:
|
Compatibility notes:
|
||||||
|
|
||||||
@ -438,7 +548,7 @@ You can restrict the visibility of a `var` field's or property's setter by using
|
|||||||
|
|
||||||
#### On Fields
|
#### On Fields
|
||||||
|
|
||||||
```kotlin
|
```lyng
|
||||||
class SecretCounter {
|
class SecretCounter {
|
||||||
var count = 0
|
var count = 0
|
||||||
private set // Can be read anywhere, but written only in SecretCounter
|
private set // Can be read anywhere, but written only in SecretCounter
|
||||||
|
|||||||
@ -20,7 +20,7 @@ Files
|
|||||||
- Constants: `true`, `false`, `null`, `this`
|
- Constants: `true`, `false`, `null`, `this`
|
||||||
- Annotations: `@name` (Unicode identifiers supported)
|
- Annotations: `@name` (Unicode identifiers supported)
|
||||||
- Labels: `name:` (Unicode identifiers supported)
|
- Labels: `name:` (Unicode identifiers supported)
|
||||||
- Declarations: highlights declared names in `fun|fn name`, `class|enum Name`, `val|var name`
|
- Declarations: highlights declared names in `fun|fn name`, `class|enum|interface Name`, `val|var name`
|
||||||
- Types: built-ins (`Int|Real|String|Bool|Char|Regex`) and Capitalized identifiers (heuristic)
|
- Types: built-ins (`Int|Real|String|Bool|Char|Regex`) and Capitalized identifiers (heuristic)
|
||||||
- Operators including ranges (`..`, `..<`, `...`), null-safe (`?.`, `?[`, `?(`, `?{`, `?:`, `??`), arrows (`->`, `=>`, `::`), match operators (`=~`, `!~`), bitwise, arithmetic, etc.
|
- Operators including ranges (`..`, `..<`, `...`), null-safe (`?.`, `?[`, `?(`, `?{`, `?:`, `??`), arrows (`->`, `=>`, `::`), match operators (`=~`, `!~`), bitwise, arithmetic, etc.
|
||||||
- Shuttle operator `<=>`
|
- Shuttle operator `<=>`
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "lyng-textmate",
|
"name": "lyng-textmate",
|
||||||
"displayName": "Lyng",
|
"displayName": "Lyng",
|
||||||
"description": "TextMate grammar for the Lyng language (for JetBrains IDEs via TextMate Bundles and VS Code).",
|
"description": "TextMate grammar for the Lyng language (for JetBrains IDEs via TextMate Bundles and VS Code).",
|
||||||
"version": "0.0.3",
|
"version": "0.1.0",
|
||||||
"publisher": "lyng",
|
"publisher": "lyng",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": { "vscode": "^1.0.0" },
|
"engines": { "vscode": "^1.0.0" },
|
||||||
|
|||||||
@ -76,8 +76,8 @@
|
|||||||
},
|
},
|
||||||
"labels": { "patterns": [ { "name": "entity.name.label.lyng", "match": "[\\p{L}_][\\p{L}\\p{N}_]*:" } ] },
|
"labels": { "patterns": [ { "name": "entity.name.label.lyng", "match": "[\\p{L}_][\\p{L}\\p{N}_]*:" } ] },
|
||||||
"directives": { "patterns": [ { "name": "meta.directive.lyng", "match": "^\\s*#[_A-Za-z][_A-Za-z0-9]*" } ] },
|
"directives": { "patterns": [ { "name": "meta.directive.lyng", "match": "^\\s*#[_A-Za-z][_A-Za-z0-9]*" } ] },
|
||||||
"declarations": { "patterns": [ { "name": "meta.function.declaration.lyng", "match": "\\b(fun|fn)\\s+(?:([\\p{L}_][\\p{L}\\p{N}_]*)\\.)?([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "keyword.declaration.lyng" }, "2": { "name": "entity.name.type.lyng" }, "3": { "name": "entity.name.function.lyng" } } }, { "name": "meta.type.declaration.lyng", "match": "\\b(?:class|enum)\\s+([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "entity.name.type.lyng" } } }, { "name": "meta.variable.declaration.lyng", "match": "\\b(val|var)\\s+(?:([\\p{L}_][\\p{L}\\p{N}_]*)\\.)?([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "keyword.declaration.lyng" }, "2": { "name": "entity.name.type.lyng" }, "3": { "name": "variable.other.declaration.lyng" } } } ] },
|
"declarations": { "patterns": [ { "name": "meta.function.declaration.lyng", "match": "\\b(fun|fn)\\s+(?:([\\p{L}_][\\p{L}\\p{N}_]*)\\.)?([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "keyword.declaration.lyng" }, "2": { "name": "entity.name.type.lyng" }, "3": { "name": "entity.name.function.lyng" } } }, { "name": "meta.type.declaration.lyng", "match": "\\b(?:class|enum|interface)\\s+([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "entity.name.type.lyng" } } }, { "name": "meta.variable.declaration.lyng", "match": "\\b(val|var)\\s+(?:([\\p{L}_][\\p{L}\\p{N}_]*)\\.)?([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "name": "keyword.declaration.lyng" }, "2": { "name": "entity.name.type.lyng" }, "3": { "name": "variable.other.declaration.lyng" } } } ] },
|
||||||
"keywords": { "patterns": [ { "name": "keyword.control.lyng", "match": "\\b(?:if|else|when|while|do|for|try|catch|finally|throw|return|break|continue)\\b" }, { "name": "keyword.declaration.lyng", "match": "\\b(?:fun|fn|class|enum|val|var|import|package|constructor|property|open|extern|private|protected|static|get|set)\\b" }, { "name": "keyword.operator.word.lyng", "match": "\\bnot\\s+(?:in|is)\\b" }, { "name": "keyword.operator.word.lyng", "match": "\\b(?:and|or|not|in|is|as|as\\?)\\b" } ] },
|
"keywords": { "patterns": [ { "name": "keyword.control.lyng", "match": "\\b(?:if|else|when|while|do|for|try|catch|finally|throw|return|break|continue)\\b" }, { "name": "keyword.declaration.lyng", "match": "\\b(?:fun|fn|class|enum|interface|val|var|import|package|constructor|property|abstract|override|open|closed|extern|private|protected|static|get|set)\\b" }, { "name": "keyword.operator.word.lyng", "match": "\\bnot\\s+(?:in|is)\\b" }, { "name": "keyword.operator.word.lyng", "match": "\\b(?:and|or|not|in|is|as|as\\?)\\b" } ] },
|
||||||
"constants": { "patterns": [ { "name": "constant.language.lyng", "match": "(?:\\b(?:true|false|null|this)\\b|π)" } ] },
|
"constants": { "patterns": [ { "name": "constant.language.lyng", "match": "(?:\\b(?:true|false|null|this)\\b|π)" } ] },
|
||||||
"types": { "patterns": [ { "name": "storage.type.lyng", "match": "\\b(?:Int|Real|String|Bool|Char|Regex)\\b" }, { "name": "entity.name.type.lyng", "match": "\\b[A-Z][A-Za-z0-9_]*\\b(?!\\s*\\()" } ] },
|
"types": { "patterns": [ { "name": "storage.type.lyng", "match": "\\b(?:Int|Real|String|Bool|Char|Regex)\\b" }, { "name": "entity.name.type.lyng", "match": "\\b[A-Z][A-Za-z0-9_]*\\b(?!\\s*\\()" } ] },
|
||||||
"operators": { "patterns": [ { "name": "keyword.operator.comparison.lyng", "match": "===|!==|==|!=|<=|>=|<|>" }, { "name": "keyword.operator.shuttle.lyng", "match": "<=>" }, { "name": "keyword.operator.arrow.lyng", "match": "=>|->|::" }, { "name": "keyword.operator.range.lyng", "match": "\\.\\.\\.|\\.\\.<|\\.\\." }, { "name": "keyword.operator.nullsafe.lyng", "match": "\\?\\.|\\?\\[|\\?\\(|\\?\\{|\\?:|\\?\\?" }, { "name": "keyword.operator.assignment.lyng", "match": "(?:\\+=|-=|\\*=|/=|%=|=)" }, { "name": "keyword.operator.logical.lyng", "match": "&&|\\|\\|" }, { "name": "keyword.operator.bitwise.lyng", "match": "<<|>>|&|\\||\\^|~" }, { "name": "keyword.operator.match.lyng", "match": "=~|!~" }, { "name": "keyword.operator.arithmetic.lyng", "match": "\\+\\+|--|[+\\-*/%]" }, { "name": "keyword.operator.other.lyng", "match": "[!?]" } ] },
|
"operators": { "patterns": [ { "name": "keyword.operator.comparison.lyng", "match": "===|!==|==|!=|<=|>=|<|>" }, { "name": "keyword.operator.shuttle.lyng", "match": "<=>" }, { "name": "keyword.operator.arrow.lyng", "match": "=>|->|::" }, { "name": "keyword.operator.range.lyng", "match": "\\.\\.\\.|\\.\\.<|\\.\\." }, { "name": "keyword.operator.nullsafe.lyng", "match": "\\?\\.|\\?\\[|\\?\\(|\\?\\{|\\?:|\\?\\?" }, { "name": "keyword.operator.assignment.lyng", "match": "(?:\\+=|-=|\\*=|/=|%=|=)" }, { "name": "keyword.operator.logical.lyng", "match": "&&|\\|\\|" }, { "name": "keyword.operator.bitwise.lyng", "match": "<<|>>|&|\\||\\^|~" }, { "name": "keyword.operator.match.lyng", "match": "=~|!~" }, { "name": "keyword.operator.arithmetic.lyng", "match": "\\+\\+|--|[+\\-*/%]" }, { "name": "keyword.operator.other.lyng", "match": "[!?]" } ] },
|
||||||
|
|||||||
@ -579,7 +579,8 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
|||||||
val s = w.lowercase()
|
val s = w.lowercase()
|
||||||
return s in setOf(
|
return s in setOf(
|
||||||
// common code words / language keywords to avoid noise
|
// common code words / language keywords to avoid noise
|
||||||
"val","var","fun","class","enum","type","import","package","return","if","else","when","while","for","try","catch","finally","true","false","null",
|
"val","var","fun","class","interface","enum","type","import","package","return","if","else","when","while","for","try","catch","finally","true","false","null",
|
||||||
|
"abstract","closed","override",
|
||||||
// very common English words
|
// very common English words
|
||||||
"the","and","or","not","with","from","into","this","that","file","found","count","name","value","object"
|
"the","and","or","not","with","from","into","this","that","file","found","count","name","value","object"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -32,7 +32,8 @@ class LyngLexer : LexerBase() {
|
|||||||
private var myTokenType: IElementType? = null
|
private var myTokenType: IElementType? = null
|
||||||
|
|
||||||
private val keywords = setOf(
|
private val keywords = setOf(
|
||||||
"fun", "val", "var", "class", "type", "import", "as",
|
"fun", "val", "var", "class", "interface", "type", "import", "as",
|
||||||
|
"abstract", "closed", "override",
|
||||||
"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"
|
"get", "set"
|
||||||
|
|||||||
@ -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.1.0-rc"
|
version = "1.1.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
|
||||||
|
|
||||||
|
|||||||
@ -53,26 +53,71 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
|||||||
super.objects[name]?.let { return it }
|
super.objects[name]?.let { return it }
|
||||||
super.localBindings[name]?.let { return it }
|
super.localBindings[name]?.let { return it }
|
||||||
|
|
||||||
// 2) Members on the captured receiver instance
|
// 1a) Priority: if we are in a class context, prefer our own private members to support
|
||||||
(closureScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)
|
// non-virtual private dispatch. This prevents a subclass from accidentally
|
||||||
?.instanceScope
|
// capturing a private member call from a base class.
|
||||||
?.objects
|
// We only return non-field/non-property members (methods) here; fields must
|
||||||
?.get(name)
|
// be resolved via instance storage in priority 2.
|
||||||
?.let { rec ->
|
currentClassCtx?.let { ctx ->
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
ctx.members[name]?.let { rec ->
|
||||||
|
if (rec.visibility == Visibility.Private &&
|
||||||
|
rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Field &&
|
||||||
|
rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property) return rec
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Members on the captured receiver instance
|
||||||
|
(closureScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
||||||
|
// Check direct locals in instance scope (unmangled)
|
||||||
|
inst.instanceScope.objects[name]?.let { rec ->
|
||||||
|
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
||||||
|
canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
||||||
|
}
|
||||||
|
// Check mangled names for fields along MRO
|
||||||
|
for (cls in inst.objClass.mro) {
|
||||||
|
inst.instanceScope.objects["${cls.className}::$name"]?.let { rec ->
|
||||||
|
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
||||||
|
canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) return rec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
findExtension(closureScope.thisObj.objClass, name)?.let { return it }
|
findExtension(closureScope.thisObj.objClass, name)?.let { return it }
|
||||||
closureScope.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
closureScope.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||||
|
// Return only non-field/non-property members (methods) from class-level records.
|
||||||
|
// Fields and properties must be resolved via instance storage (mangled) or readField.
|
||||||
|
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Field &&
|
||||||
|
rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
||||||
|
!rec.isAbstract) return rec
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Closure scope chain (locals/parents + members), ignore ClosureScope overrides to prevent recursion
|
// 3) Closure scope chain (locals/parents + members), ignore ClosureScope overrides to prevent recursion
|
||||||
closureScope.chainLookupWithMembers(name, currentClassCtx)?.let { return it }
|
closureScope.chainLookupWithMembers(name, currentClassCtx)?.let { return it }
|
||||||
|
|
||||||
// 4) Caller `this` members
|
// 4) Caller `this` members
|
||||||
|
(callScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
||||||
|
// Check direct locals in instance scope (unmangled)
|
||||||
|
inst.instanceScope.objects[name]?.let { rec ->
|
||||||
|
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
||||||
|
canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
||||||
|
}
|
||||||
|
// Check mangled names for fields along MRO
|
||||||
|
for (cls in inst.objClass.mro) {
|
||||||
|
inst.instanceScope.objects["${cls.className}::$name"]?.let { rec ->
|
||||||
|
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
||||||
|
canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) return rec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
findExtension(callScope.thisObj.objClass, name)?.let { return it }
|
findExtension(callScope.thisObj.objClass, name)?.let { return it }
|
||||||
callScope.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
callScope.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) return rec
|
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||||
|
if (rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Field &&
|
||||||
|
rec.type != net.sergeych.lyng.obj.ObjRecord.Type.Property &&
|
||||||
|
!rec.isAbstract) return rec
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5) Caller chain (locals/parents + members)
|
// 5) Caller chain (locals/parents + members)
|
||||||
|
|||||||
@ -236,7 +236,7 @@ class Compiler(
|
|||||||
cc.next()
|
cc.next()
|
||||||
pendingDeclStart = t.pos
|
pendingDeclStart = t.pos
|
||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
val st = parseFunctionDeclaration(isOpen = false, isExtern = false, isStatic = false)
|
val st = parseFunctionDeclaration(isExtern = false, isStatic = false)
|
||||||
statements += st
|
statements += st
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -1298,11 +1298,93 @@ class Compiler(
|
|||||||
else
|
else
|
||||||
null
|
null
|
||||||
|
|
||||||
|
private suspend fun parseDeclarationWithModifiers(firstId: Token): Statement {
|
||||||
|
val modifiers = mutableSetOf<String>()
|
||||||
|
var currentToken = firstId
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
when (currentToken.value) {
|
||||||
|
"private", "protected", "static", "abstract", "closed", "override", "extern", "open" -> {
|
||||||
|
modifiers.add(currentToken.value)
|
||||||
|
val next = cc.peekNextNonWhitespace()
|
||||||
|
if (next.type == Token.Type.ID) {
|
||||||
|
currentToken = cc.next()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val visibility = when {
|
||||||
|
modifiers.contains("private") -> Visibility.Private
|
||||||
|
modifiers.contains("protected") -> Visibility.Protected
|
||||||
|
else -> Visibility.Public
|
||||||
|
}
|
||||||
|
val isStatic = modifiers.contains("static")
|
||||||
|
val isAbstract = modifiers.contains("abstract")
|
||||||
|
val isClosed = modifiers.contains("closed")
|
||||||
|
val isOverride = modifiers.contains("override")
|
||||||
|
val isExtern = modifiers.contains("extern")
|
||||||
|
|
||||||
|
if (isStatic && (isAbstract || isOverride || isClosed))
|
||||||
|
throw ScriptError(currentToken.pos, "static members cannot be abstract, closed or override")
|
||||||
|
|
||||||
|
if (visibility == Visibility.Private && isAbstract)
|
||||||
|
throw ScriptError(currentToken.pos, "abstract members cannot be private")
|
||||||
|
|
||||||
|
pendingDeclStart = firstId.pos
|
||||||
|
pendingDeclDoc = consumePendingDoc()
|
||||||
|
|
||||||
|
val isMember = (codeContexts.lastOrNull() is CodeContext.ClassBody)
|
||||||
|
|
||||||
|
if (!isMember && (isOverride || isClosed))
|
||||||
|
throw ScriptError(currentToken.pos, "modifiers override and closed are only allowed for class members")
|
||||||
|
|
||||||
|
if (!isMember && isAbstract && currentToken.value != "class")
|
||||||
|
throw ScriptError(currentToken.pos, "modifier abstract at top level is only allowed for classes")
|
||||||
|
|
||||||
|
return when (currentToken.value) {
|
||||||
|
"val" -> parseVarDeclaration(false, visibility, isAbstract, isClosed, isOverride, isStatic)
|
||||||
|
"var" -> parseVarDeclaration(true, visibility, isAbstract, isClosed, isOverride, isStatic)
|
||||||
|
"fun", "fn" -> parseFunctionDeclaration(visibility, isAbstract, isClosed, isOverride, isExtern, isStatic)
|
||||||
|
"class" -> {
|
||||||
|
if (isStatic || isClosed || isOverride || isExtern)
|
||||||
|
throw ScriptError(currentToken.pos, "unsupported modifiers for class: ${modifiers.joinToString(" ")}")
|
||||||
|
parseClassDeclaration(isAbstract)
|
||||||
|
}
|
||||||
|
|
||||||
|
"interface" -> {
|
||||||
|
if (isStatic || isClosed || isOverride || isExtern || isAbstract)
|
||||||
|
throw ScriptError(
|
||||||
|
currentToken.pos,
|
||||||
|
"unsupported modifiers for interface: ${modifiers.joinToString(" ")}"
|
||||||
|
)
|
||||||
|
// interface is synonym for abstract class
|
||||||
|
parseClassDeclaration(isAbstract = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw ScriptError(currentToken.pos, "expected declaration after modifiers, found ${currentToken.value}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse keyword-starting statement.
|
* Parse keyword-starting statement.
|
||||||
* @return parsed statement or null if, for example. [id] is not among keywords
|
* @return parsed statement or null if, for example. [id] is not among keywords
|
||||||
*/
|
*/
|
||||||
private suspend fun parseKeywordStatement(id: Token): Statement? = when (id.value) {
|
private suspend fun parseKeywordStatement(id: Token): Statement? = when (id.value) {
|
||||||
|
"abstract", "closed", "override", "extern", "private", "protected", "static" -> {
|
||||||
|
parseDeclarationWithModifiers(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
"interface" -> {
|
||||||
|
pendingDeclStart = id.pos
|
||||||
|
pendingDeclDoc = consumePendingDoc()
|
||||||
|
parseClassDeclaration(isAbstract = true)
|
||||||
|
}
|
||||||
|
|
||||||
"val" -> {
|
"val" -> {
|
||||||
pendingDeclStart = id.pos
|
pendingDeclStart = id.pos
|
||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
@ -1318,71 +1400,15 @@ class Compiler(
|
|||||||
"fun" -> {
|
"fun" -> {
|
||||||
pendingDeclStart = id.pos
|
pendingDeclStart = id.pos
|
||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
parseFunctionDeclaration(isOpen = false, isExtern = false, isStatic = false)
|
parseFunctionDeclaration(isExtern = false, isStatic = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
"fn" -> {
|
"fn" -> {
|
||||||
pendingDeclStart = id.pos
|
pendingDeclStart = id.pos
|
||||||
pendingDeclDoc = consumePendingDoc()
|
pendingDeclDoc = consumePendingDoc()
|
||||||
parseFunctionDeclaration(isOpen = false, isExtern = false, isStatic = false)
|
parseFunctionDeclaration(isExtern = false, isStatic = false)
|
||||||
}
|
}
|
||||||
// Visibility modifiers for declarations: private/protected val/var/fun/fn
|
// Visibility modifiers for declarations: private/protected val/var/fun/fn
|
||||||
"private" -> {
|
|
||||||
var k = cc.requireToken(Token.Type.ID, "declaration expected after 'private'")
|
|
||||||
var isStatic = false
|
|
||||||
if (k.value == "static") {
|
|
||||||
isStatic = true
|
|
||||||
k = cc.requireToken(Token.Type.ID, "declaration expected after 'private static'")
|
|
||||||
}
|
|
||||||
when (k.value) {
|
|
||||||
"val" -> parseVarDeclaration(false, Visibility.Private, isStatic = isStatic)
|
|
||||||
"var" -> parseVarDeclaration(true, Visibility.Private, isStatic = isStatic)
|
|
||||||
"fun" -> parseFunctionDeclaration(
|
|
||||||
visibility = Visibility.Private,
|
|
||||||
isOpen = false,
|
|
||||||
isExtern = false,
|
|
||||||
isStatic = isStatic
|
|
||||||
)
|
|
||||||
|
|
||||||
"fn" -> parseFunctionDeclaration(
|
|
||||||
visibility = Visibility.Private,
|
|
||||||
isOpen = false,
|
|
||||||
isExtern = false,
|
|
||||||
isStatic = isStatic
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> k.raiseSyntax("unsupported private declaration kind: ${k.value}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"protected" -> {
|
|
||||||
var k = cc.requireToken(Token.Type.ID, "declaration expected after 'protected'")
|
|
||||||
var isStatic = false
|
|
||||||
if (k.value == "static") {
|
|
||||||
isStatic = true
|
|
||||||
k = cc.requireToken(Token.Type.ID, "declaration expected after 'protected static'")
|
|
||||||
}
|
|
||||||
when (k.value) {
|
|
||||||
"val" -> parseVarDeclaration(false, Visibility.Protected, isStatic = isStatic)
|
|
||||||
"var" -> parseVarDeclaration(true, Visibility.Protected, isStatic = isStatic)
|
|
||||||
"fun" -> parseFunctionDeclaration(
|
|
||||||
visibility = Visibility.Protected,
|
|
||||||
isOpen = false,
|
|
||||||
isExtern = false,
|
|
||||||
isStatic = isStatic
|
|
||||||
)
|
|
||||||
|
|
||||||
"fn" -> parseFunctionDeclaration(
|
|
||||||
visibility = Visibility.Protected,
|
|
||||||
isOpen = false,
|
|
||||||
isExtern = false,
|
|
||||||
isStatic = isStatic
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> k.raiseSyntax("unsupported protected declaration kind: ${k.value}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"while" -> parseWhileStatement()
|
"while" -> parseWhileStatement()
|
||||||
"do" -> parseDoWhileStatement()
|
"do" -> parseDoWhileStatement()
|
||||||
"for" -> parseForStatement()
|
"for" -> parseForStatement()
|
||||||
@ -1444,17 +1470,17 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
|
|
||||||
cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(Visibility.Private, isExtern)
|
cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(Visibility.Private, isExtern)
|
||||||
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(isExtern = isExtern)
|
||||||
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isOpen = true, isExtern = isExtern)
|
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(isExtern = isExtern)
|
||||||
|
|
||||||
cc.matchQualifiers("fun") -> {
|
cc.matchQualifiers("fun") -> {
|
||||||
pendingDeclStart = id.pos; pendingDeclDoc =
|
pendingDeclStart = id.pos; pendingDeclDoc =
|
||||||
consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
|
consumePendingDoc(); parseFunctionDeclaration(isExtern = isExtern)
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.matchQualifiers("fn") -> {
|
cc.matchQualifiers("fn") -> {
|
||||||
pendingDeclStart = id.pos; pendingDeclDoc =
|
pendingDeclStart = id.pos; pendingDeclDoc =
|
||||||
consumePendingDoc(); parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
|
consumePendingDoc(); parseFunctionDeclaration(isExtern = isExtern)
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.matchQualifiers("val", "private", "static") -> {
|
cc.matchQualifiers("val", "private", "static") -> {
|
||||||
@ -1821,7 +1847,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun parseClassDeclaration(): Statement {
|
private suspend fun parseClassDeclaration(isAbstract: Boolean = false): Statement {
|
||||||
val nameToken = cc.requireToken(Token.Type.ID)
|
val nameToken = cc.requireToken(Token.Type.ID)
|
||||||
val startPos = pendingDeclStart ?: nameToken.pos
|
val startPos = pendingDeclStart ?: nameToken.pos
|
||||||
val doc = pendingDeclDoc ?: consumePendingDoc()
|
val doc = pendingDeclDoc ?: consumePendingDoc()
|
||||||
@ -1947,6 +1973,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()).also {
|
val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()).also {
|
||||||
|
it.isAbstract = isAbstract
|
||||||
it.instanceConstructor = constructorCode
|
it.instanceConstructor = constructorCode
|
||||||
it.constructorMeta = constructorArgsDeclaration
|
it.constructorMeta = constructorArgsDeclaration
|
||||||
// Attach per-parent constructor args (thunks) if provided
|
// Attach per-parent constructor args (thunks) if provided
|
||||||
@ -1954,6 +1981,22 @@ class Compiler(
|
|||||||
val argsList = baseSpecs[i].args
|
val argsList = baseSpecs[i].args
|
||||||
if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList
|
if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList
|
||||||
}
|
}
|
||||||
|
// Register constructor fields in the class members
|
||||||
|
constructorArgsDeclaration?.params?.forEach { p ->
|
||||||
|
if (p.accessType != null) {
|
||||||
|
it.createField(
|
||||||
|
p.name, ObjNull,
|
||||||
|
isMutable = p.accessType == AccessType.Var,
|
||||||
|
visibility = p.visibility ?: Visibility.Public,
|
||||||
|
declaringClass = it,
|
||||||
|
// Constructor fields are not currently supporting override/closed in parser
|
||||||
|
// but we should pass Pos.builtIn to skip validation for now if needed,
|
||||||
|
// or p.pos to allow it.
|
||||||
|
pos = Pos.builtIn,
|
||||||
|
type = ObjRecord.Type.ConstructorField
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addItem(className, false, newClass)
|
addItem(className, false, newClass)
|
||||||
@ -1974,7 +2017,7 @@ class Compiler(
|
|||||||
val v = rec.value
|
val v = rec.value
|
||||||
if (v is Statement) {
|
if (v is Statement) {
|
||||||
if (newClass.members[k] == null) {
|
if (newClass.members[k] == null) {
|
||||||
newClass.addFn(k, isOpen = true) {
|
newClass.addFn(k, isMutable = true, pos = rec.importedFrom?.pos ?: nameToken.pos) {
|
||||||
(thisObj as? ObjInstance)?.let { i ->
|
(thisObj as? ObjInstance)?.let { i ->
|
||||||
v.execute(ClosureScope(this, i.instanceScope))
|
v.execute(ClosureScope(this, i.instanceScope))
|
||||||
} ?: v.execute(thisObj.autoInstanceScope(this))
|
} ?: v.execute(thisObj.autoInstanceScope(this))
|
||||||
@ -1982,6 +2025,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
newClass.checkAbstractSatisfaction(nameToken.pos)
|
||||||
// Debug summary: list registered instance methods and class-scope functions for this class
|
// Debug summary: list registered instance methods and class-scope functions for this class
|
||||||
newClass
|
newClass
|
||||||
}
|
}
|
||||||
@ -2395,7 +2439,9 @@ class Compiler(
|
|||||||
|
|
||||||
private suspend fun parseFunctionDeclaration(
|
private suspend fun parseFunctionDeclaration(
|
||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
isAbstract: Boolean = false,
|
||||||
|
isClosed: Boolean = false,
|
||||||
|
isOverride: Boolean = false,
|
||||||
isExtern: Boolean = false,
|
isExtern: Boolean = false,
|
||||||
isStatic: Boolean = false,
|
isStatic: Boolean = false,
|
||||||
): Statement {
|
): Statement {
|
||||||
@ -2480,7 +2526,12 @@ class Compiler(
|
|||||||
localDeclCountStack.add(0)
|
localDeclCountStack.add(0)
|
||||||
val fnStatements = if (isExtern)
|
val fnStatements = if (isExtern)
|
||||||
statement { raiseError("extern function not provided: $name") }
|
statement { raiseError("extern function not provided: $name") }
|
||||||
else
|
else if (isAbstract) {
|
||||||
|
val next = cc.peekNextNonWhitespace()
|
||||||
|
if (next.type == Token.Type.ASSIGN || next.type == Token.Type.LBRACE)
|
||||||
|
throw ScriptError(next.pos, "abstract function $name cannot have a body")
|
||||||
|
null
|
||||||
|
} else
|
||||||
withLocalNames(paramNames) {
|
withLocalNames(paramNames) {
|
||||||
val next = cc.peekNextNonWhitespace()
|
val next = cc.peekNextNonWhitespace()
|
||||||
if (next.type == Token.Type.ASSIGN) {
|
if (next.type == Token.Type.ASSIGN) {
|
||||||
@ -2518,7 +2569,7 @@ class Compiler(
|
|||||||
if (extTypeName != null) {
|
if (extTypeName != null) {
|
||||||
context.thisObj = callerContext.thisObj
|
context.thisObj = callerContext.thisObj
|
||||||
}
|
}
|
||||||
fnStatements.execute(context)
|
fnStatements?.execute(context) ?: ObjVoid
|
||||||
}
|
}
|
||||||
// parentContext
|
// parentContext
|
||||||
val fnCreateStatement = statement(start) { context ->
|
val fnCreateStatement = statement(start) { context ->
|
||||||
@ -2550,7 +2601,15 @@ class Compiler(
|
|||||||
if (!isStatic && th is ObjClass) {
|
if (!isStatic && th is ObjClass) {
|
||||||
// Instance method declared inside a class body: register on the class
|
// Instance method declared inside a class body: register on the class
|
||||||
val cls: ObjClass = th
|
val cls: ObjClass = th
|
||||||
cls.addFn(name, isOpen = true, visibility = visibility) {
|
cls.addFn(
|
||||||
|
name,
|
||||||
|
isMutable = true,
|
||||||
|
visibility = visibility,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride,
|
||||||
|
pos = start
|
||||||
|
) {
|
||||||
// Execute with the instance as receiver; set caller lexical class for visibility
|
// Execute with the instance as receiver; set caller lexical class for visibility
|
||||||
val savedCtx = this.currentClassCtx
|
val savedCtx = this.currentClassCtx
|
||||||
this.currentClassCtx = cls
|
this.currentClassCtx = cls
|
||||||
@ -2628,7 +2687,9 @@ class Compiler(
|
|||||||
private suspend fun parseVarDeclaration(
|
private suspend fun parseVarDeclaration(
|
||||||
isMutable: Boolean,
|
isMutable: Boolean,
|
||||||
visibility: Visibility,
|
visibility: Visibility,
|
||||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
isAbstract: Boolean = false,
|
||||||
|
isClosed: Boolean = false,
|
||||||
|
isOverride: Boolean = false,
|
||||||
isStatic: Boolean = false
|
isStatic: Boolean = false
|
||||||
): Statement {
|
): Statement {
|
||||||
val nextToken = cc.next()
|
val nextToken = cc.next()
|
||||||
@ -2741,7 +2802,15 @@ class Compiler(
|
|||||||
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
|
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
|
||||||
if (!isStatic) declareLocalName(name)
|
if (!isStatic) declareLocalName(name)
|
||||||
|
|
||||||
val isDelegate = if (!isProperty && eqToken.isId("by")) {
|
val isDelegate = if (isAbstract) {
|
||||||
|
if (!isProperty && (eqToken.type == Token.Type.ASSIGN || eqToken.isId("by")))
|
||||||
|
throw ScriptError(eqToken.pos, "abstract variable $name cannot have an initializer or delegate")
|
||||||
|
// Abstract variables don't have initializers
|
||||||
|
cc.restorePos(markBeforeEq)
|
||||||
|
cc.skipWsTokens()
|
||||||
|
setNull = true
|
||||||
|
false
|
||||||
|
} else if (!isProperty && eqToken.isId("by")) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
if (!isProperty && eqToken.type != Token.Type.ASSIGN) {
|
if (!isProperty && eqToken.type != Token.Type.ASSIGN) {
|
||||||
@ -2945,22 +3014,58 @@ class Compiler(
|
|||||||
val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance)
|
val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance)
|
||||||
if (isClassScope) {
|
if (isClassScope) {
|
||||||
val cls = context.thisObj as ObjClass
|
val cls = context.thisObj as ObjClass
|
||||||
// Register the property
|
// Register in class members for reflection/MRO/satisfaction checks
|
||||||
cls.instanceInitializers += statement(start) { scp ->
|
if (isProperty) {
|
||||||
scp.addItem(
|
cls.addProperty(
|
||||||
storageName,
|
name,
|
||||||
isMutable,
|
visibility = visibility,
|
||||||
prop,
|
writeVisibility = setterVisibility,
|
||||||
visibility,
|
isAbstract = isAbstract,
|
||||||
setterVisibility,
|
isClosed = isClosed,
|
||||||
recordType = ObjRecord.Type.Property
|
isOverride = isOverride,
|
||||||
|
pos = start
|
||||||
)
|
)
|
||||||
ObjVoid
|
} else {
|
||||||
|
cls.createField(
|
||||||
|
name,
|
||||||
|
ObjNull,
|
||||||
|
isMutable = isMutable,
|
||||||
|
visibility = visibility,
|
||||||
|
writeVisibility = setterVisibility,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride,
|
||||||
|
type = ObjRecord.Type.Field
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the property/field initialization thunk
|
||||||
|
if (!isAbstract) {
|
||||||
|
cls.instanceInitializers += statement(start) { scp ->
|
||||||
|
scp.addItem(
|
||||||
|
storageName,
|
||||||
|
isMutable,
|
||||||
|
prop,
|
||||||
|
visibility,
|
||||||
|
setterVisibility,
|
||||||
|
recordType = ObjRecord.Type.Property,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride
|
||||||
|
)
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ObjVoid
|
ObjVoid
|
||||||
} else {
|
} else {
|
||||||
// We are in instance scope already: perform initialization immediately
|
// We are in instance scope already: perform initialization immediately
|
||||||
context.addItem(storageName, isMutable, prop, visibility, setterVisibility, recordType = ObjRecord.Type.Property)
|
context.addItem(
|
||||||
|
storageName, isMutable, prop, visibility, setterVisibility,
|
||||||
|
recordType = ObjRecord.Type.Property,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride
|
||||||
|
)
|
||||||
prop
|
prop
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -2971,22 +3076,51 @@ class Compiler(
|
|||||||
val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance)
|
val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance)
|
||||||
if (isClassScope) {
|
if (isClassScope) {
|
||||||
val cls = context.thisObj as ObjClass
|
val cls = context.thisObj as ObjClass
|
||||||
|
// Register in class members for reflection/MRO/satisfaction checks
|
||||||
|
cls.createField(
|
||||||
|
name,
|
||||||
|
ObjNull,
|
||||||
|
isMutable = isMutable,
|
||||||
|
visibility = visibility,
|
||||||
|
writeVisibility = setterVisibility,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride,
|
||||||
|
pos = start,
|
||||||
|
type = ObjRecord.Type.Field
|
||||||
|
)
|
||||||
|
|
||||||
// Defer: at instance construction, evaluate initializer in instance scope and store under mangled name
|
// Defer: at instance construction, evaluate initializer in instance scope and store under mangled name
|
||||||
val initStmt = statement(start) { scp ->
|
if (!isAbstract) {
|
||||||
val initValue =
|
val initStmt = statement(start) { scp ->
|
||||||
initialExpression?.execute(scp)?.byValueCopy() ?: if (isLateInitVal) ObjUnset else ObjNull
|
val initValue =
|
||||||
// Preserve mutability of declaration: do NOT use addOrUpdateItem here, as it creates mutable records
|
initialExpression?.execute(scp)?.byValueCopy()
|
||||||
scp.addItem(storageName, isMutable, initValue, visibility, setterVisibility, recordType = ObjRecord.Type.Field)
|
?: if (isLateInitVal) ObjUnset else ObjNull
|
||||||
ObjVoid
|
// Preserve mutability of declaration: do NOT use addOrUpdateItem here, as it creates mutable records
|
||||||
|
scp.addItem(
|
||||||
|
storageName, isMutable, initValue, visibility, setterVisibility,
|
||||||
|
recordType = ObjRecord.Type.Field,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride
|
||||||
|
)
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
|
cls.instanceInitializers += initStmt
|
||||||
}
|
}
|
||||||
cls.instanceInitializers += initStmt
|
|
||||||
ObjVoid
|
ObjVoid
|
||||||
} else {
|
} else {
|
||||||
// We are in instance scope already: perform initialization immediately
|
// We are in instance scope already: perform initialization immediately
|
||||||
val initValue =
|
val initValue =
|
||||||
initialExpression?.execute(context)?.byValueCopy() ?: if (isLateInitVal) ObjUnset else ObjNull
|
initialExpression?.execute(context)?.byValueCopy() ?: if (isLateInitVal) ObjUnset else ObjNull
|
||||||
// Preserve mutability of declaration: create record with correct mutability
|
// Preserve mutability of declaration: create record with correct mutability
|
||||||
context.addItem(storageName, isMutable, initValue, visibility, setterVisibility, recordType = ObjRecord.Type.Field)
|
context.addItem(
|
||||||
|
storageName, isMutable, initValue, visibility, setterVisibility,
|
||||||
|
recordType = ObjRecord.Type.Field,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride
|
||||||
|
)
|
||||||
initValue
|
initValue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -155,7 +155,10 @@ open class Scope(
|
|||||||
this.extensions[cls]?.get(name)?.let { return it }
|
this.extensions[cls]?.get(name)?.let { return it }
|
||||||
}
|
}
|
||||||
return thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
return thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) rec else null
|
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||||
|
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
||||||
|
else rec
|
||||||
|
} else null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +179,11 @@ open class Scope(
|
|||||||
s.extensions[cls]?.get(name)?.let { return it }
|
s.extensions[cls]?.get(name)?.let { return it }
|
||||||
}
|
}
|
||||||
s.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
s.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
if (canAccessMember(rec.visibility, rec.declaringClass, caller)) {
|
||||||
|
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) {
|
||||||
|
// ignore fields, properties and abstracts here, they will be handled by the caller via readField
|
||||||
|
} else return rec
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s = s.parent
|
s = s.parent
|
||||||
}
|
}
|
||||||
@ -332,7 +339,10 @@ open class Scope(
|
|||||||
?: parent?.get(name)
|
?: parent?.get(name)
|
||||||
// Finally, fallback to class members on thisObj
|
// Finally, fallback to class members on thisObj
|
||||||
?: thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
?: thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) rec else null
|
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||||
|
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
||||||
|
else rec
|
||||||
|
} else null
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -435,7 +445,10 @@ open class Scope(
|
|||||||
value: Obj,
|
value: Obj,
|
||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
writeVisibility: Visibility? = null,
|
writeVisibility: Visibility? = null,
|
||||||
recordType: ObjRecord.Type = ObjRecord.Type.Other
|
recordType: ObjRecord.Type = ObjRecord.Type.Other,
|
||||||
|
isAbstract: Boolean = false,
|
||||||
|
isClosed: Boolean = false,
|
||||||
|
isOverride: Boolean = false
|
||||||
): ObjRecord =
|
): ObjRecord =
|
||||||
objects[name]?.let {
|
objects[name]?.let {
|
||||||
if( !it.isMutable )
|
if( !it.isMutable )
|
||||||
@ -449,7 +462,7 @@ open class Scope(
|
|||||||
callScope.localBindings[name] = it
|
callScope.localBindings[name] = it
|
||||||
}
|
}
|
||||||
it
|
it
|
||||||
} ?: addItem(name, true, value, visibility, writeVisibility, recordType)
|
} ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride)
|
||||||
|
|
||||||
fun addItem(
|
fun addItem(
|
||||||
name: String,
|
name: String,
|
||||||
@ -458,9 +471,19 @@ open class Scope(
|
|||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
writeVisibility: Visibility? = null,
|
writeVisibility: Visibility? = null,
|
||||||
recordType: ObjRecord.Type = ObjRecord.Type.Other,
|
recordType: ObjRecord.Type = ObjRecord.Type.Other,
|
||||||
declaringClass: net.sergeych.lyng.obj.ObjClass? = currentClassCtx
|
declaringClass: net.sergeych.lyng.obj.ObjClass? = currentClassCtx,
|
||||||
|
isAbstract: Boolean = false,
|
||||||
|
isClosed: Boolean = false,
|
||||||
|
isOverride: Boolean = false
|
||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
val rec = ObjRecord(value, isMutable, visibility, writeVisibility, declaringClass = declaringClass, type = recordType)
|
val rec = ObjRecord(
|
||||||
|
value, isMutable, visibility, writeVisibility,
|
||||||
|
declaringClass = declaringClass,
|
||||||
|
type = recordType,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride
|
||||||
|
)
|
||||||
objects[name] = rec
|
objects[name] = 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
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
enum class Visibility {
|
enum class Visibility {
|
||||||
Public, Private, Protected;//, Internal
|
Public, Protected, Private;//, Internal
|
||||||
val isPublic by lazy { this == Public }
|
val isPublic by lazy { this == Public }
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val isProtected by lazy { this == Protected }
|
val isProtected by lazy { this == Protected }
|
||||||
|
|||||||
@ -41,7 +41,8 @@ private val fallbackKeywordIds = setOf(
|
|||||||
// boolean operators
|
// boolean operators
|
||||||
"and", "or", "not",
|
"and", "or", "not",
|
||||||
// declarations & modifiers
|
// declarations & modifiers
|
||||||
"fun", "fn", "class", "enum", "val", "var", "import", "package",
|
"fun", "fn", "class", "interface", "enum", "val", "var", "import", "package",
|
||||||
|
"abstract", "closed", "override",
|
||||||
"private", "protected", "static", "open", "extern", "init", "get", "set", "by",
|
"private", "protected", "static", "open", "extern", "init", "get", "set", "by",
|
||||||
// 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",
|
||||||
|
|||||||
@ -112,8 +112,8 @@ object BuiltinDocRegistry : BuiltinDocSource {
|
|||||||
fun extensionMemberNamesFor(className: String): List<String> {
|
fun extensionMemberNamesFor(className: String): List<String> {
|
||||||
val src = try { rootLyng } catch (_: Throwable) { null } ?: return emptyList()
|
val src = try { rootLyng } catch (_: Throwable) { null } ?: return emptyList()
|
||||||
val out = LinkedHashSet<String>()
|
val out = LinkedHashSet<String>()
|
||||||
// Match lines like: fun String.trim(...) or val Int.isEven = ...
|
// Match lines like: fun String.trim(...) or val Int.isEven = ... (allowing modifiers)
|
||||||
val re = Regex("^\\s*(?:fun|val|var)\\s+${className}\\.([A-Za-z_][A-Za-z0-9_]*)\\b", RegexOption.MULTILINE)
|
val re = Regex("^\\s*(?:(?:abstract|override|closed|private|protected|static|open|extern)\\s+)*(?:fun|val|var)\\s+${className}\\.([A-Za-z_][A-Za-z0-9_]*)\\b", RegexOption.MULTILINE)
|
||||||
re.findAll(src).forEach { m ->
|
re.findAll(src).forEach { m ->
|
||||||
val name = m.groupValues.getOrNull(1)?.trim()
|
val name = m.groupValues.getOrNull(1)?.trim()
|
||||||
if (!name.isNullOrEmpty()) out.add(name)
|
if (!name.isNullOrEmpty()) out.add(name)
|
||||||
@ -371,20 +371,20 @@ private object StdlibInlineDocIndex {
|
|||||||
else -> {
|
else -> {
|
||||||
// Non-comment, non-blank: try to match a declaration just after comments
|
// Non-comment, non-blank: try to match a declaration just after comments
|
||||||
if (buf.isNotEmpty()) {
|
if (buf.isNotEmpty()) {
|
||||||
// fun/val/var Class.name( ... )
|
// fun/val/var Class.name( ... ) (allowing modifiers)
|
||||||
val mExt = Regex("^(?:fun|val|var)\\s+([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_]*)\\b").find(line)
|
val mExt = Regex("^(?:(?:abstract|override|closed|private|protected|static|open|extern)\\s+)*(?:fun|val|var)\\s+([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_]*)\\b").find(line)
|
||||||
if (mExt != null) {
|
if (mExt != null) {
|
||||||
val (cls, name) = mExt.destructured
|
val (cls, name) = mExt.destructured
|
||||||
flushTo(Key.Method(cls, name))
|
flushTo(Key.Method(cls, name))
|
||||||
} else {
|
} else {
|
||||||
// fun name( ... )
|
// fun name( ... ) (allowing modifiers)
|
||||||
val mTop = Regex("^fun\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(").find(line)
|
val mTop = Regex("^(?:(?:abstract|override|closed|private|protected|static|open|extern)\\s+)*fun\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(").find(line)
|
||||||
if (mTop != null) {
|
if (mTop != null) {
|
||||||
val (name) = mTop.destructured
|
val (name) = mTop.destructured
|
||||||
flushTo(Key.TopFun(name))
|
flushTo(Key.TopFun(name))
|
||||||
} else {
|
} else {
|
||||||
// class Name
|
// class/interface Name (allowing modifiers)
|
||||||
val mClass = Regex("^class\\s+([A-Za-z_][A-Za-z0-9_]*)\\b").find(line)
|
val mClass = Regex("^(?:(?:abstract|private|protected|static|open|extern)\\s+)*(?:class|interface)\\s+([A-Za-z_][A-Za-z0-9_]*)\\b").find(line)
|
||||||
if (mClass != null) {
|
if (mClass != null) {
|
||||||
val (name) = mClass.destructured
|
val (name) = mClass.destructured
|
||||||
flushTo(Key.Clazz(name))
|
flushTo(Key.Clazz(name))
|
||||||
|
|||||||
@ -435,16 +435,16 @@ object DocLookupUtils {
|
|||||||
if (start !in 0..end) return emptyMap()
|
if (start !in 0..end) return emptyMap()
|
||||||
val body = text.substring(start, end)
|
val body = text.substring(start, end)
|
||||||
val map = LinkedHashMap<String, ScannedSig>()
|
val map = LinkedHashMap<String, ScannedSig>()
|
||||||
// fun name(params): Type
|
// fun name(params): Type (allowing modifiers like abstract, override, closed)
|
||||||
val funRe = Regex("^\\s*fun\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(([^)]*)\\)\\s*(?::\\s*([A-Za-z_][A-Za-z0-9_]*))?", RegexOption.MULTILINE)
|
val funRe = Regex("^\\s*(?:(?:abstract|override|closed|private|protected|static|open|extern)\\s+)*fun\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(([^)]*)\\)\\s*(?::\\s*([A-Za-z_][A-Za-z0-9_]*))?", RegexOption.MULTILINE)
|
||||||
for (m in funRe.findAll(body)) {
|
for (m in funRe.findAll(body)) {
|
||||||
val name = m.groupValues.getOrNull(1) ?: continue
|
val name = m.groupValues.getOrNull(1) ?: continue
|
||||||
val params = m.groupValues.getOrNull(2)?.split(',')?.mapNotNull { it.trim().takeIf { it.isNotEmpty() } } ?: emptyList()
|
val params = m.groupValues.getOrNull(2)?.split(',')?.mapNotNull { it.trim().takeIf { it.isNotEmpty() } } ?: emptyList()
|
||||||
val type = m.groupValues.getOrNull(3)?.takeIf { it.isNotBlank() }
|
val type = m.groupValues.getOrNull(3)?.takeIf { it.isNotBlank() }
|
||||||
map[name] = ScannedSig("fun", params, type)
|
map[name] = ScannedSig("fun", params, type)
|
||||||
}
|
}
|
||||||
// val/var name: Type
|
// val/var name: Type (allowing modifiers)
|
||||||
val valRe = Regex("^\\s*(val|var)\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*(?::\\s*([A-Za-z_][A-Za-z0-9_]*))?", RegexOption.MULTILINE)
|
val valRe = Regex("^\\s*(?:(?:abstract|override|closed|private|protected|static|open|extern)\\s+)*(val|var)\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*(?::\\s*([A-Za-z_][A-Za-z0-9_]*))?", RegexOption.MULTILINE)
|
||||||
for (m in valRe.findAll(body)) {
|
for (m in valRe.findAll(body)) {
|
||||||
val kind = m.groupValues.getOrNull(1) ?: continue
|
val kind = m.groupValues.getOrNull(1) ?: continue
|
||||||
val name = m.groupValues.getOrNull(2) ?: continue
|
val name = m.groupValues.getOrNull(2) ?: continue
|
||||||
|
|||||||
@ -89,7 +89,7 @@ open class Obj {
|
|||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
if (rec != null) {
|
if (rec != null && !rec.isAbstract && rec.type != ObjRecord.Type.Property) {
|
||||||
val decl = rec.declaringClass ?: cls
|
val decl = rec.declaringClass ?: cls
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
if (!canAccessMember(rec.visibility, decl, caller))
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
@ -347,8 +347,12 @@ open class Obj {
|
|||||||
// 1. Hierarchy members (excluding root fallback)
|
// 1. Hierarchy members (excluding root fallback)
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
cls.members[name]?.let { return resolveRecord(scope, it, name, it.declaringClass) }
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
cls.classScope?.objects?.get(name)?.let { return resolveRecord(scope, it, name, it.declaringClass) }
|
if (rec != null) {
|
||||||
|
if (!rec.isAbstract) {
|
||||||
|
return resolveRecord(scope, rec, name, rec.declaringClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Extensions
|
// 2. Extensions
|
||||||
@ -393,8 +397,11 @@ open class Obj {
|
|||||||
// 1. Hierarchy members (excluding root fallback)
|
// 1. Hierarchy members (excluding root fallback)
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
field = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
if (field != null) break
|
if (rec != null && !rec.isAbstract) {
|
||||||
|
field = rec
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 2. Extensions
|
// 2. Extensions
|
||||||
if (field == null) {
|
if (field == null) {
|
||||||
|
|||||||
@ -91,6 +91,8 @@ open class ObjClass(
|
|||||||
vararg parents: ObjClass,
|
vararg parents: ObjClass,
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
|
|
||||||
|
var isAbstract: 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()
|
||||||
var layoutVersion: Int = 0
|
var layoutVersion: Int = 0
|
||||||
@ -178,7 +180,13 @@ open class ObjClass(
|
|||||||
val mro: List<ObjClass> by lazy {
|
val mro: List<ObjClass> by lazy {
|
||||||
val base = c3Linearize(this, mutableMapOf())
|
val base = c3Linearize(this, mutableMapOf())
|
||||||
if (this.className == "Obj" || base.any { it.className == "Obj" }) base
|
if (this.className == "Obj" || base.any { it.className == "Obj" }) base
|
||||||
else base + rootObjectType
|
else {
|
||||||
|
// During very early bootstrap rootObjectType might not be initialized yet.
|
||||||
|
// We use a safe check here.
|
||||||
|
@Suppress("UNNECESSARY_SAFE_CALL")
|
||||||
|
val root = net.sergeych.lyng.obj.Obj.rootObjectType
|
||||||
|
if (root != null) base + root else base
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parents in C3 order (no self). */
|
/** Parents in C3 order (no self). */
|
||||||
@ -204,6 +212,7 @@ open class ObjClass(
|
|||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
|
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
|
||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
if (isAbstract) scope.raiseError("can't instantiate abstract class $className")
|
||||||
val instance = createInstance(scope)
|
val instance = createInstance(scope)
|
||||||
initializeInstance(instance, scope.args, runConstructors = true)
|
initializeInstance(instance, scope.args, runConstructors = true)
|
||||||
return instance
|
return instance
|
||||||
@ -347,14 +356,54 @@ open class ObjClass(
|
|||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
writeVisibility: Visibility? = null,
|
writeVisibility: Visibility? = null,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
declaringClass: ObjClass? = this
|
declaringClass: ObjClass? = this,
|
||||||
|
isAbstract: Boolean = false,
|
||||||
|
isClosed: Boolean = false,
|
||||||
|
isOverride: Boolean = false,
|
||||||
|
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||||
) {
|
) {
|
||||||
|
// Validation of override rules: only for non-system declarations
|
||||||
|
if (pos != Pos.builtIn) {
|
||||||
|
val existing = getInstanceMemberOrNull(name)
|
||||||
|
var actualOverride = false
|
||||||
|
if (existing != null && existing.declaringClass != this) {
|
||||||
|
// If the existing member is private in the ancestor, it's not visible for overriding.
|
||||||
|
// It should be treated as a new member in this class.
|
||||||
|
if (!existing.visibility.isPublic && !canAccessMember(existing.visibility, existing.declaringClass, this)) {
|
||||||
|
// It's effectively not there for us, so actualOverride remains false
|
||||||
|
} else {
|
||||||
|
actualOverride = true
|
||||||
|
// It's an override (implicit or explicit)
|
||||||
|
if (existing.isClosed)
|
||||||
|
throw ScriptError(pos, "can't override closed member $name from ${existing.declaringClass?.className}")
|
||||||
|
|
||||||
|
if (!isOverride)
|
||||||
|
throw ScriptError(pos, "member $name overrides parent member but 'override' keyword is missing")
|
||||||
|
|
||||||
|
if (visibility.ordinal > existing.visibility.ordinal)
|
||||||
|
throw ScriptError(pos, "can't narrow visibility of $name from ${existing.visibility} to $visibility")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOverride && !actualOverride) {
|
||||||
|
throw ScriptError(pos, "member $name is marked 'override' but does not override anything")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Allow overriding ancestors: only prevent redefinition if THIS class already defines an immutable member
|
// Allow overriding ancestors: only prevent redefinition if THIS class already defines an immutable member
|
||||||
val existingInSelf = members[name]
|
val existingInSelf = members[name]
|
||||||
if (existingInSelf != null && existingInSelf.isMutable == false)
|
if (existingInSelf != null && existingInSelf.isMutable == false)
|
||||||
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
|
||||||
members[name] = ObjRecord(initialValue, isMutable, visibility, writeVisibility, declaringClass = declaringClass)
|
members[name] = ObjRecord(
|
||||||
|
initialValue, isMutable, visibility, writeVisibility,
|
||||||
|
declaringClass = declaringClass,
|
||||||
|
isAbstract = isAbstract,
|
||||||
|
isClosed = isClosed,
|
||||||
|
isOverride = isOverride,
|
||||||
|
type = type
|
||||||
|
)
|
||||||
// Structural change: bump layout version for PIC invalidation
|
// Structural change: bump layout version for PIC invalidation
|
||||||
layoutVersion += 1
|
layoutVersion += 1
|
||||||
}
|
}
|
||||||
@ -383,14 +432,22 @@ open class ObjClass(
|
|||||||
|
|
||||||
fun addFn(
|
fun addFn(
|
||||||
name: String,
|
name: String,
|
||||||
isOpen: Boolean = false,
|
isMutable: Boolean = false,
|
||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
writeVisibility: Visibility? = null,
|
writeVisibility: Visibility? = null,
|
||||||
declaringClass: ObjClass? = this,
|
declaringClass: ObjClass? = this,
|
||||||
code: suspend Scope.() -> Obj
|
isAbstract: Boolean = false,
|
||||||
|
isClosed: Boolean = false,
|
||||||
|
isOverride: Boolean = false,
|
||||||
|
pos: Pos = Pos.builtIn,
|
||||||
|
code: (suspend Scope.() -> Obj)? = null
|
||||||
) {
|
) {
|
||||||
val stmt = statement { code() }
|
val stmt = code?.let { statement { it() } } ?: ObjNull
|
||||||
createField(name, stmt, isOpen, visibility, writeVisibility, Pos.builtIn, declaringClass)
|
createField(
|
||||||
|
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
||||||
|
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||||
|
type = ObjRecord.Type.Fun
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
|
fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
|
||||||
@ -401,13 +458,20 @@ open class ObjClass(
|
|||||||
setter: (suspend Scope.(Obj) -> Unit)? = null,
|
setter: (suspend Scope.(Obj) -> Unit)? = null,
|
||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
writeVisibility: Visibility? = null,
|
writeVisibility: Visibility? = null,
|
||||||
declaringClass: ObjClass? = this
|
declaringClass: ObjClass? = this,
|
||||||
|
isAbstract: Boolean = false,
|
||||||
|
isClosed: Boolean = false,
|
||||||
|
isOverride: Boolean = false,
|
||||||
|
pos: Pos = Pos.builtIn,
|
||||||
) {
|
) {
|
||||||
val g = getter?.let { statement { it() } }
|
val g = getter?.let { statement { it() } }
|
||||||
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
|
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
|
||||||
val prop = ObjProperty(name, g, s)
|
val prop = if (isAbstract) ObjNull else ObjProperty(name, g, s)
|
||||||
members[name] = ObjRecord(prop, false, visibility, writeVisibility, declaringClass, type = ObjRecord.Type.Property)
|
createField(
|
||||||
layoutVersion += 1
|
name, prop, false, visibility, writeVisibility, pos, declaringClass,
|
||||||
|
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||||
|
type = ObjRecord.Type.Property
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
||||||
@ -442,6 +506,38 @@ open class ObjClass(
|
|||||||
getInstanceMemberOrNull(name)
|
getInstanceMemberOrNull(name)
|
||||||
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
||||||
|
|
||||||
|
fun findFirstConcreteMember(name: String): ObjRecord? {
|
||||||
|
for (cls in mro) {
|
||||||
|
cls.members[name]?.let {
|
||||||
|
if (!it.isAbstract) return it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkAbstractSatisfaction(pos: Pos) {
|
||||||
|
if (isAbstract) return
|
||||||
|
|
||||||
|
val missing = mutableSetOf<String>()
|
||||||
|
for (cls in mroParents) {
|
||||||
|
for ((name, rec) in cls.members) {
|
||||||
|
if (rec.isAbstract) {
|
||||||
|
val current = findFirstConcreteMember(name)
|
||||||
|
if (current == null) {
|
||||||
|
missing.add(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missing.isNotEmpty()) {
|
||||||
|
throw ScriptError(
|
||||||
|
pos,
|
||||||
|
"class $className is not abstract and does not implement abstract members: ${missing.joinToString(", ")}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve member starting from a specific ancestor class [start], not from this class.
|
* Resolve member starting from a specific ancestor class [start], not from this class.
|
||||||
* Searches [start] first, then traverses its linearized parents.
|
* Searches [start] first, then traverses its linearized parents.
|
||||||
|
|||||||
@ -58,10 +58,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
// self first, then parents
|
// self first, then parents
|
||||||
fun findMangled(): ObjRecord? {
|
fun findMangled(): ObjRecord? {
|
||||||
// self
|
// self
|
||||||
instanceScope.objects["${cls.className}::$name"]?.let { return it }
|
instanceScope.objects["${cls.className}::$name"]?.let {
|
||||||
|
if (name == "c") println("[DEBUG_LOG] findMangled('c') found in self (${cls.className}): value=${it.value}")
|
||||||
|
return it
|
||||||
|
}
|
||||||
// ancestors in deterministic C3 order
|
// ancestors in deterministic C3 order
|
||||||
for (p in cls.mroParents) {
|
for (p in cls.mroParents) {
|
||||||
instanceScope.objects["${p.className}::$name"]?.let { return it }
|
instanceScope.objects["${p.className}::$name"]?.let {
|
||||||
|
if (name == "c") println("[DEBUG_LOG] findMangled('c') found in parent (${p.className}): value=${it.value}")
|
||||||
|
return it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -125,6 +131,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
|
|
||||||
val rec = findMangled()
|
val rec = findMangled()
|
||||||
if (rec != null) {
|
if (rec != null) {
|
||||||
|
if (name == "c") println("[DEBUG_LOG] writeField('c') found in mangled: value was ${rec.value}, setting to $newValue")
|
||||||
val declaring = when {
|
val declaring = when {
|
||||||
instanceScope.objects.containsKey("${cls.className}::$name") -> cls
|
instanceScope.objects.containsKey("${cls.className}::$name") -> cls
|
||||||
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
|
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
|
||||||
@ -155,34 +162,40 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
onNotFoundResult: (suspend () -> Obj?)?
|
onNotFoundResult: (suspend () -> Obj?)?
|
||||||
): Obj =
|
): Obj =
|
||||||
instanceScope[name]?.let { rec ->
|
instanceScope[name]?.let { rec ->
|
||||||
val decl = rec.declaringClass
|
if (rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
||||||
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
else {
|
||||||
if (!canAccessMember(rec.visibility, decl, caller))
|
val decl = rec.declaringClass
|
||||||
scope.raiseError(
|
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
||||||
ObjAccessException(
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
scope,
|
scope.raiseError(
|
||||||
"can't invoke method $name (declared in ${decl?.className ?: "?"})"
|
ObjAccessException(
|
||||||
|
scope,
|
||||||
|
"can't invoke method $name (declared in ${decl?.className ?: "?"})"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
rec.value.invoke(
|
||||||
|
instanceScope,
|
||||||
|
this,
|
||||||
|
args
|
||||||
)
|
)
|
||||||
rec.value.invoke(
|
}
|
||||||
instanceScope,
|
|
||||||
this,
|
|
||||||
args
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
?: run {
|
?: run {
|
||||||
// fallback: class-scope function (registered during class body execution)
|
// fallback: class-scope function (registered during class body execution)
|
||||||
objClass.classScope?.objects?.get(name)?.let { rec ->
|
objClass.classScope?.objects?.get(name)?.let { rec ->
|
||||||
val decl = rec.declaringClass
|
if (rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
||||||
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
else {
|
||||||
if (!canAccessMember(rec.visibility, decl, caller))
|
val decl = rec.declaringClass
|
||||||
scope.raiseError(
|
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
||||||
ObjAccessException(
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
scope,
|
scope.raiseError(
|
||||||
"can't invoke method $name (declared in ${decl?.className ?: "?"})"
|
ObjAccessException(
|
||||||
|
scope,
|
||||||
|
"can't invoke method $name (declared in ${decl?.className ?: "?"})"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
rec.value.invoke(instanceScope, this, args)
|
||||||
rec.value.invoke(instanceScope, this, args)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||||
|
|||||||
@ -32,6 +32,9 @@ data class ObjRecord(
|
|||||||
var importedFrom: Scope? = null,
|
var importedFrom: Scope? = null,
|
||||||
val isTransient: Boolean = false,
|
val isTransient: Boolean = false,
|
||||||
val type: Type = Type.Other,
|
val type: Type = Type.Other,
|
||||||
|
val isAbstract: Boolean = false,
|
||||||
|
val isClosed: Boolean = false,
|
||||||
|
val isOverride: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val effectiveWriteVisibility: Visibility get() = writeVisibility ?: visibility
|
val effectiveWriteVisibility: Visibility get() = writeVisibility ?: visibility
|
||||||
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
|
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
|
||||||
|
|||||||
@ -1182,8 +1182,11 @@ class MethodCallRef(
|
|||||||
var hierarchyMember: ObjRecord? = null
|
var hierarchyMember: ObjRecord? = null
|
||||||
for (cls in base.objClass.mro) {
|
for (cls in base.objClass.mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
hierarchyMember = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
if (hierarchyMember != null) break
|
if (rec != null && !rec.isAbstract && rec.type != ObjRecord.Type.Field) {
|
||||||
|
hierarchyMember = rec
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hierarchyMember != null) {
|
if (hierarchyMember != null) {
|
||||||
|
|||||||
@ -52,8 +52,8 @@ class MIC3MroTest {
|
|||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
class A() { fun common() { "A" } }
|
class A() { fun common() { "A" } }
|
||||||
class B() : A() { fun common() { "B" } }
|
class B() : A() { override fun common() { "B" } }
|
||||||
class C() : A() { fun common() { "C" } }
|
class C() : A() { override fun common() { "C" } }
|
||||||
class D() : B(), C()
|
class D() : B(), C()
|
||||||
|
|
||||||
val d = D()
|
val d = D()
|
||||||
|
|||||||
@ -348,7 +348,8 @@ class OOTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testPropAsExtension() = runTest {
|
fun testPropAsExtension() = runTest {
|
||||||
val scope = Script.newScope()
|
val scope = Script.newScope()
|
||||||
scope.eval("""
|
scope.eval(
|
||||||
|
"""
|
||||||
class A(x) {
|
class A(x) {
|
||||||
private val privateVal = 100
|
private val privateVal = 100
|
||||||
val p1 get() = x + 1
|
val p1 get() = x + 1
|
||||||
@ -368,7 +369,8 @@ class OOTest {
|
|||||||
// it should also work with properties:
|
// it should also work with properties:
|
||||||
val A.p10 get() = x * 10
|
val A.p10 get() = x * 10
|
||||||
assertEquals(20, A(2).p10)
|
assertEquals(20, A(2).p10)
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
// important is that such extensions should not be able to access private members
|
// important is that such extensions should not be able to access private members
|
||||||
// and thus remove privateness:
|
// and thus remove privateness:
|
||||||
@ -383,23 +385,28 @@ class OOTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testExtensionsAreScopeIsolated() = runTest {
|
fun testExtensionsAreScopeIsolated() = runTest {
|
||||||
val scope1 = Script.newScope()
|
val scope1 = Script.newScope()
|
||||||
scope1.eval("""
|
scope1.eval(
|
||||||
|
"""
|
||||||
val String.totalDigits get() {
|
val String.totalDigits get() {
|
||||||
// notice using `this`:
|
// notice using `this`:
|
||||||
this.characters.filter{ it.isDigit() }.size()
|
this.characters.filter{ it.isDigit() }.size()
|
||||||
}
|
}
|
||||||
assertEquals(2, "answer is 42".totalDigits)
|
assertEquals(2, "answer is 42".totalDigits)
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
val scope2 = Script.newScope()
|
val scope2 = Script.newScope()
|
||||||
scope2.eval("""
|
scope2.eval(
|
||||||
|
"""
|
||||||
// in scope2 we didn't override `totalDigits` extension:
|
// in scope2 we didn't override `totalDigits` extension:
|
||||||
assertThrows { "answer is 42".totalDigits }
|
assertThrows { "answer is 42".totalDigits }
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCacheInClass() = runTest {
|
fun testCacheInClass() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
class T(salt) {
|
class T(salt) {
|
||||||
private var c
|
private var c
|
||||||
|
|
||||||
@ -416,24 +423,28 @@ class OOTest {
|
|||||||
assertEquals("bar.", t2.getResult())
|
assertEquals("bar.", t2.getResult())
|
||||||
assertEquals("foo.", t1.getResult())
|
assertEquals("foo.", t1.getResult())
|
||||||
|
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLateInitValsInClasses() = runTest {
|
fun testLateInitValsInClasses() = runTest {
|
||||||
assertFails {
|
assertFails {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
class T {
|
class T {
|
||||||
val x
|
val x
|
||||||
}
|
}
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertFails {
|
assertFails {
|
||||||
eval("val String.late")
|
eval("val String.late")
|
||||||
}
|
}
|
||||||
|
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
// but we can "late-init" them in init block:
|
// but we can "late-init" them in init block:
|
||||||
class OK {
|
class OK {
|
||||||
val x
|
val x
|
||||||
@ -463,12 +474,14 @@ class OOTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AccessBefore()
|
AccessBefore()
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testPrivateSet() = runTest {
|
fun testPrivateSet() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
class A {
|
class A {
|
||||||
var y = 100
|
var y = 100
|
||||||
private set
|
private set
|
||||||
@ -505,7 +518,8 @@ class OOTest {
|
|||||||
assertThrows(AccessException) { d.y = 10 }
|
assertThrows(AccessException) { d.y = 10 }
|
||||||
d.setY(20)
|
d.setY(20)
|
||||||
assertEquals(20, d.y)
|
assertEquals(20, d.y)
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -515,4 +529,205 @@ class OOTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAbstractClassesAndOverridingProposal() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
/*
|
||||||
|
Abstract class is a sort of interface on steroidsL it is a class some members/methods of which
|
||||||
|
are required to be implemented by heirs. Still it is a regular class in all other respects.
|
||||||
|
Just can't be instantiated
|
||||||
|
*/
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
// abstract modifier is required. It can have a constructor, or be without it:
|
||||||
|
abstract class A(someParam=1) {
|
||||||
|
// if the method is marked as abstract, it has no body:
|
||||||
|
abstract fun foo(): Int
|
||||||
|
|
||||||
|
// abstract var/var have no initializer:
|
||||||
|
abstract var bar
|
||||||
|
}
|
||||||
|
// can't create instance of the abstract class:
|
||||||
|
assertThrows { A() }
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
// create abstract method with body or val/var with initializer is an error:
|
||||||
|
assertFails { scope.eval("abstract class B { abstract fun foo() = 1 }") }
|
||||||
|
assertFails { scope.eval("abstract class C { abstract val bar = 1 }") }
|
||||||
|
|
||||||
|
// inheriting an abstract class without implementing all of it abstract members and methods
|
||||||
|
// is not allowed:
|
||||||
|
assertFails { scope.eval("class D : A(1) { override fun foo() = 10 }") }
|
||||||
|
|
||||||
|
// but it is allowed to inherit in another abstract class:
|
||||||
|
scope.eval("abstract class E : A(1) { override fun foo() = 10 }")
|
||||||
|
|
||||||
|
// implementing all abstracts let us have regular class:
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
class F : E() { override val bar = 11 }
|
||||||
|
assertEquals(10, F().foo())
|
||||||
|
assertEquals(11, F().bar)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Another possibility to override symbol is multiple inheritance: the parent that
|
||||||
|
// follows the abstract class in MI chain can override the abstract symbol:
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
// This implementor know nothing of A but still implements de-facto its needs:
|
||||||
|
class Implementor {
|
||||||
|
val bar = 3
|
||||||
|
fun foo() = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we can use MI to implement abstract class:
|
||||||
|
class F2 : A(42), Implementor
|
||||||
|
|
||||||
|
assertEquals(1, F2().foo())
|
||||||
|
assertEquals(3, F2().bar)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
/*
|
||||||
|
Notes.
|
||||||
|
|
||||||
|
The override keyword is an _optional_ flag that the symbol must exist in one of the parents
|
||||||
|
and can be overridden.
|
||||||
|
|
||||||
|
Compiler checks as early as possible that the symbol exists in one of the parents and is open.
|
||||||
|
By default, all public/protected symbols are open. If there is no such symbol, the exception is thrown.
|
||||||
|
|
||||||
|
In contrast, if the symbol has no special flags, the compiler either creates a new one of overrides
|
||||||
|
existing, checking that override is allowed.
|
||||||
|
|
||||||
|
Question to AI: the keyword to mark non-overridable symbols? final is not the best option as for me.
|
||||||
|
|
||||||
|
Overriding the var/val should also be possible with an initializer of with get()/set(value).
|
||||||
|
|
||||||
|
overriding can't alter visibility: it must remain as declared in the parent. Private symbols can't be
|
||||||
|
neither declared abstract nor overridden.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAbstractAndOverrideEdgeCases() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
|
||||||
|
// 1. abstract private is an error:
|
||||||
|
assertFails { scope.eval("abstract class Err { abstract private fun foo() }") }
|
||||||
|
assertFails { scope.eval("abstract class Err { abstract private val x }") }
|
||||||
|
|
||||||
|
// 2. private member in parent is not visible for overriding:
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
class Base {
|
||||||
|
private fun secret() = 1
|
||||||
|
fun callSecret() = secret()
|
||||||
|
}
|
||||||
|
class Derived : Base() {
|
||||||
|
// This is NOT an override, but a new method
|
||||||
|
fun secret() = 2
|
||||||
|
}
|
||||||
|
val d = Derived()
|
||||||
|
assertEquals(2, d.secret())
|
||||||
|
assertEquals(1, d.callSecret())
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
// Using override keyword when there is only a private member in parent is an error:
|
||||||
|
assertFails { scope.eval("class D2 : Base() { override fun secret() = 3 }") }
|
||||||
|
|
||||||
|
// 3. interface can have state (constructor, fields, init):
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
interface I(val x) {
|
||||||
|
var y = x * 2
|
||||||
|
val z
|
||||||
|
init {
|
||||||
|
z = y + 1
|
||||||
|
}
|
||||||
|
fun foo() = x + y + z
|
||||||
|
}
|
||||||
|
class Impl : I(10)
|
||||||
|
val impl = Impl()
|
||||||
|
assertEquals(10, impl.x)
|
||||||
|
assertEquals(20, impl.y)
|
||||||
|
assertEquals(21, impl.z)
|
||||||
|
assertEquals(51, impl.foo())
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
// 4. closed members cannot be overridden:
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
class G {
|
||||||
|
closed fun locked() = "locked"
|
||||||
|
closed val permanent = 42
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertFails { scope.eval("class H : G() { override fun locked() = \"free\" }") }
|
||||||
|
assertFails { scope.eval("class H : G() { override val permanent = 0 }") }
|
||||||
|
// Even without override keyword, it should fail if it's closed:
|
||||||
|
assertFails { scope.eval("class H : G() { fun locked() = \"free\" }") }
|
||||||
|
|
||||||
|
// 5. Visibility widening is allowed, narrowing is forbidden:
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
class BaseVis {
|
||||||
|
protected fun prot() = 1
|
||||||
|
}
|
||||||
|
class Widened : BaseVis() {
|
||||||
|
override fun prot() = 2 // Widened to public (default)
|
||||||
|
}
|
||||||
|
assertEquals(2, Widened().prot())
|
||||||
|
|
||||||
|
class BasePub {
|
||||||
|
fun pub() = 1
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
// Narrowing:
|
||||||
|
assertFails { scope.eval("class Narrowed : BasePub() { override protected fun pub() = 2 }") }
|
||||||
|
assertFails { scope.eval("class Narrowed : BasePub() { override private fun pub() = 2 }") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testInterfaceImplementationByParts() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
// Interface with state (id) and abstract requirements
|
||||||
|
interface Character(val id) {
|
||||||
|
abstract var health
|
||||||
|
abstract var mana
|
||||||
|
fun isAlive() = health > 0
|
||||||
|
fun status() = name + " (#" + id + "): " + health + " HP, " + mana + " MP"
|
||||||
|
// name is also abstractly required by the status method,
|
||||||
|
// even if not explicitly marked 'abstract val' here,
|
||||||
|
// it will be looked up in MRO
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part 1: Provides health
|
||||||
|
class HealthPool(var health)
|
||||||
|
|
||||||
|
// Part 2: Provides mana and name
|
||||||
|
class ManaPool(var mana) {
|
||||||
|
val name = "Hero"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Composite class implementing Character by parts
|
||||||
|
class Warrior(id, h, m) : HealthPool(h), ManaPool(m), Character(id)
|
||||||
|
|
||||||
|
val w = Warrior(1, 100, 50)
|
||||||
|
assertEquals(100, w.health)
|
||||||
|
assertEquals(50, w.mana)
|
||||||
|
assertEquals(1, w.id)
|
||||||
|
assert(w.isAlive())
|
||||||
|
assertEquals("Hero (#1): 100 HP, 50 MP", w.status())
|
||||||
|
|
||||||
|
w.health = 0
|
||||||
|
assert(!w.isAlive())
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -530,10 +530,10 @@ private fun detectDeclarationAndParamOverrides(text: String): Map<Pair<Int, Int>
|
|||||||
fun isIdentPart(ch: Char) = ch == '_' || ch == '$' || ch == '~' || ch.isLetterOrDigit()
|
fun isIdentPart(ch: Char) = ch == '_' || ch == '$' || ch == '~' || ch.isLetterOrDigit()
|
||||||
// A conservative list of language keywords to avoid misclassifying as function calls
|
// A conservative list of language keywords to avoid misclassifying as function calls
|
||||||
val kw = setOf(
|
val kw = setOf(
|
||||||
"package", "import", "fun", "fn", "class", "enum", "val", "var",
|
"package", "import", "fun", "fn", "class", "interface", "enum", "val", "var",
|
||||||
"if", "else", "while", "do", "for", "when", "try", "catch", "finally",
|
"if", "else", "while", "do", "for", "when", "try", "catch", "finally",
|
||||||
"throw", "return", "break", "continue", "in", "is", "as", "as?", "not",
|
"throw", "return", "break", "continue", "in", "is", "as", "as?", "not",
|
||||||
"true", "false", "null", "private", "protected", "open", "extern", "static",
|
"true", "false", "null", "private", "protected", "abstract", "closed", "override", "open", "extern", "static",
|
||||||
"init", "get", "set", "Unset", "by"
|
"init", "get", "set", "Unset", "by"
|
||||||
)
|
)
|
||||||
fun skipWs(idx0: Int): Int {
|
fun skipWs(idx0: Int): Int {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user