Make ObjInt and ObjReal immutable and update number operations accordingly. Add support for class properties with get/set accessors. Rework loop parsing logic to improve clarity and consistency. Update .gitignore and TextMate grammar. Enhance Changelog and document new features.
This commit is contained in:
parent
157b716eb7
commit
5f3a54d08f
3
.gitignore
vendored
3
.gitignore
vendored
@ -17,5 +17,6 @@ xcuserdata
|
||||
/sample_texts/1.txt.gz
|
||||
/kotlin-js-store/wasm/yarn.lock
|
||||
/distributables
|
||||
/.output.txt
|
||||
.output*.txt
|
||||
debug.log
|
||||
/build.log
|
||||
|
||||
@ -2,6 +2,15 @@
|
||||
|
||||
### Unreleased
|
||||
|
||||
- Language: Class properties with accessors
|
||||
- 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 }`.
|
||||
- Laconic Expression Shorthand: `val prop get() = expression` and `var prop get() = read set(v) = write`.
|
||||
- Properties are pure accessors and do **not** have automatic backing fields.
|
||||
- Validation: `var` properties must have both accessors; `val` must have only a getter.
|
||||
- Integration: Updated TextMate grammar and IntelliJ plugin (highlighting + keywords).
|
||||
- Documentation: New "Properties" section in `docs/OOP.md`.
|
||||
|
||||
- Docs: Scopes and Closures guidance
|
||||
- New page: `docs/scopes_and_closures.md` detailing `ClosureScope` resolution order, recursion‑safe helpers (`chainLookupIgnoreClosure`, `chainLookupWithMembers`, `baseGetIgnoreClosure`), cycle prevention, and capturing lexical environments for callbacks (`snapshotForClosure`).
|
||||
- Updated: `docs/advanced_topics.md` (link to the new page), `docs/parallelism.md` (closures in `launch`/`flow`), `docs/OOP.md` (visibility from closures with preserved `currentClassCtx`), `docs/exceptions_handling.md` (compatibility alias `SymbolNotFound`).
|
||||
|
||||
@ -189,6 +189,7 @@ Ready features:
|
||||
- [x] better stack reporting
|
||||
- [x] regular exceptions + extended `when`
|
||||
- [x] multiple inheritance for user classes
|
||||
- [x] class properties (accessors)
|
||||
|
||||
## plan: towards v1.5 Enhancing
|
||||
|
||||
|
||||
53
docs/OOP.md
53
docs/OOP.md
@ -42,6 +42,59 @@ a _constructor_ that requires two parameters for fields. So when creating it wit
|
||||
Form now on `Point` is a class, it's type is `Class`, and we can create instances with it as in the
|
||||
example above.
|
||||
|
||||
## Properties
|
||||
|
||||
Properties allow you to define member accessors that look like fields but execute code when read or written. Unlike regular fields, properties in Lyng do **not** have automatic backing fields; they are pure accessors.
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
Properties are declared using `val` (read-only) or `var` (read-write) followed by a name and `get()`/`set()` blocks:
|
||||
|
||||
```kotlin
|
||||
class Person(private var _age: Int) {
|
||||
// Read-only property
|
||||
val ageCategory
|
||||
get() {
|
||||
if (_age < 18) "Minor" else "Adult"
|
||||
}
|
||||
|
||||
// Read-write property
|
||||
var age: Int
|
||||
get() { _age }
|
||||
set(value) {
|
||||
if (value >= 0) _age = value
|
||||
}
|
||||
}
|
||||
|
||||
val p = Person(15)
|
||||
assertEquals("Minor", p.ageCategory)
|
||||
p.age = 20
|
||||
assertEquals("Adult", p.ageCategory)
|
||||
```
|
||||
|
||||
### Laconic Expression Shorthand
|
||||
|
||||
For simple accessors, you can use the `=` shorthand for a more elegant and laconic form:
|
||||
|
||||
```kotlin
|
||||
class Circle(val radius: Real) {
|
||||
val area get() = π * radius * radius
|
||||
val circumference get() = 2 * π * radius
|
||||
}
|
||||
|
||||
class Counter {
|
||||
private var _count = 0
|
||||
var count get() = _count set(v) = _count = v
|
||||
}
|
||||
```
|
||||
|
||||
### Key Rules
|
||||
|
||||
- **`val` properties** must have a `get()` accessor and cannot have a `set()`.
|
||||
- **`var` properties** must have both `get()` and `set()` accessors.
|
||||
- **No Backing Fields**: There is no magic `field` identifier. If you need to store state, you must declare a separate (usually `private`) field.
|
||||
- **Type Inference**: You can omit the type declaration if it can be inferred or if you don't need strict typing.
|
||||
|
||||
## Instance initialization: init block
|
||||
|
||||
In addition to the primary constructor arguments, you can provide an `init` block that runs on each instance creation. This is useful for more complex initializations, side effects, or setting up fields that depend on multiple constructor parameters.
|
||||
|
||||
@ -77,7 +77,7 @@
|
||||
"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]*" } ] },
|
||||
"declarations": { "patterns": [ { "name": "meta.function.declaration.lyng", "match": "\\b(?:fun|fn)\\s+([\\p{L}_][\\p{L}\\p{N}_]*)", "captures": { "1": { "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}_]*)", "captures": { "1": { "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)\\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|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" } ] },
|
||||
"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*\\()" } ] },
|
||||
"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": "[!?]" } ] },
|
||||
|
||||
@ -34,7 +34,8 @@ class LyngLexer : LexerBase() {
|
||||
private val keywords = setOf(
|
||||
"fun", "val", "var", "class", "type", "import", "as",
|
||||
"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"
|
||||
)
|
||||
|
||||
override fun start(buffer: CharSequence, startOffset: Int, endOffset: Int, initialState: Int) {
|
||||
|
||||
@ -1956,7 +1956,7 @@ class Compiler(
|
||||
while (cc.hasPrevious() && cnt < maxDepth) {
|
||||
val t = cc.previous()
|
||||
cnt++
|
||||
if (t.type == Token.Type.LABEL) {
|
||||
if (t.type == Token.Type.LABEL || t.type == Token.Type.ATLABEL) {
|
||||
found = t.value
|
||||
break
|
||||
}
|
||||
@ -1987,7 +1987,8 @@ class Compiler(
|
||||
val namesForLoop = (currentLocalNames?.toSet() ?: emptySet()) + tVar.value
|
||||
val (canBreak, body, elseStatement) = withLocalNames(namesForLoop) {
|
||||
val loopParsed = cc.parseLoop {
|
||||
parseStatement() ?: throw ScriptError(start, "Bad for statement: expected loop body")
|
||||
if (cc.current().type == Token.Type.LBRACE) parseBlock()
|
||||
else parseStatement() ?: throw ScriptError(start, "Bad for statement: expected loop body")
|
||||
}
|
||||
// possible else clause
|
||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||
@ -2050,21 +2051,19 @@ class Compiler(
|
||||
var index = 0
|
||||
while (true) {
|
||||
loopSO.value = current
|
||||
if (canBreak) {
|
||||
try {
|
||||
result = body.execute(forContext)
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
breakCaught = true
|
||||
if (lbe.doContinue) continue
|
||||
else {
|
||||
result = lbe.result
|
||||
break
|
||||
}
|
||||
} else
|
||||
throw lbe
|
||||
}
|
||||
} else result = body.execute(forContext)
|
||||
try {
|
||||
result = body.execute(forContext)
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
breakCaught = true
|
||||
if (lbe.doContinue) continue
|
||||
else {
|
||||
result = lbe.result
|
||||
break
|
||||
}
|
||||
} else
|
||||
throw lbe
|
||||
}
|
||||
if (++index >= size) break
|
||||
current = sourceObj.getAt(forContext, ObjInt(index.toLong()))
|
||||
}
|
||||
@ -2086,11 +2085,9 @@ class Compiler(
|
||||
body: Statement, elseStatement: Statement?, label: String?, catchBreak: Boolean
|
||||
): Obj {
|
||||
var result: Obj = ObjVoid
|
||||
val iVar = ObjInt(0)
|
||||
loopVar.value = iVar
|
||||
if (catchBreak) {
|
||||
for (i in start..<end) {
|
||||
iVar.value = i//.toLong()
|
||||
loopVar.value = ObjInt(i)
|
||||
try {
|
||||
result = body.execute(forScope)
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
@ -2103,7 +2100,7 @@ class Compiler(
|
||||
}
|
||||
} else {
|
||||
for (i in start..<end) {
|
||||
iVar.value = i
|
||||
loopVar.value = ObjInt(i)
|
||||
result = body.execute(forScope)
|
||||
}
|
||||
}
|
||||
@ -2152,20 +2149,18 @@ class Compiler(
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
private suspend fun parseDoWhileStatement(): Statement {
|
||||
val label = getLabel()?.also { cc.labels += it }
|
||||
val (breakFound, body) = cc.parseLoop {
|
||||
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad while statement: expected statement")
|
||||
val (canBreak, body) = cc.parseLoop {
|
||||
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement")
|
||||
}
|
||||
label?.also { cc.labels -= it }
|
||||
|
||||
cc.skipTokens(Token.Type.NEWLINE)
|
||||
cc.skipWsTokens()
|
||||
val tWhile = cc.next()
|
||||
if (tWhile.type != Token.Type.ID || tWhile.value != "while")
|
||||
throw ScriptError(tWhile.pos, "Expected 'while' after do body")
|
||||
|
||||
val t = cc.next()
|
||||
if (t.type != Token.Type.ID && t.value != "while")
|
||||
cc.skipTokenOfType(Token.Type.LPAREN, "expected '(' here")
|
||||
|
||||
val conditionStart = ensureLparen()
|
||||
val condition =
|
||||
parseExpression() ?: throw ScriptError(conditionStart, "Bad while statement: expected expression")
|
||||
ensureLparen()
|
||||
val condition = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected condition after 'while'")
|
||||
ensureRparen()
|
||||
|
||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||
@ -2176,13 +2171,11 @@ class Compiler(
|
||||
null
|
||||
}
|
||||
|
||||
|
||||
return statement(body.pos) {
|
||||
var wasBroken = false
|
||||
var result: Obj = ObjVoid
|
||||
lateinit var doScope: Scope
|
||||
while (true) {
|
||||
doScope = it.createChildScope().apply { skipScopeCreation = true }
|
||||
val doScope = it.createChildScope().apply { skipScopeCreation = true }
|
||||
try {
|
||||
result = body.execute(doScope)
|
||||
} catch (e: LoopBreakContinueException) {
|
||||
@ -2194,11 +2187,12 @@ class Compiler(
|
||||
}
|
||||
// for continue: just fall through to condition check below
|
||||
} else {
|
||||
// Not our label, let outer loops handle it
|
||||
throw e
|
||||
}
|
||||
}
|
||||
if (!condition.execute(doScope).toBool()) break
|
||||
if (!condition.execute(doScope).toBool()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) }
|
||||
result
|
||||
@ -2212,7 +2206,10 @@ class Compiler(
|
||||
parseExpression() ?: throw ScriptError(start, "Bad while statement: expected expression")
|
||||
ensureRparen()
|
||||
|
||||
val body = parseStatement() ?: throw ScriptError(start, "Bad while statement: expected statement")
|
||||
val (canBreak, body) = cc.parseLoop {
|
||||
if (cc.current().type == Token.Type.LBRACE) parseBlock()
|
||||
else parseStatement() ?: throw ScriptError(start, "Bad while statement: expected statement")
|
||||
}
|
||||
label?.also { cc.labels -= it }
|
||||
|
||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||
@ -2226,21 +2223,23 @@ class Compiler(
|
||||
var result: Obj = ObjVoid
|
||||
var wasBroken = false
|
||||
while (condition.execute(it).toBool()) {
|
||||
try {
|
||||
// we don't need to create new context here: if body is a block,
|
||||
// parse block will do it, otherwise single statement doesn't need it:
|
||||
result = body.execute(it)
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) continue
|
||||
else {
|
||||
result = lbe.result
|
||||
wasBroken = true
|
||||
break
|
||||
}
|
||||
} else
|
||||
throw lbe
|
||||
}
|
||||
val loopScope = it.createChildScope()
|
||||
if (canBreak) {
|
||||
try {
|
||||
result = body.execute(loopScope)
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) continue
|
||||
else {
|
||||
result = lbe.result
|
||||
wasBroken = true
|
||||
break
|
||||
}
|
||||
} else
|
||||
throw lbe
|
||||
}
|
||||
} else
|
||||
result = body.execute(loopScope)
|
||||
}
|
||||
if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) }
|
||||
result
|
||||
@ -2265,7 +2264,12 @@ class Compiler(
|
||||
cc.previous()
|
||||
val resultExpr = if (t.pos.line == start.line && (!t.isComment &&
|
||||
t.type != Token.Type.SEMICOLON &&
|
||||
t.type != Token.Type.NEWLINE)
|
||||
t.type != Token.Type.NEWLINE &&
|
||||
t.type != Token.Type.RBRACE &&
|
||||
t.type != Token.Type.RPAREN &&
|
||||
t.type != Token.Type.RBRACKET &&
|
||||
t.type != Token.Type.COMMA &&
|
||||
t.type != Token.Type.EOF)
|
||||
) {
|
||||
// we have something on this line, could be expression
|
||||
parseStatement()
|
||||
@ -2635,34 +2639,50 @@ class Compiler(
|
||||
parseTypeDeclarationWithMini().second
|
||||
} else null
|
||||
|
||||
val markBeforeEq = cc.savePos()
|
||||
val eqToken = cc.next()
|
||||
var setNull = false
|
||||
var isProperty = false
|
||||
|
||||
val declaringClassNameCaptured = (codeContexts.lastOrNull() as? CodeContext.ClassBody)?.name
|
||||
|
||||
if (declaringClassNameCaptured != null) {
|
||||
val mark = cc.savePos()
|
||||
cc.restorePos(markBeforeEq)
|
||||
val next = cc.peekNextNonWhitespace()
|
||||
if (next.isId("get") || next.isId("set")) {
|
||||
isProperty = true
|
||||
cc.restorePos(markBeforeEq)
|
||||
} else {
|
||||
cc.restorePos(mark)
|
||||
}
|
||||
}
|
||||
|
||||
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
|
||||
if (!isStatic) declareLocalName(name)
|
||||
|
||||
val isDelegate = if (eqToken.isId("by")) {
|
||||
val isDelegate = if (!isProperty && eqToken.isId("by")) {
|
||||
true
|
||||
} else {
|
||||
if (eqToken.type != Token.Type.ASSIGN) {
|
||||
if (!isMutable)
|
||||
if (!isProperty && eqToken.type != Token.Type.ASSIGN) {
|
||||
if (!isMutable && (declaringClassNameCaptured == null))
|
||||
throw ScriptError(start, "val must be initialized")
|
||||
else {
|
||||
cc.previous()
|
||||
cc.restorePos(markBeforeEq)
|
||||
setNull = true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
val initialExpression = if (setNull) null
|
||||
val initialExpression = if (setNull || isProperty) null
|
||||
else parseStatement(true)
|
||||
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
||||
|
||||
// Emit MiniValDecl for this declaration (before execution wiring), attach doc if any
|
||||
run {
|
||||
val declRange = MiniRange(pendingDeclStart ?: start, cc.currentPos())
|
||||
val initR = if (setNull) null else MiniRange(eqToken.pos, cc.currentPos())
|
||||
val initR = if (setNull || isProperty) null else MiniRange(eqToken.pos, cc.currentPos())
|
||||
val node = MiniValDecl(
|
||||
range = declRange,
|
||||
name = name,
|
||||
@ -2691,8 +2711,70 @@ class Compiler(
|
||||
return NopStatement
|
||||
}
|
||||
|
||||
// Determine declaring class (if inside class body) at compile time, capture it in the closure
|
||||
val declaringClassNameCaptured = (codeContexts.lastOrNull() as? CodeContext.ClassBody)?.name
|
||||
// Check for accessors if it is a class member
|
||||
var getter: Statement? = null
|
||||
var setter: Statement? = null
|
||||
if (declaringClassNameCaptured != null) {
|
||||
while (true) {
|
||||
val t = cc.peekNextNonWhitespace()
|
||||
if (t.isId("get")) {
|
||||
cc.skipWsTokens()
|
||||
cc.next() // consume 'get'
|
||||
cc.requireToken(Token.Type.LPAREN)
|
||||
cc.requireToken(Token.Type.RPAREN)
|
||||
getter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
||||
cc.skipWsTokens()
|
||||
parseBlock()
|
||||
} else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) {
|
||||
cc.skipWsTokens()
|
||||
cc.next() // consume '='
|
||||
val expr = parseExpression() ?: throw ScriptError(cc.current().pos, "Expected getter expression")
|
||||
(expr as? Statement) ?: statement(expr.pos) { s -> expr.execute(s) }
|
||||
} else {
|
||||
throw ScriptError(cc.current().pos, "Expected { or = after get()")
|
||||
}
|
||||
} else if (t.isId("set")) {
|
||||
cc.skipWsTokens()
|
||||
cc.next() // consume 'set'
|
||||
cc.requireToken(Token.Type.LPAREN)
|
||||
val setArg = cc.requireToken(Token.Type.ID, "Expected setter argument name")
|
||||
cc.requireToken(Token.Type.RPAREN)
|
||||
setter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
||||
cc.skipWsTokens()
|
||||
val body = parseBlock()
|
||||
statement(body.pos) { scope ->
|
||||
val value = scope.args.list.firstOrNull() ?: ObjNull
|
||||
scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument)
|
||||
body.execute(scope)
|
||||
}
|
||||
} else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) {
|
||||
cc.skipWsTokens()
|
||||
cc.next() // consume '='
|
||||
val expr = parseExpression() ?: throw ScriptError(cc.current().pos, "Expected setter expression")
|
||||
val st = (expr as? Statement) ?: statement(expr.pos) { s -> expr.execute(s) }
|
||||
statement(st.pos) { scope ->
|
||||
val value = scope.args.list.firstOrNull() ?: ObjNull
|
||||
scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument)
|
||||
st.execute(scope)
|
||||
}
|
||||
} else {
|
||||
throw ScriptError(cc.current().pos, "Expected { or = after set(...)")
|
||||
}
|
||||
} else break
|
||||
}
|
||||
if (getter != null || setter != null) {
|
||||
if (isMutable) {
|
||||
if (getter == null || setter == null) {
|
||||
throw ScriptError(start, "var property must have both get() and set()")
|
||||
}
|
||||
} else {
|
||||
if (setter != null)
|
||||
throw ScriptError(start, "val property cannot have a setter (name: $name)")
|
||||
if (getter == null)
|
||||
throw ScriptError(start, "val property with accessors must have a getter (name: $name)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return statement(start) { context ->
|
||||
// In true class bodies (not inside a function), store fields under a class-qualified key to support MI collisions
|
||||
@ -2709,23 +2791,32 @@ class Compiler(
|
||||
|
||||
if (isDelegate) {
|
||||
TODO()
|
||||
// println("initial expr = $initialExpression")
|
||||
// val initValue =
|
||||
// (initialExpression?.execute(context.copy(Arguments(ObjString(name)))) as? Statement)
|
||||
// ?.execute(context.copy(Arguments(ObjString(name))))
|
||||
// ?: context.raiseError("delegate initialization required")
|
||||
// println("delegate init: $initValue")
|
||||
// if (!initValue.isInstanceOf(ObjArray))
|
||||
// context.raiseIllegalArgument("delegate initialized must be an array")
|
||||
// val s = initValue.getAt(context, 1)
|
||||
// val setter = if (s == ObjNull) statement { raiseNotImplemented("setter is not provided") }
|
||||
// else (s as? Statement) ?: context.raiseClassCastError("setter must be a callable")
|
||||
// ObjDelegate(
|
||||
// (initValue.getAt(context, 0) as? Statement)
|
||||
// ?: context.raiseClassCastError("getter must be a callable"), setter
|
||||
// ).also {
|
||||
// context.addItem(name, isMutable, it, visibility, recordType = ObjRecord.Type.Field)
|
||||
// }
|
||||
} else if (getter != null || setter != null) {
|
||||
val declaringClassName = declaringClassNameCaptured!!
|
||||
val storageName = "$declaringClassName::$name"
|
||||
val prop = ObjProperty(name, getter, setter)
|
||||
|
||||
// If we are in class scope now (defining instance field), defer initialization to instance time
|
||||
val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance)
|
||||
if (isClassScope) {
|
||||
val cls = context.thisObj as ObjClass
|
||||
// Register the property
|
||||
cls.instanceInitializers += statement(start) { scp ->
|
||||
scp.addItem(
|
||||
storageName,
|
||||
isMutable,
|
||||
prop,
|
||||
visibility,
|
||||
recordType = ObjRecord.Type.Property
|
||||
)
|
||||
ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
} else {
|
||||
// We are in instance scope already: perform initialization immediately
|
||||
context.addItem(storageName, isMutable, prop, visibility, recordType = ObjRecord.Type.Property)
|
||||
prop
|
||||
}
|
||||
} else {
|
||||
if (declaringClassName != null && !isStatic) {
|
||||
val storageName = "$declaringClassName::$name"
|
||||
|
||||
@ -25,11 +25,12 @@ class CompilerContext(val tokens: List<Token>) {
|
||||
var loopLevel = 0
|
||||
|
||||
inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
|
||||
if (++loopLevel == 0) breakFound = false
|
||||
val oldBreakFound = breakFound
|
||||
breakFound = false
|
||||
val result = f()
|
||||
return Pair(breakFound, result).also {
|
||||
--loopLevel
|
||||
}
|
||||
val currentBreakFound = breakFound
|
||||
breakFound = oldBreakFound || currentBreakFound
|
||||
return Pair(currentBreakFound, result)
|
||||
}
|
||||
|
||||
var currentIndex = 0
|
||||
@ -108,7 +109,6 @@ class CompilerContext(val tokens: List<Token>) {
|
||||
val t = next()
|
||||
return if (t.type != tokenType) {
|
||||
if (!isOptional) {
|
||||
println("unexpected: $t (needed $tokenType)")
|
||||
throw ScriptError(t.pos, errorMessage)
|
||||
} else {
|
||||
previous()
|
||||
|
||||
@ -419,9 +419,12 @@ open class Scope(
|
||||
callScope.allocateSlotFor(name, rec)
|
||||
}
|
||||
}
|
||||
// Map to a slot for fast local access (if not already mapped)
|
||||
if (getSlotIndexOf(name) == null) {
|
||||
// Map to a slot for fast local access (ensure consistency)
|
||||
val idx = getSlotIndexOf(name)
|
||||
if (idx == null) {
|
||||
allocateSlotFor(name, rec)
|
||||
} else {
|
||||
slots[idx] = rec
|
||||
}
|
||||
return rec
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ open class Obj {
|
||||
|
||||
open suspend fun assign(scope: Scope, other: Obj): Obj? = null
|
||||
|
||||
open fun getValue(scope: Scope) = this
|
||||
open suspend fun getValue(scope: Scope) = this
|
||||
|
||||
/**
|
||||
* a += b
|
||||
|
||||
@ -32,19 +32,24 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
|
||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
// Direct (unmangled) lookup first
|
||||
instanceScope[name]?.let {
|
||||
val decl = it.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
instanceScope[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
// Allow unconditional access when accessing through `this` of the same instance
|
||||
if (scope.thisObj === this) return it
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(it.visibility, decl, caller))
|
||||
scope.raiseError(
|
||||
ObjAccessException(
|
||||
scope,
|
||||
"can't access field $name (declared in ${decl?.className ?: "?"})"
|
||||
if (scope.thisObj !== this) {
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(
|
||||
ObjAccessException(
|
||||
scope,
|
||||
"can't access field $name (declared in ${decl?.className ?: "?"})"
|
||||
)
|
||||
)
|
||||
)
|
||||
return it
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
val prop = rec.value as ObjProperty
|
||||
return rec.copy(value = prop.callGetter(scope, this))
|
||||
}
|
||||
return rec
|
||||
}
|
||||
// Try MI-mangled lookup along linearization (C3 MRO): ClassName::name
|
||||
val cls = objClass
|
||||
@ -65,15 +70,20 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
instanceScope.objects.containsKey("${cls.className}::$name") -> cls
|
||||
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
|
||||
}
|
||||
if (scope.thisObj === this) return rec
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, declaring, caller))
|
||||
scope.raiseError(
|
||||
ObjAccessException(
|
||||
scope,
|
||||
"can't access field $name (declared in ${declaring?.className ?: "?"})"
|
||||
if (scope.thisObj !== this) {
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, declaring, caller))
|
||||
scope.raiseError(
|
||||
ObjAccessException(
|
||||
scope,
|
||||
"can't access field $name (declared in ${declaring?.className ?: "?"})"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
val prop = rec.value as ObjProperty
|
||||
return rec.copy(value = prop.callGetter(scope, this))
|
||||
}
|
||||
return rec
|
||||
}
|
||||
// Fall back to methods/properties on class
|
||||
@ -84,9 +94,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
// Direct (unmangled) first
|
||||
instanceScope[name]?.let { f ->
|
||||
val decl = f.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
if (scope.thisObj === this) {
|
||||
// direct self-assignment allowed; enforce mutability below
|
||||
} else {
|
||||
if (scope.thisObj !== this) {
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(f.visibility, decl, caller))
|
||||
ObjIllegalAssignmentException(
|
||||
@ -94,6 +102,11 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
"can't assign to field $name (declared in ${decl?.className ?: "?"})"
|
||||
).raise()
|
||||
}
|
||||
if (f.type == ObjRecord.Type.Property) {
|
||||
val prop = f.value as ObjProperty
|
||||
prop.callSetter(scope, this, newValue)
|
||||
return
|
||||
}
|
||||
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
if (f.value.assign(scope, newValue) == null)
|
||||
f.value = newValue
|
||||
@ -123,6 +136,11 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
"can't assign to field $name (declared in ${declaring?.className ?: "?"})"
|
||||
).raise()
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
val prop = rec.value as ObjProperty
|
||||
prop.callSetter(scope, this, newValue)
|
||||
return
|
||||
}
|
||||
if (!rec.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
if (rec.value.assign(scope, newValue) == null)
|
||||
rec.value = newValue
|
||||
@ -211,10 +229,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
|
||||
// using objlist allow for some optimizations:
|
||||
val params = meta.params.map { readField(scope, it.name).value }
|
||||
println("serializing $objClass with params: $params")
|
||||
encoder.encodeAnyList(scope, params)
|
||||
val vars = serializingVars.values.map { it.value }
|
||||
println("encoding vars: $vars")
|
||||
if (vars.isNotEmpty<Obj>()) {
|
||||
encoder.encodeAnyList(scope, vars)
|
||||
}
|
||||
|
||||
@ -24,36 +24,32 @@ import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
class ObjInt(var value: Long, override val isConst: Boolean = false) : Obj(), Numeric {
|
||||
class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Numeric {
|
||||
override val longValue get() = value
|
||||
override val doubleValue get() = value.toDouble()
|
||||
override val toObjInt get() = this
|
||||
override val toObjReal = ObjReal(doubleValue)
|
||||
|
||||
override fun byValueCopy(): Obj = ObjInt(value)
|
||||
override fun byValueCopy(): Obj = this
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return value.hashCode()
|
||||
}
|
||||
|
||||
override suspend fun getAndIncrement(scope: Scope): Obj {
|
||||
ensureNotConst(scope)
|
||||
return ObjInt(value).also { value++ }
|
||||
return this
|
||||
}
|
||||
|
||||
override suspend fun getAndDecrement(scope: Scope): Obj {
|
||||
ensureNotConst(scope)
|
||||
return ObjInt(value).also { value-- }
|
||||
return this
|
||||
}
|
||||
|
||||
override suspend fun incrementAndGet(scope: Scope): Obj {
|
||||
ensureNotConst(scope)
|
||||
return ObjInt(++value)
|
||||
return ObjInt(value + 1)
|
||||
}
|
||||
|
||||
override suspend fun decrementAndGet(scope: Scope): Obj {
|
||||
ensureNotConst(scope)
|
||||
return ObjInt(--value)
|
||||
return ObjInt(value - 1)
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
@ -93,15 +89,9 @@ class ObjInt(var value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
else ObjReal(this.value.toDouble() % other.toDouble())
|
||||
|
||||
/**
|
||||
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
|
||||
* assignment
|
||||
* Numbers are now immutable, so we can't do in-place assignment.
|
||||
*/
|
||||
override suspend fun assign(scope: Scope, other: Obj): Obj? {
|
||||
return if (!isConst && other is ObjInt) {
|
||||
value = other.value
|
||||
this
|
||||
} else null
|
||||
}
|
||||
override suspend fun assign(scope: Scope, other: Obj): Obj? = null
|
||||
|
||||
override suspend fun toKotlin(scope: Scope): Any {
|
||||
return value
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
|
||||
/**
|
||||
* Property accessor storage. Per instructions, properties do NOT have
|
||||
* automatic backing fields. They are pure accessors.
|
||||
*/
|
||||
class ObjProperty(
|
||||
val name: String,
|
||||
val getter: Statement?,
|
||||
val setter: Statement?
|
||||
) : Obj() {
|
||||
|
||||
suspend fun callGetter(scope: Scope, instance: ObjInstance): Obj {
|
||||
val g = getter ?: scope.raiseError("property $name has no getter")
|
||||
// Execute getter in a child scope of the instance with 'this' properly set
|
||||
return g.execute(instance.instanceScope.createChildScope(newThisObj = instance))
|
||||
}
|
||||
|
||||
suspend fun callSetter(scope: Scope, instance: ObjInstance, value: Obj) {
|
||||
val s = setter ?: scope.raiseError("property $name has no setter")
|
||||
// Execute setter in a child scope of the instance with 'this' properly set and the value as an argument
|
||||
s.execute(instance.instanceScope.createChildScope(args = Arguments(value), newThisObj = instance))
|
||||
}
|
||||
|
||||
override fun toString(): String = "Property($name)"
|
||||
}
|
||||
@ -39,7 +39,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override fun byValueCopy(): Obj = ObjReal(value)
|
||||
override fun byValueCopy(): Obj = this
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other !is Numeric) return -2
|
||||
|
||||
@ -42,6 +42,7 @@ data class ObjRecord(
|
||||
@Suppress("unused")
|
||||
Class,
|
||||
Enum,
|
||||
Property,
|
||||
Other;
|
||||
|
||||
val isArgument get() = this == Argument
|
||||
|
||||
@ -375,22 +375,12 @@ class IncDecRef(
|
||||
if (!rec.isMutable) scope.raiseError("Cannot ${if (isIncrement) "increment" else "decrement"} immutable value")
|
||||
val v = rec.value
|
||||
val one = ObjInt.One
|
||||
return if (v.isConst) {
|
||||
// Mirror existing semantics in Compiler for const values
|
||||
val result = if (isIncrement) v.plus(scope, one) else v.minus(scope, one)
|
||||
// write back
|
||||
target.setAt(atPos, scope, result)
|
||||
// For post-inc: previous code returned NEW value; for pre-inc: returned ORIGINAL value
|
||||
if (isPost) result.asReadonly else v.asReadonly
|
||||
} else {
|
||||
val res = when {
|
||||
isIncrement && isPost -> v.getAndIncrement(scope)
|
||||
isIncrement && !isPost -> v.incrementAndGet(scope)
|
||||
!isIncrement && isPost -> v.getAndDecrement(scope)
|
||||
else -> v.decrementAndGet(scope)
|
||||
}
|
||||
res.asReadonly
|
||||
}
|
||||
// We now treat numbers as immutable and always perform write-back via setAt.
|
||||
// This avoids issues where literals are shared and mutated in-place.
|
||||
// For post-inc: return ORIGINAL value; for pre-inc: return NEW value.
|
||||
val result = if (isIncrement) v.plus(scope, one) else v.minus(scope, one)
|
||||
target.setAt(atPos, scope, result)
|
||||
return (if (isPost) v else result).asReadonly
|
||||
}
|
||||
}
|
||||
|
||||
@ -635,12 +625,16 @@ class FieldRef(
|
||||
val (k, v) = receiverKeyAndVersion(base)
|
||||
val rec = tRecord
|
||||
if (rec != null && tKey == k && tVer == v && tFrameId == scope.frameId) {
|
||||
// visibility/mutability checks
|
||||
if (!rec.isMutable) scope.raiseError(ObjIllegalAssignmentException(scope, "can't reassign val $name"))
|
||||
if (!rec.visibility.isPublic)
|
||||
scope.raiseError(ObjAccessException(scope, "can't access non-public field $name"))
|
||||
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
||||
return
|
||||
// If it is a property, we must go through writeField (slow path for now)
|
||||
// or handle it here.
|
||||
if (rec.type != ObjRecord.Type.Property) {
|
||||
// visibility/mutability checks
|
||||
if (!rec.isMutable) scope.raiseError(ObjIllegalAssignmentException(scope, "can't reassign val $name"))
|
||||
if (!rec.visibility.isPublic)
|
||||
scope.raiseError(ObjAccessException(scope, "can't access non-public field $name"))
|
||||
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fieldPic) {
|
||||
@ -1229,7 +1223,7 @@ class MethodCallRef(
|
||||
/**
|
||||
* Reference to a local/visible variable by name (Phase A: scope lookup).
|
||||
*/
|
||||
class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
||||
class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
override fun forEachVariable(block: (String) -> Unit) {
|
||||
block(name)
|
||||
}
|
||||
@ -1253,7 +1247,6 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
scope.pos = atPos
|
||||
// 1) Try fast slot/local
|
||||
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
||||
scope.getSlotIndexOf(name)?.let {
|
||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.localVarPicHit++
|
||||
@ -1472,7 +1465,7 @@ class BoundLocalVarRef(
|
||||
* It resolves the slot once per frame and never falls back to global/module lookup.
|
||||
*/
|
||||
class FastLocalVarRef(
|
||||
private val name: String,
|
||||
val name: String,
|
||||
private val atPos: Pos,
|
||||
) : ObjRef {
|
||||
override fun forEachVariable(block: (String) -> Unit) {
|
||||
@ -1779,10 +1772,16 @@ class AssignRef(
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val v = if (PerfFlags.RVAL_FASTPATH) value.evalValue(scope) else value.get(scope).value
|
||||
val rec = target.get(scope)
|
||||
if (!rec.isMutable) throw ScriptError(atPos, "cannot assign to immutable variable")
|
||||
if (rec.value.assign(scope, v) == null) {
|
||||
target.setAt(atPos, scope, v)
|
||||
// For properties, we should not call get() on target because it invokes the getter.
|
||||
// Instead, we call setAt directly.
|
||||
if (target is FieldRef || target is IndexRef || target is LocalVarRef || target is FastLocalVarRef || target is BoundLocalVarRef) {
|
||||
target.setAt(atPos, scope, v)
|
||||
} else {
|
||||
val rec = target.get(scope)
|
||||
if (!rec.isMutable) throw ScriptError(atPos, "cannot assign to immutable variable")
|
||||
if (rec.value.assign(scope, v) == null) {
|
||||
target.setAt(atPos, scope, v)
|
||||
}
|
||||
}
|
||||
return v.asReadonly
|
||||
}
|
||||
|
||||
@ -2318,29 +2318,33 @@ class ScriptTest {
|
||||
@Test
|
||||
fun doWhileValuesLabelTest() = runTest {
|
||||
withTimeout(5.seconds) {
|
||||
eval(
|
||||
"""
|
||||
var count = 0
|
||||
var count2 = 0
|
||||
var count3 = 0
|
||||
val result = outer@ do {
|
||||
count2++
|
||||
count = 0
|
||||
do {
|
||||
count++
|
||||
if( count < 10 || count2 < 5 ) {
|
||||
continue
|
||||
}
|
||||
if( count % 2 == 1 )
|
||||
break@outer "found "+count + "/" + count2
|
||||
} while(count < 14)
|
||||
count3++
|
||||
} while( count2 < 100 )
|
||||
else "no"
|
||||
assertEquals("found 11/5", result)
|
||||
assertEquals( 4, count3)
|
||||
""".trimIndent()
|
||||
)
|
||||
try {
|
||||
eval(
|
||||
"""
|
||||
var count = 0
|
||||
var count2 = 0
|
||||
var count3 = 0
|
||||
val result = outer@ do {
|
||||
count2++
|
||||
count = 0
|
||||
do {
|
||||
count++
|
||||
if( count < 10 || count2 < 5 ) {
|
||||
continue
|
||||
}
|
||||
if( count % 2 == 1 )
|
||||
break@outer "found "+count + "/" + count2
|
||||
} while(count < 14)
|
||||
count3++
|
||||
} while( count2 < 100 )
|
||||
else "no"
|
||||
assertEquals("found 11/5", result)
|
||||
assertEquals( 4, count3)
|
||||
""".trimIndent()
|
||||
)
|
||||
} catch (e: ExecutionError) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
66
lynglib/src/commonTest/kotlin/net/sergeych/lyng/PropsTest.kt
Normal file
66
lynglib/src/commonTest/kotlin/net/sergeych/lyng/PropsTest.kt
Normal file
@ -0,0 +1,66 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
|
||||
class PropsTest {
|
||||
|
||||
@Test
|
||||
fun propsProposal() = runTest {
|
||||
eval("""
|
||||
|
||||
class WithProps {
|
||||
|
||||
// readonly property without declared type:
|
||||
val readonlyProp
|
||||
get() {
|
||||
"readonly foo"
|
||||
}
|
||||
|
||||
val readonlyWithType: Int get() { 42 }
|
||||
|
||||
private var field = 0
|
||||
private var field2 = ""
|
||||
|
||||
// with type declaration
|
||||
var propName: Int
|
||||
get() {
|
||||
field * 10
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
}
|
||||
|
||||
// or without
|
||||
var propNameWithoutType
|
||||
get() {
|
||||
"/"+ field2 + "/"
|
||||
}
|
||||
set(value) {
|
||||
field2 = value
|
||||
}
|
||||
}
|
||||
|
||||
val w = WithProps()
|
||||
assertEquals("readonly foo", w.readonlyProp)
|
||||
assertEquals(42, w.readonlyWithType)
|
||||
|
||||
w.propNameWithoutType = "foo"
|
||||
assertEquals("/foo/", w.propNameWithoutType)
|
||||
|
||||
w.propName = 123
|
||||
assertEquals(1230, w.propName)
|
||||
|
||||
class Shorthand {
|
||||
private var _p = 0
|
||||
var p: Int
|
||||
get() = _p * 2
|
||||
set(v) = _p = v
|
||||
}
|
||||
val s = Shorthand()
|
||||
s.p = 21
|
||||
assertEquals(42, s.p)
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
@ -18,15 +18,15 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
actual object PerfDefaults {
|
||||
actual val LOCAL_SLOT_PIC: Boolean = true
|
||||
actual val EMIT_FAST_LOCAL_REFS: Boolean = true
|
||||
actual val LOCAL_SLOT_PIC: Boolean = false
|
||||
actual val EMIT_FAST_LOCAL_REFS: Boolean = false
|
||||
|
||||
actual val ARG_BUILDER: Boolean = true
|
||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
||||
actual val SCOPE_POOL: Boolean = true
|
||||
actual val ARG_BUILDER: Boolean = false
|
||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = false
|
||||
actual val SCOPE_POOL: Boolean = false
|
||||
|
||||
actual val FIELD_PIC: Boolean = true
|
||||
actual val METHOD_PIC: Boolean = true
|
||||
actual val FIELD_PIC: Boolean = false
|
||||
actual val METHOD_PIC: Boolean = false
|
||||
actual val FIELD_PIC_SIZE_4: Boolean = false
|
||||
actual val METHOD_PIC_SIZE_4: Boolean = false
|
||||
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false
|
||||
@ -35,8 +35,8 @@ actual object PerfDefaults {
|
||||
|
||||
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
||||
|
||||
actual val PRIMITIVE_FASTOPS: Boolean = true
|
||||
actual val RVAL_FASTPATH: Boolean = true
|
||||
actual val PRIMITIVE_FASTOPS: Boolean = false
|
||||
actual val RVAL_FASTPATH: Boolean = false
|
||||
|
||||
// Regex caching (JVM-first): enabled by default on JVM
|
||||
actual val REGEX_CACHE: Boolean = true
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user