Compare commits
No commits in common. "8f04b25fcb506002929d02d4124a729da3e4a2d7" and "827df9c8cd42cd54b3fd05e29db4efc9c87b067d" have entirely different histories.
8f04b25fcb
...
827df9c8cd
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
id("org.jetbrains.intellij") version "1.17.4"
|
id("org.jetbrains.intellij") version "1.17.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "net.sergeych.lyng"
|
group = "net.sergeych.lyng"
|
||||||
@ -52,7 +52,7 @@ dependencies {
|
|||||||
intellij {
|
intellij {
|
||||||
type.set("IC")
|
type.set("IC")
|
||||||
// Build against a modern baseline. Install range is controlled by since/until below.
|
// Build against a modern baseline. Install range is controlled by since/until below.
|
||||||
version.set("2024.1.6")
|
version.set("2024.3.1")
|
||||||
// We manage <idea-version> ourselves in plugin.xml to keep it open-ended (no upper cap)
|
// We manage <idea-version> ourselves in plugin.xml to keep it open-ended (no upper cap)
|
||||||
updateSinceUntilBuild.set(false)
|
updateSinceUntilBuild.set(false)
|
||||||
// Include only available bundled plugins for this IDE build
|
// Include only available bundled plugins for this IDE build
|
||||||
|
|||||||
@ -167,12 +167,8 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
.withIcon(AllIcons.Nodes.Field)
|
.withIcon(AllIcons.Nodes.Field)
|
||||||
.let { b -> if (!ci.typeText.isNullOrBlank()) b.withTypeText(ci.typeText, true) else b }
|
.let { b -> if (!ci.typeText.isNullOrBlank()) b.withTypeText(ci.typeText, true) else b }
|
||||||
}
|
}
|
||||||
if (ci.priority != 0.0) {
|
|
||||||
emit(PrioritizedLookupElement.withPriority(builder, ci.priority))
|
|
||||||
} else {
|
|
||||||
emit(builder)
|
emit(builder)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// In member context, ensure stdlib extension-like methods (e.g., String.re) are present
|
// In member context, ensure stdlib extension-like methods (e.g., String.re) are present
|
||||||
if (memberDotPos != null) {
|
if (memberDotPos != null) {
|
||||||
val existing = engineItems.map { it.name }.toMutableSet()
|
val existing = engineItems.map { it.name }.toMutableSet()
|
||||||
@ -405,7 +401,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
}
|
}
|
||||||
supplementPreferredBases(className)
|
supplementPreferredBases(className)
|
||||||
|
|
||||||
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>, groupPriority: Double) {
|
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>) {
|
||||||
val keys = map.keys.sortedBy { it.lowercase() }
|
val keys = map.keys.sortedBy { it.lowercase() }
|
||||||
for (name in keys) {
|
for (name in keys) {
|
||||||
val list = map[name] ?: continue
|
val list = map[name] ?: continue
|
||||||
@ -432,12 +428,8 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
.withTailText(tail, true)
|
.withTailText(tail, true)
|
||||||
.withTypeText(ret, true)
|
.withTypeText(ret, true)
|
||||||
.withInsertHandler(ParenInsertHandler)
|
.withInsertHandler(ParenInsertHandler)
|
||||||
if (groupPriority != 0.0) {
|
|
||||||
emit(PrioritizedLookupElement.withPriority(builder, groupPriority))
|
|
||||||
} else {
|
|
||||||
emit(builder)
|
emit(builder)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
is MiniMemberValDecl -> {
|
is MiniMemberValDecl -> {
|
||||||
val icon = if (rep.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field
|
val icon = if (rep.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field
|
||||||
// Prefer a field variant with known type if available
|
// Prefer a field variant with known type if available
|
||||||
@ -447,20 +439,16 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
val builder = LookupElementBuilder.create(name)
|
val builder = LookupElementBuilder.create(name)
|
||||||
.withIcon(icon)
|
.withIcon(icon)
|
||||||
.withTypeText(typeOf(chosen.type), true)
|
.withTypeText(typeOf(chosen.type), true)
|
||||||
if (groupPriority != 0.0) {
|
|
||||||
emit(PrioritizedLookupElement.withPriority(builder, groupPriority))
|
|
||||||
} else {
|
|
||||||
emit(builder)
|
emit(builder)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
is MiniInitDecl -> {}
|
is MiniInitDecl -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit what we have first
|
// Emit what we have first
|
||||||
emitGroup(directMap, 100.0)
|
emitGroup(directMap)
|
||||||
emitGroup(inheritedMap, 0.0)
|
emitGroup(inheritedMap)
|
||||||
|
|
||||||
// If suggestions are suspiciously sparse for known container classes,
|
// If suggestions are suspiciously sparse for known container classes,
|
||||||
// try to conservatively supplement using a curated list resolved via docs registry.
|
// try to conservatively supplement using a curated list resolved via docs registry.
|
||||||
@ -521,7 +509,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
.withInsertHandler(ParenInsertHandler)
|
.withInsertHandler(ParenInsertHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit(PrioritizedLookupElement.withPriority(builder, 50.0))
|
emit(builder)
|
||||||
already.add(name)
|
already.add(name)
|
||||||
} else {
|
} else {
|
||||||
// Synthetic fallback: method without detailed params/types to improve UX in absence of docs
|
// Synthetic fallback: method without detailed params/types to improve UX in absence of docs
|
||||||
@ -535,7 +523,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
.withTailText("()", true)
|
.withTailText("()", true)
|
||||||
.withInsertHandler(ParenInsertHandler)
|
.withInsertHandler(ParenInsertHandler)
|
||||||
}
|
}
|
||||||
emit(PrioritizedLookupElement.withPriority(builder, 50.0))
|
emit(builder)
|
||||||
already.add(name)
|
already.add(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -588,7 +576,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
.withInsertHandler(ParenInsertHandler)
|
.withInsertHandler(ParenInsertHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit(PrioritizedLookupElement.withPriority(builder, 50.0))
|
emit(builder)
|
||||||
already.add(name)
|
already.add(name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -597,7 +585,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
.withIcon(AllIcons.Nodes.Method)
|
.withIcon(AllIcons.Nodes.Method)
|
||||||
.withTailText("()", true)
|
.withTailText("()", true)
|
||||||
.withInsertHandler(ParenInsertHandler)
|
.withInsertHandler(ParenInsertHandler)
|
||||||
emit(PrioritizedLookupElement.withPriority(builder, 50.0))
|
emit(builder)
|
||||||
already.add(name)
|
already.add(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -461,39 +461,25 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
private fun ensureExternalDocsRegistered() { @Suppress("UNUSED_EXPRESSION") externalDocsLoaded }
|
private fun ensureExternalDocsRegistered() { @Suppress("UNUSED_EXPRESSION") externalDocsLoaded }
|
||||||
|
|
||||||
private fun tryLoadExternalDocs(): Boolean {
|
private fun tryLoadExternalDocs(): Boolean {
|
||||||
var anyLoaded = false
|
return try {
|
||||||
try {
|
|
||||||
// Try known registrars; ignore failures if module is absent
|
// Try known registrars; ignore failures if module is absent
|
||||||
val cls = Class.forName("net.sergeych.lyngio.docs.FsBuiltinDocs")
|
val cls = Class.forName("net.sergeych.lyngio.docs.FsBuiltinDocs")
|
||||||
val m = cls.getMethod("ensure")
|
val m = cls.getMethod("ensure")
|
||||||
m.invoke(null)
|
m.invoke(null)
|
||||||
log.info("[LYNG_DEBUG] QuickDoc: external docs loaded: net.sergeych.lyngio.docs.FsBuiltinDocs.ensure() OK")
|
log.info("[LYNG_DEBUG] QuickDoc: external docs loaded: net.sergeych.lyngio.docs.FsBuiltinDocs.ensure() OK")
|
||||||
anyLoaded = true
|
true
|
||||||
} catch (_: Throwable) {}
|
} catch (_: Throwable) {
|
||||||
|
|
||||||
try {
|
|
||||||
val cls = Class.forName("net.sergeych.lyngio.docs.ProcessBuiltinDocs")
|
|
||||||
val m = cls.getMethod("ensure")
|
|
||||||
m.invoke(null)
|
|
||||||
log.info("[LYNG_DEBUG] QuickDoc: external docs loaded: net.sergeych.lyngio.docs.ProcessBuiltinDocs.ensure() OK")
|
|
||||||
anyLoaded = true
|
|
||||||
} catch (_: Throwable) {}
|
|
||||||
|
|
||||||
if (!anyLoaded) {
|
|
||||||
// Seed a minimal plugin-local fallback so Path docs still work without lyngio
|
// Seed a minimal plugin-local fallback so Path docs still work without lyngio
|
||||||
val seeded = try {
|
val seeded = try {
|
||||||
FsDocsFallback.ensureOnce()
|
FsDocsFallback.ensureOnce()
|
||||||
ProcessDocsFallback.ensureOnce()
|
|
||||||
true
|
|
||||||
} catch (_: Throwable) { false }
|
} catch (_: Throwable) { false }
|
||||||
if (seeded) {
|
if (seeded) {
|
||||||
log.info("[LYNG_DEBUG] QuickDoc: external docs NOT found; seeded plugin fallbacks")
|
log.info("[LYNG_DEBUG] QuickDoc: external docs NOT found; seeded plugin fallback for lyng.io.fs")
|
||||||
} else {
|
} else {
|
||||||
log.info("[LYNG_DEBUG] QuickDoc: external docs NOT found (lyngio absent on classpath)")
|
log.info("[LYNG_DEBUG] QuickDoc: external docs NOT found (lyngio absent on classpath)")
|
||||||
}
|
}
|
||||||
return seeded
|
seeded
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCustomDocumentationElement(
|
override fun getCustomDocumentationElement(
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Minimal fallback docs seeding for `lyng.io.process` used only inside the IDEA plugin
|
|
||||||
* when external docs module (lyngio) is not present on the classpath.
|
|
||||||
*/
|
|
||||||
package net.sergeych.lyng.idea.docs
|
|
||||||
|
|
||||||
import net.sergeych.lyng.miniast.BuiltinDocRegistry
|
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
|
||||||
import net.sergeych.lyng.miniast.type
|
|
||||||
|
|
||||||
internal object ProcessDocsFallback {
|
|
||||||
@Volatile
|
|
||||||
private var seeded = false
|
|
||||||
|
|
||||||
fun ensureOnce(): Boolean {
|
|
||||||
if (seeded) return true
|
|
||||||
synchronized(this) {
|
|
||||||
if (seeded) return true
|
|
||||||
BuiltinDocRegistry.module("lyng.io.process") {
|
|
||||||
classDoc(name = "Process", doc = "Process execution and control.") {
|
|
||||||
method(
|
|
||||||
name = "execute",
|
|
||||||
doc = "Execute a process with arguments.",
|
|
||||||
params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))),
|
|
||||||
returns = type("RunningProcess"),
|
|
||||||
isStatic = true
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "shell",
|
|
||||||
doc = "Execute a command via system shell.",
|
|
||||||
params = listOf(ParamDoc("command", type("lyng.String"))),
|
|
||||||
returns = type("RunningProcess"),
|
|
||||||
isStatic = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
classDoc(name = "RunningProcess", doc = "Handle to a running process.") {
|
|
||||||
method(name = "stdout", doc = "Get standard output stream as a Flow of lines.", returns = type("lyng.Flow"))
|
|
||||||
method(name = "stderr", doc = "Get standard error stream as a Flow of lines.", returns = type("lyng.Flow"))
|
|
||||||
method(name = "waitFor", doc = "Wait for the process to exit.", returns = type("lyng.Int"))
|
|
||||||
method(name = "signal", doc = "Send a signal to the process.", params = listOf(ParamDoc("signal", type("lyng.String"))))
|
|
||||||
method(name = "destroy", doc = "Forcefully terminate the process.")
|
|
||||||
}
|
|
||||||
|
|
||||||
valDoc(name = "Process", doc = "Process execution and control.", type = type("Process"))
|
|
||||||
valDoc(name = "RunningProcess", doc = "Handle to a running process.", type = type("RunningProcess"))
|
|
||||||
}
|
|
||||||
seeded = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -54,8 +54,6 @@ class LyngSpellcheckingStrategy : SpellcheckingStrategy() {
|
|||||||
} catch (_: Throwable) { false }
|
} catch (_: Throwable) { false }
|
||||||
|
|
||||||
override fun getTokenizer(element: PsiElement): Tokenizer<*> {
|
override fun getTokenizer(element: PsiElement): Tokenizer<*> {
|
||||||
if (element is com.intellij.psi.PsiFile) return EMPTY_TOKENIZER
|
|
||||||
|
|
||||||
val hasGrazie = grazieInstalled()
|
val hasGrazie = grazieInstalled()
|
||||||
val hasGrazieApi = grazieApiAvailable()
|
val hasGrazieApi = grazieApiAvailable()
|
||||||
val settings = LyngFormatterSettings.getInstance(element.project)
|
val settings = LyngFormatterSettings.getInstance(element.project)
|
||||||
@ -65,40 +63,27 @@ class LyngSpellcheckingStrategy : SpellcheckingStrategy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val file = element.containingFile ?: return EMPTY_TOKENIZER
|
val file = element.containingFile ?: return EMPTY_TOKENIZER
|
||||||
val et = element.node?.elementType
|
val index = LyngSpellIndex.getUpToDate(file) ?: run {
|
||||||
val index = LyngSpellIndex.getUpToDate(file)
|
// Suspend legacy spellcheck until MiniAst-based index is ready
|
||||||
|
return EMPTY_TOKENIZER
|
||||||
|
}
|
||||||
|
val elRange = element.textRange ?: return EMPTY_TOKENIZER
|
||||||
|
|
||||||
|
fun overlaps(list: List<TextRange>) = list.any { it.intersects(elRange) }
|
||||||
|
|
||||||
// Decide responsibility per settings
|
// Decide responsibility per settings
|
||||||
// If Grazie is present but its public API is not available (IC-243), do NOT delegate to it.
|
// If Grazie is present but its public API is not available (IC-243), do NOT delegate to it.
|
||||||
val preferGrazie = hasGrazie && hasGrazieApi && settings.preferGrazieForCommentsAndLiterals
|
val preferGrazie = hasGrazie && hasGrazieApi && settings.preferGrazieForCommentsAndLiterals
|
||||||
val grazieIds = hasGrazie && hasGrazieApi && settings.grazieChecksIdentifiers
|
val grazieIds = hasGrazie && hasGrazieApi && settings.grazieChecksIdentifiers
|
||||||
|
|
||||||
if (index == null) {
|
|
||||||
// Index not ready: fall back to Lexer-based token types.
|
|
||||||
// Identifiers are safe because LyngLexer separates keywords from identifiers.
|
|
||||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.IDENTIFIER) {
|
|
||||||
return if (grazieIds) EMPTY_TOKENIZER else IDENTIFIER_TOKENIZER
|
|
||||||
}
|
|
||||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.LINE_COMMENT || et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.BLOCK_COMMENT) {
|
|
||||||
return if (preferGrazie) EMPTY_TOKENIZER else COMMENT_TEXT_TOKENIZER
|
|
||||||
}
|
|
||||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.STRING && settings.spellCheckStringLiterals) {
|
|
||||||
return if (preferGrazie) EMPTY_TOKENIZER else STRING_WITH_PRINTF_EXCLUDES
|
|
||||||
}
|
|
||||||
return EMPTY_TOKENIZER
|
|
||||||
}
|
|
||||||
|
|
||||||
val elRange = element.textRange ?: return EMPTY_TOKENIZER
|
|
||||||
fun overlaps(list: List<TextRange>) = list.any { it.intersects(elRange) }
|
|
||||||
|
|
||||||
// Identifiers: only if range is within identifiers index and not delegated to Grazie
|
// Identifiers: only if range is within identifiers index and not delegated to Grazie
|
||||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.IDENTIFIER && overlaps(index.identifiers) && !grazieIds) return IDENTIFIER_TOKENIZER
|
if (overlaps(index.identifiers) && !grazieIds) return IDENTIFIER_TOKENIZER
|
||||||
|
|
||||||
// Comments: only if not delegated to Grazie and overlapping indexed comments
|
// Comments: only if not delegated to Grazie and overlapping indexed comments
|
||||||
if ((et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.LINE_COMMENT || et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.BLOCK_COMMENT) && overlaps(index.comments) && !preferGrazie) return COMMENT_TEXT_TOKENIZER
|
if (!preferGrazie && overlaps(index.comments)) return COMMENT_TEXT_TOKENIZER
|
||||||
|
|
||||||
// Strings: only if not delegated to Grazie, literals checking enabled, and overlapping indexed strings
|
// Strings: only if not delegated to Grazie, literals checking enabled, and overlapping indexed strings
|
||||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.STRING && settings.spellCheckStringLiterals && overlaps(index.strings) && !preferGrazie) return STRING_WITH_PRINTF_EXCLUDES
|
if (!preferGrazie && settings.spellCheckStringLiterals && overlaps(index.strings)) return STRING_WITH_PRINTF_EXCLUDES
|
||||||
|
|
||||||
return EMPTY_TOKENIZER
|
return EMPTY_TOKENIZER
|
||||||
}
|
}
|
||||||
@ -108,7 +93,7 @@ class LyngSpellcheckingStrategy : SpellcheckingStrategy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private object IDENTIFIER_TOKENIZER : Tokenizer<PsiElement>() {
|
private object IDENTIFIER_TOKENIZER : Tokenizer<PsiElement>() {
|
||||||
private val splitter = com.intellij.spellchecker.inspections.IdentifierSplitter.getInstance()
|
private val splitter = PlainTextSplitter.getInstance()
|
||||||
override fun tokenize(element: PsiElement, consumer: TokenConsumer) {
|
override fun tokenize(element: PsiElement, consumer: TokenConsumer) {
|
||||||
val text = element.text
|
val text = element.text
|
||||||
if (text.isNullOrEmpty()) return
|
if (text.isNullOrEmpty()) return
|
||||||
|
|||||||
@ -1,20 +1,3 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ensure external/bundled docs are registered in BuiltinDocRegistry
|
* Ensure external/bundled docs are registered in BuiltinDocRegistry
|
||||||
* so completion/quickdoc can resolve things like lyng.io.fs.Path.
|
* so completion/quickdoc can resolve things like lyng.io.fs.Path.
|
||||||
@ -23,7 +6,6 @@ package net.sergeych.lyng.idea.util
|
|||||||
|
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import net.sergeych.lyng.idea.docs.FsDocsFallback
|
import net.sergeych.lyng.idea.docs.FsDocsFallback
|
||||||
import net.sergeych.lyng.idea.docs.ProcessDocsFallback
|
|
||||||
|
|
||||||
object DocsBootstrap {
|
object DocsBootstrap {
|
||||||
private val log = Logger.getInstance(DocsBootstrap::class.java)
|
private val log = Logger.getInstance(DocsBootstrap::class.java)
|
||||||
@ -38,32 +20,20 @@ object DocsBootstrap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryLoadExternal(): Boolean {
|
private fun tryLoadExternal(): Boolean = try {
|
||||||
var anyLoaded = false
|
|
||||||
try {
|
|
||||||
val cls = Class.forName("net.sergeych.lyngio.docs.FsBuiltinDocs")
|
val cls = Class.forName("net.sergeych.lyngio.docs.FsBuiltinDocs")
|
||||||
val m = cls.getMethod("ensure")
|
val m = cls.getMethod("ensure")
|
||||||
m.invoke(null)
|
m.invoke(null)
|
||||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs loaded: net.sergeych.lyngio.docs.FsBuiltinDocs.ensure() OK")
|
log.info("[LYNG_DEBUG] DocsBootstrap: external docs loaded: net.sergeych.lyngio.docs.FsBuiltinDocs.ensure() OK")
|
||||||
anyLoaded = true
|
true
|
||||||
} catch (_: Throwable) {}
|
} catch (_: Throwable) {
|
||||||
|
false
|
||||||
try {
|
|
||||||
val cls = Class.forName("net.sergeych.lyngio.docs.ProcessBuiltinDocs")
|
|
||||||
val m = cls.getMethod("ensure")
|
|
||||||
m.invoke(null)
|
|
||||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs loaded: net.sergeych.lyngio.docs.ProcessBuiltinDocs.ensure() OK")
|
|
||||||
anyLoaded = true
|
|
||||||
} catch (_: Throwable) {}
|
|
||||||
return anyLoaded
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun trySeedFallback(): Boolean = try {
|
private fun trySeedFallback(): Boolean = try {
|
||||||
val seededFs = FsDocsFallback.ensureOnce()
|
val seeded = FsDocsFallback.ensureOnce()
|
||||||
val seededProcess = ProcessDocsFallback.ensureOnce()
|
|
||||||
val seeded = seededFs || seededProcess
|
|
||||||
if (seeded) {
|
if (seeded) {
|
||||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs not found; seeded plugin fallback for lyng.io.fs/process")
|
log.info("[LYNG_DEBUG] DocsBootstrap: external docs not found; seeded plugin fallback for lyng.io.fs")
|
||||||
} else {
|
} else {
|
||||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs not found; no fallback seeded")
|
log.info("[LYNG_DEBUG] DocsBootstrap: external docs not found; no fallback seeded")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,8 +16,8 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<idea-plugin>
|
<idea-plugin>
|
||||||
<!-- Open-ended compatibility: 2024.1+ (build 241 and newer) -->
|
<!-- Open-ended compatibility: 2024.3+ (build 243 and newer) -->
|
||||||
<idea-version since-build="241"/>
|
<idea-version since-build="243"/>
|
||||||
<id>net.sergeych.lyng.idea</id>
|
<id>net.sergeych.lyng.idea</id>
|
||||||
<name>Lyng</name>
|
<name>Lyng</name>
|
||||||
<vendor email="real.sergeych@gmail.com">Sergey Chernov</vendor>
|
<vendor email="real.sergeych@gmail.com">Sergey Chernov</vendor>
|
||||||
|
|||||||
@ -1,20 +1,3 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.idea.completion
|
package net.sergeych.lyng.idea.completion
|
||||||
|
|
||||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||||
@ -135,41 +118,4 @@ class LyngCompletionMemberTest : BasePlatformTestCase() {
|
|||||||
// Heuristic: we expect more than a couple of items (not just size/toList)
|
// Heuristic: we expect more than a couple of items (not just size/toList)
|
||||||
assertTrue("Too few member suggestions after list literal: $items", items.size >= 3)
|
assertTrue("Too few member suggestions after list literal: $items", items.size >= 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test_ProcessModule_Completion() {
|
|
||||||
val code = """
|
|
||||||
import lyng.io.process
|
|
||||||
Process.<caret>
|
|
||||||
""".trimIndent()
|
|
||||||
val imported = listOf("lyng.io.process")
|
|
||||||
ensureDocs(imported)
|
|
||||||
|
|
||||||
val items = complete(code)
|
|
||||||
assertTrue("Should contain 'execute'", items.contains("execute"))
|
|
||||||
assertTrue("Should contain 'shell'", items.contains("shell"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun test_RunningProcess_Completion() {
|
|
||||||
val code = """
|
|
||||||
import lyng.io.process
|
|
||||||
val p = Process.shell("ls")
|
|
||||||
p.<caret>
|
|
||||||
""".trimIndent()
|
|
||||||
val imported = listOf("lyng.io.process")
|
|
||||||
ensureDocs(imported)
|
|
||||||
|
|
||||||
val items = complete(code)
|
|
||||||
assertTrue("Should contain 'stdout'", items.contains("stdout"))
|
|
||||||
assertTrue("Should contain 'waitFor'", items.contains("waitFor"))
|
|
||||||
assertTrue("Should contain 'signal'", items.contains("signal"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun test_RegistryDirect() {
|
|
||||||
DocsBootstrap.ensure()
|
|
||||||
val docs = BuiltinDocRegistry.docsForModule("lyng.io.process")
|
|
||||||
assertTrue("Docs for lyng.io.process should not be empty", docs.isNotEmpty())
|
|
||||||
val processClass = docs.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == "Process" }
|
|
||||||
assertNotNull("Should contain Process class", processClass)
|
|
||||||
assertTrue("Process should have members", processClass!!.members.isNotEmpty())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,6 @@ group = "net.sergeych"
|
|||||||
version = "0.0.1-SNAPSHOT"
|
version = "0.0.1-SNAPSHOT"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(17)
|
|
||||||
jvm()
|
jvm()
|
||||||
androidTarget {
|
androidTarget {
|
||||||
publishLibraryVariants("release")
|
publishLibraryVariants("release")
|
||||||
|
|||||||
@ -39,176 +39,45 @@ object FsBuiltinDocs {
|
|||||||
name = "Path",
|
name = "Path",
|
||||||
doc = "Filesystem path class. Construct with a string: `Path(\"/tmp\")`."
|
doc = "Filesystem path class. Construct with a string: `Path(\"/tmp\")`."
|
||||||
) {
|
) {
|
||||||
method(
|
// Common instance methods (subset sufficient for Quick Docs)
|
||||||
name = "name",
|
|
||||||
doc = "Base name of the path (last segment).",
|
|
||||||
returns = type("lyng.String")
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "parent",
|
|
||||||
doc = "Parent directory as a Path or null if none.",
|
|
||||||
returns = type("Path", nullable = true)
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "segments",
|
|
||||||
doc = "List of path segments.",
|
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String")))
|
|
||||||
)
|
|
||||||
method(
|
method(
|
||||||
name = "exists",
|
name = "exists",
|
||||||
doc = "Check whether this path exists on the filesystem.",
|
doc = "Whether the path exists on the filesystem.",
|
||||||
returns = type("lyng.Bool")
|
returns = type("lyng.Bool")
|
||||||
)
|
)
|
||||||
method(
|
method(
|
||||||
name = "isFile",
|
name = "isFile",
|
||||||
doc = "True if this path is a regular file (based on cached metadata).",
|
doc = "Whether the path exists and is a file.",
|
||||||
returns = type("lyng.Bool")
|
returns = type("lyng.Bool")
|
||||||
)
|
)
|
||||||
method(
|
method(
|
||||||
name = "isDirectory",
|
name = "isDir",
|
||||||
doc = "True if this path is a directory (based on cached metadata).",
|
doc = "Whether the path exists and is a directory.",
|
||||||
returns = type("lyng.Bool")
|
returns = type("lyng.Bool")
|
||||||
)
|
)
|
||||||
method(
|
|
||||||
name = "size",
|
|
||||||
doc = "File size in bytes, or null when unavailable.",
|
|
||||||
returns = type("lyng.Int", nullable = true)
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "createdAt",
|
|
||||||
doc = "Creation time as `Instant`, or null when unavailable.",
|
|
||||||
returns = type("lyng.Instant", nullable = true)
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "createdAtMillis",
|
|
||||||
doc = "Creation time in milliseconds since epoch, or null when unavailable.",
|
|
||||||
returns = type("lyng.Int", nullable = true)
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "modifiedAt",
|
|
||||||
doc = "Last modification time as `Instant`, or null when unavailable.",
|
|
||||||
returns = type("lyng.Instant", nullable = true)
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "modifiedAtMillis",
|
|
||||||
doc = "Last modification time in milliseconds since epoch, or null when unavailable.",
|
|
||||||
returns = type("lyng.Int", nullable = true)
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "list",
|
|
||||||
doc = "List directory entries as `Path` objects.",
|
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "readBytes",
|
|
||||||
doc = "Read the entire file into a binary buffer.",
|
|
||||||
returns = type("lyng.Buffer")
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "writeBytes",
|
|
||||||
doc = "Write a binary buffer to the file, replacing content.",
|
|
||||||
params = listOf(ParamDoc("bytes", type("lyng.Buffer")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "appendBytes",
|
|
||||||
doc = "Append a binary buffer to the end of the file.",
|
|
||||||
params = listOf(ParamDoc("bytes", type("lyng.Buffer")))
|
|
||||||
)
|
|
||||||
method(
|
method(
|
||||||
name = "readUtf8",
|
name = "readUtf8",
|
||||||
doc = "Read the entire file as a UTF-8 string.",
|
doc = "Read the entire file as UTF-8 string.",
|
||||||
returns = type("lyng.String")
|
returns = type("lyng.String")
|
||||||
)
|
)
|
||||||
method(
|
method(
|
||||||
name = "writeUtf8",
|
name = "writeUtf8",
|
||||||
doc = "Write a UTF-8 string to the file, replacing content.",
|
doc = "Write UTF-8 string to the file (overwrite).",
|
||||||
params = listOf(ParamDoc("text", type("lyng.String")))
|
params = listOf(ParamDoc("text", type("lyng.String")))
|
||||||
)
|
)
|
||||||
method(
|
method(
|
||||||
name = "appendUtf8",
|
name = "bytes",
|
||||||
doc = "Append UTF-8 text to the end of the file.",
|
doc = "Iterate file content as `Buffer` chunks.",
|
||||||
params = listOf(ParamDoc("text", type("lyng.String")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "metadata",
|
|
||||||
doc = "Fetch cached metadata as a map of fields: `isFile`, `isDirectory`, `size`, `createdAtMillis`, `modifiedAtMillis`, `isSymlink`.",
|
|
||||||
returns = TypeGenericDoc(type("lyng.Map"), listOf(type("lyng.String"), type("lyng.Any")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "mkdirs",
|
|
||||||
doc = "Create directories (like `mkdir -p`). If `mustCreate` is true and the path already exists, the call fails.",
|
|
||||||
params = listOf(ParamDoc("mustCreate", type("lyng.Bool")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "move",
|
|
||||||
doc = "Move this path to a new location. `to` may be a `Path` or `String`.",
|
|
||||||
params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "delete",
|
|
||||||
doc = "Delete this path. `recursively=true` removes directories with their contents.",
|
|
||||||
params = listOf(ParamDoc("mustExist", type("lyng.Bool")), ParamDoc("recursively", type("lyng.Bool")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "copy",
|
|
||||||
doc = "Copy this path to a new location. `to` may be a `Path` or `String`.",
|
|
||||||
params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "glob",
|
|
||||||
doc = "List entries matching a glob pattern (no recursion).",
|
|
||||||
params = listOf(ParamDoc("pattern", type("lyng.String"))),
|
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "readChunks",
|
|
||||||
doc = "Read file in fixed-size chunks as an iterator of `Buffer`.",
|
|
||||||
params = listOf(ParamDoc("size", type("lyng.Int"))),
|
params = listOf(ParamDoc("size", type("lyng.Int"))),
|
||||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Buffer")))
|
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Buffer")))
|
||||||
)
|
)
|
||||||
method(
|
|
||||||
name = "readUtf8Chunks",
|
|
||||||
doc = "Read UTF-8 text in fixed-size chunks as an iterator of `String`.",
|
|
||||||
params = listOf(ParamDoc("size", type("lyng.Int"))),
|
|
||||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String")))
|
|
||||||
)
|
|
||||||
method(
|
method(
|
||||||
name = "lines",
|
name = "lines",
|
||||||
doc = "Iterate lines of the file as `String` values.",
|
doc = "Iterate file as lines of text.",
|
||||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String")))
|
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String")))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
classDoc(
|
|
||||||
name = "BytesIterator",
|
|
||||||
doc = "Iterator over binary chunks."
|
|
||||||
) {
|
|
||||||
method("iterator", "Return this iterator instance.", returns = type("BytesIterator"))
|
|
||||||
method("hasNext", "Whether there is another chunk available.", returns = type("lyng.Bool"))
|
|
||||||
method("next", "Return the next chunk as a `Buffer`.", returns = type("lyng.Buffer"))
|
|
||||||
method("cancelIteration", "Stop the iteration early.")
|
|
||||||
}
|
|
||||||
|
|
||||||
classDoc(
|
|
||||||
name = "StringChunksIterator",
|
|
||||||
doc = "Iterator over UTF-8 text chunks."
|
|
||||||
) {
|
|
||||||
method("iterator", "Return this iterator instance.", returns = type("StringChunksIterator"))
|
|
||||||
method("hasNext", "Whether there is another chunk available.", returns = type("lyng.Bool"))
|
|
||||||
method("next", "Return the next UTF-8 chunk as a `String`.", returns = type("lyng.String"))
|
|
||||||
method("cancelIteration", "Stop the iteration early.")
|
|
||||||
}
|
|
||||||
|
|
||||||
classDoc(
|
|
||||||
name = "LinesIterator",
|
|
||||||
doc = "Iterator that yields lines of text."
|
|
||||||
) {
|
|
||||||
method("iterator", "Return this iterator instance.", returns = type("LinesIterator"))
|
|
||||||
method("hasNext", "Whether another line is available.", returns = type("lyng.Bool"))
|
|
||||||
method("next", "Return the next line as `String`.", returns = type("lyng.String"))
|
|
||||||
method("cancelIteration", "Stop the iteration early.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top-level exported constants
|
// Top-level exported constants
|
||||||
valDoc(
|
valDoc(
|
||||||
name = "Path",
|
name = "Path",
|
||||||
|
|||||||
@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyngio.docs
|
|
||||||
|
|
||||||
import net.sergeych.lyng.miniast.BuiltinDocRegistry
|
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
|
||||||
import net.sergeych.lyng.miniast.type
|
|
||||||
|
|
||||||
object ProcessBuiltinDocs {
|
|
||||||
private var registered = false
|
|
||||||
|
|
||||||
fun ensure() {
|
|
||||||
if (registered) return
|
|
||||||
BuiltinDocRegistry.module("lyng.io.process") {
|
|
||||||
classDoc(
|
|
||||||
name = "Process",
|
|
||||||
doc = "Process execution and control."
|
|
||||||
) {
|
|
||||||
method(
|
|
||||||
name = "execute",
|
|
||||||
doc = "Execute a process with arguments.",
|
|
||||||
params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))),
|
|
||||||
returns = type("RunningProcess"),
|
|
||||||
isStatic = true
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "shell",
|
|
||||||
doc = "Execute a command via system shell.",
|
|
||||||
params = listOf(ParamDoc("command", type("lyng.String"))),
|
|
||||||
returns = type("RunningProcess"),
|
|
||||||
isStatic = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
classDoc(
|
|
||||||
name = "Platform",
|
|
||||||
doc = "Platform information."
|
|
||||||
) {
|
|
||||||
method(
|
|
||||||
name = "details",
|
|
||||||
doc = "Get platform core details.",
|
|
||||||
returns = type("lyng.Map"),
|
|
||||||
isStatic = true
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "isSupported",
|
|
||||||
doc = "Check if processes are supported on this platform.",
|
|
||||||
returns = type("lyng.Bool"),
|
|
||||||
isStatic = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
classDoc(
|
|
||||||
name = "RunningProcess",
|
|
||||||
doc = "Handle to a running process."
|
|
||||||
) {
|
|
||||||
method(
|
|
||||||
name = "stdout",
|
|
||||||
doc = "Get standard output stream as a Flow of lines.",
|
|
||||||
returns = type("lyng.Flow")
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "stderr",
|
|
||||||
doc = "Get standard error stream as a Flow of lines.",
|
|
||||||
returns = type("lyng.Flow")
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "signal",
|
|
||||||
doc = "Send a signal to the process (e.g. 'SIGINT', 'SIGTERM', 'SIGKILL').",
|
|
||||||
params = listOf(ParamDoc("signal", type("lyng.String")))
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "waitFor",
|
|
||||||
doc = "Wait for the process to exit and return its exit code.",
|
|
||||||
returns = type("lyng.Int")
|
|
||||||
)
|
|
||||||
method(
|
|
||||||
name = "destroy",
|
|
||||||
doc = "Forcefully terminate the process."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top-level exported constants
|
|
||||||
valDoc(
|
|
||||||
name = "Process",
|
|
||||||
doc = "Process execution and control.",
|
|
||||||
type = type("Process")
|
|
||||||
)
|
|
||||||
valDoc(
|
|
||||||
name = "Platform",
|
|
||||||
doc = "Platform information.",
|
|
||||||
type = type("Platform")
|
|
||||||
)
|
|
||||||
valDoc(
|
|
||||||
name = "RunningProcess",
|
|
||||||
doc = "Handle to a running process.",
|
|
||||||
type = type("RunningProcess")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
registered = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -46,7 +46,6 @@ buildkonfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(17)
|
|
||||||
jvm()
|
jvm()
|
||||||
androidTarget {
|
androidTarget {
|
||||||
publishLibraryVariants("release")
|
publishLibraryVariants("release")
|
||||||
|
|||||||
@ -51,32 +51,6 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
defaultVisibility: Visibility = Visibility.Public,
|
defaultVisibility: Visibility = Visibility.Public,
|
||||||
declaringClass: net.sergeych.lyng.obj.ObjClass? = scope.currentClassCtx
|
declaringClass: net.sergeych.lyng.obj.ObjClass? = scope.currentClassCtx
|
||||||
) {
|
) {
|
||||||
// Fast path for simple positional-only calls with no ellipsis and no defaults
|
|
||||||
if (arguments.named.isEmpty() && !arguments.tailBlockMode) {
|
|
||||||
var hasComplex = false
|
|
||||||
for (p in params) {
|
|
||||||
if (p.isEllipsis || p.defaultValue != null) {
|
|
||||||
hasComplex = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasComplex) {
|
|
||||||
if (arguments.list.size != params.size)
|
|
||||||
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
|
||||||
|
|
||||||
for (i in params.indices) {
|
|
||||||
val a = params[i]
|
|
||||||
val value = arguments.list[i]
|
|
||||||
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable,
|
|
||||||
value.byValueCopy(),
|
|
||||||
a.visibility ?: defaultVisibility,
|
|
||||||
recordType = ObjRecord.Type.Argument,
|
|
||||||
declaringClass = declaringClass)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun assign(a: Item, value: Obj) {
|
fun assign(a: Item, value: Obj) {
|
||||||
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable,
|
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable,
|
||||||
value.byValueCopy(),
|
value.byValueCopy(),
|
||||||
|
|||||||
@ -169,9 +169,9 @@ class Compiler(
|
|||||||
miniSink?.onScriptStart(start)
|
miniSink?.onScriptStart(start)
|
||||||
do {
|
do {
|
||||||
val t = cc.current()
|
val t = cc.current()
|
||||||
if (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
|
if (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINLGE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
|
||||||
when (t.type) {
|
when (t.type) {
|
||||||
Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> pushPendingDocToken(t)
|
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> pushPendingDocToken(t)
|
||||||
Token.Type.NEWLINE -> {
|
Token.Type.NEWLINE -> {
|
||||||
// A standalone newline not immediately following a comment resets doc buffer
|
// A standalone newline not immediately following a comment resets doc buffer
|
||||||
if (!prevWasComment) clearPendingDoc() else prevWasComment = false
|
if (!prevWasComment) clearPendingDoc() else prevWasComment = false
|
||||||
@ -316,7 +316,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.LABEL -> continue
|
Token.Type.LABEL -> continue
|
||||||
Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
|
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
|
||||||
|
|
||||||
Token.Type.NEWLINE -> continue
|
Token.Type.NEWLINE -> continue
|
||||||
|
|
||||||
@ -407,7 +407,7 @@ class Compiler(
|
|||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
val startPos = t.pos
|
val startPos = t.pos
|
||||||
when (t.type) {
|
when (t.type) {
|
||||||
// Token.Type.NEWLINE, Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT-> {
|
// Token.Type.NEWLINE, Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT-> {
|
||||||
// continue
|
// continue
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@ -606,7 +606,7 @@ class Compiler(
|
|||||||
// to skip in parseExpression:
|
// to skip in parseExpression:
|
||||||
val current = cc.current()
|
val current = cc.current()
|
||||||
val right =
|
val right =
|
||||||
if (current.type == Token.Type.NEWLINE || current.type == Token.Type.SINGLE_LINE_COMMENT)
|
if (current.type == Token.Type.NEWLINE || current.type == Token.Type.SINLGE_LINE_COMMENT)
|
||||||
null
|
null
|
||||||
else
|
else
|
||||||
parseExpression()
|
parseExpression()
|
||||||
@ -887,7 +887,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.NEWLINE -> {}
|
Token.Type.NEWLINE -> {}
|
||||||
Token.Type.MULTILINE_COMMENT, Token.Type.SINGLE_LINE_COMMENT -> {}
|
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT -> {}
|
||||||
|
|
||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
// visibility
|
// visibility
|
||||||
@ -2734,8 +2734,7 @@ class Compiler(
|
|||||||
doc = declDocLocal,
|
doc = declDocLocal,
|
||||||
nameStart = nameStartPos,
|
nameStart = nameStartPos,
|
||||||
receiver = receiverMini,
|
receiver = receiverMini,
|
||||||
isExtern = actualExtern,
|
isExtern = actualExtern
|
||||||
isStatic = isStatic
|
|
||||||
)
|
)
|
||||||
miniSink?.onFunDecl(node)
|
miniSink?.onFunDecl(node)
|
||||||
pendingDeclDoc = null
|
pendingDeclDoc = null
|
||||||
@ -2942,11 +2941,10 @@ class Compiler(
|
|||||||
doc = declDocLocal,
|
doc = declDocLocal,
|
||||||
nameStart = nameStartPos,
|
nameStart = nameStartPos,
|
||||||
receiver = receiverMini,
|
receiver = receiverMini,
|
||||||
isExtern = actualExtern,
|
isExtern = actualExtern
|
||||||
isStatic = isStatic
|
|
||||||
)
|
)
|
||||||
miniSink?.onExitFunction(cc.currentPos())
|
|
||||||
miniSink?.onFunDecl(node)
|
miniSink?.onFunDecl(node)
|
||||||
|
miniSink?.onExitFunction(cc.currentPos())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3004,8 +3002,7 @@ class Compiler(
|
|||||||
initRange = null,
|
initRange = null,
|
||||||
doc = pendingDeclDoc,
|
doc = pendingDeclDoc,
|
||||||
nameStart = namePos,
|
nameStart = namePos,
|
||||||
isExtern = actualExtern,
|
isExtern = actualExtern
|
||||||
isStatic = false
|
|
||||||
)
|
)
|
||||||
miniSink?.onValDecl(node)
|
miniSink?.onValDecl(node)
|
||||||
}
|
}
|
||||||
@ -3197,8 +3194,7 @@ class Compiler(
|
|||||||
doc = pendingDeclDoc,
|
doc = pendingDeclDoc,
|
||||||
nameStart = nameStartPos,
|
nameStart = nameStartPos,
|
||||||
receiver = receiverMini,
|
receiver = receiverMini,
|
||||||
isExtern = actualExtern,
|
isExtern = actualExtern
|
||||||
isStatic = isStatic
|
|
||||||
)
|
)
|
||||||
miniSink?.onValDecl(node)
|
miniSink?.onValDecl(node)
|
||||||
pendingDeclDoc = null
|
pendingDeclDoc = null
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -238,7 +238,7 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.MULTILINE_COMMENT, Token.Type.SINGLE_LINE_COMMENT, Token.Type.NEWLINE -> {}
|
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {}
|
||||||
else -> {
|
else -> {
|
||||||
restorePos(pos); return false
|
restorePos(pos); return false
|
||||||
}
|
}
|
||||||
@ -265,6 +265,6 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val wstokens = setOf(Token.Type.NEWLINE, Token.Type.MULTILINE_COMMENT, Token.Type.SINGLE_LINE_COMMENT)
|
val wstokens = setOf(Token.Type.NEWLINE, Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
pos.advance()
|
pos.advance()
|
||||||
val body = loadToEndOfLine()
|
val body = loadToEndOfLine()
|
||||||
// Include the leading '//' and do not trim; keep exact lexeme (excluding preceding codepoint)
|
// Include the leading '//' and do not trim; keep exact lexeme (excluding preceding codepoint)
|
||||||
Token("//" + body, from, Token.Type.SINGLE_LINE_COMMENT)
|
Token("//" + body, from, Token.Type.SINLGE_LINE_COMMENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
'*' -> {
|
'*' -> {
|
||||||
|
|||||||
@ -70,8 +70,9 @@ open class Scope(
|
|||||||
|
|
||||||
internal fun findExtension(receiverClass: ObjClass, name: String): ObjRecord? {
|
internal fun findExtension(receiverClass: ObjClass, name: String): ObjRecord? {
|
||||||
var s: Scope? = this
|
var s: Scope? = this
|
||||||
var hops = 0
|
val visited = HashSet<Long>(4)
|
||||||
while (s != null && hops++ < 1024) {
|
while (s != null) {
|
||||||
|
if (!visited.add(s.frameId)) break
|
||||||
// Proximity rule: check all extensions in the current scope before going to parent.
|
// Proximity rule: check all extensions in the current scope before going to parent.
|
||||||
// Priority within scope: more specific class in MRO wins.
|
// Priority within scope: more specific class in MRO wins.
|
||||||
for (cls in receiverClass.mro) {
|
for (cls in receiverClass.mro) {
|
||||||
@ -107,7 +108,7 @@ open class Scope(
|
|||||||
*/
|
*/
|
||||||
internal fun tryGetLocalRecord(s: Scope, name: String, caller: net.sergeych.lyng.obj.ObjClass?): ObjRecord? {
|
internal fun tryGetLocalRecord(s: Scope, name: String, caller: net.sergeych.lyng.obj.ObjClass?): ObjRecord? {
|
||||||
caller?.let { ctx ->
|
caller?.let { ctx ->
|
||||||
s.objects[ctx.mangledName(name)]?.let { rec ->
|
s.objects["${ctx.className}::$name"]?.let { rec ->
|
||||||
if (rec.visibility == Visibility.Private) return rec
|
if (rec.visibility == Visibility.Private) return rec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,7 +116,7 @@ open class Scope(
|
|||||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
||||||
}
|
}
|
||||||
caller?.let { ctx ->
|
caller?.let { ctx ->
|
||||||
s.localBindings[ctx.mangledName(name)]?.let { rec ->
|
s.localBindings["${ctx.className}::$name"]?.let { rec ->
|
||||||
if (rec.visibility == Visibility.Private) return rec
|
if (rec.visibility == Visibility.Private) return rec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,10 +132,11 @@ open class Scope(
|
|||||||
|
|
||||||
internal fun chainLookupIgnoreClosure(name: String, followClosure: Boolean = false, caller: net.sergeych.lyng.obj.ObjClass? = null): ObjRecord? {
|
internal fun chainLookupIgnoreClosure(name: String, followClosure: Boolean = false, caller: net.sergeych.lyng.obj.ObjClass? = null): ObjRecord? {
|
||||||
var s: Scope? = this
|
var s: Scope? = this
|
||||||
// use hop counter to detect unexpected structural cycles in the parent chain
|
// use frameId to detect unexpected structural cycles in the parent chain
|
||||||
var hops = 0
|
val visited = HashSet<Long>(4)
|
||||||
val effectiveCaller = caller ?: currentClassCtx
|
val effectiveCaller = caller ?: currentClassCtx
|
||||||
while (s != null && hops++ < 1024) {
|
while (s != null) {
|
||||||
|
if (!visited.add(s.frameId)) return null
|
||||||
tryGetLocalRecord(s, name, effectiveCaller)?.let { return it }
|
tryGetLocalRecord(s, name, effectiveCaller)?.let { return it }
|
||||||
s = if (followClosure && s is ClosureScope) s.closureScope else s.parent
|
s = if (followClosure && s is ClosureScope) s.closureScope else s.parent
|
||||||
}
|
}
|
||||||
@ -153,8 +155,9 @@ open class Scope(
|
|||||||
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
|
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
|
||||||
// 2) walk parents for plain locals/bindings only
|
// 2) walk parents for plain locals/bindings only
|
||||||
var s = parent
|
var s = parent
|
||||||
var hops = 0
|
val visited = HashSet<Long>(4)
|
||||||
while (s != null && hops++ < 1024) {
|
while (s != null) {
|
||||||
|
if (!visited.add(s.frameId)) return null
|
||||||
tryGetLocalRecord(s, name, currentClassCtx)?.let { return it }
|
tryGetLocalRecord(s, name, currentClassCtx)?.let { return it }
|
||||||
s = s.parent
|
s = s.parent
|
||||||
}
|
}
|
||||||
@ -179,8 +182,9 @@ open class Scope(
|
|||||||
*/
|
*/
|
||||||
internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx, followClosure: Boolean = false): ObjRecord? {
|
internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx, followClosure: Boolean = false): ObjRecord? {
|
||||||
var s: Scope? = this
|
var s: Scope? = this
|
||||||
var hops = 0
|
val visited = HashSet<Long>(4)
|
||||||
while (s != null && hops++ < 1024) {
|
while (s != null) {
|
||||||
|
if (!visited.add(s.frameId)) return null
|
||||||
tryGetLocalRecord(s, name, caller)?.let { return it }
|
tryGetLocalRecord(s, name, caller)?.let { return it }
|
||||||
for (cls in s.thisObj.objClass.mro) {
|
for (cls in s.thisObj.objClass.mro) {
|
||||||
s.extensions[cls]?.get(name)?.let { return it }
|
s.extensions[cls]?.get(name)?.let { return it }
|
||||||
@ -392,20 +396,6 @@ open class Scope(
|
|||||||
nameToSlot[name]?.let { slots[it] = record }
|
nameToSlot[name]?.let { slots[it] = record }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all references and maps to prevent memory leaks when pooled.
|
|
||||||
*/
|
|
||||||
fun scrub() {
|
|
||||||
this.parent = null
|
|
||||||
this.skipScopeCreation = false
|
|
||||||
this.currentClassCtx = null
|
|
||||||
objects.clear()
|
|
||||||
slots.clear()
|
|
||||||
nameToSlot.clear()
|
|
||||||
localBindings.clear()
|
|
||||||
extensions.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset this scope instance so it can be safely reused as a fresh child frame.
|
* Reset this scope instance so it can be safely reused as a fresh child frame.
|
||||||
* Clears locals and slots, assigns new frameId, and sets parent/args/pos/thisObj.
|
* Clears locals and slots, assigns new frameId, and sets parent/args/pos/thisObj.
|
||||||
@ -424,6 +414,7 @@ open class Scope(
|
|||||||
nameToSlot.clear()
|
nameToSlot.clear()
|
||||||
localBindings.clear()
|
localBindings.clear()
|
||||||
extensions.clear()
|
extensions.clear()
|
||||||
|
this.currentClassCtx = parent?.currentClassCtx
|
||||||
// Now safe to validate and re-parent
|
// Now safe to validate and re-parent
|
||||||
ensureNoCycle(parent)
|
ensureNoCycle(parent)
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -543,16 +534,12 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Map to a slot for fast local access (ensure consistency)
|
// Map to a slot for fast local access (ensure consistency)
|
||||||
if (nameToSlot.isEmpty()) {
|
val idx = getSlotIndexOf(name)
|
||||||
allocateSlotFor(name, rec)
|
|
||||||
} else {
|
|
||||||
val idx = nameToSlot[name]
|
|
||||||
if (idx == null) {
|
if (idx == null) {
|
||||||
allocateSlotFor(name, rec)
|
allocateSlotFor(name, rec)
|
||||||
} else {
|
} else {
|
||||||
slots[idx] = rec
|
slots[idx] = rec
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return rec
|
return rec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
throw ScriptError(pos, text)
|
throw ScriptError(pos, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isComment: Boolean by lazy { type == Type.SINGLE_LINE_COMMENT || type == Type.MULTILINE_COMMENT }
|
val isComment: Boolean by lazy { type == Type.SINLGE_LINE_COMMENT || type == Type.MULTILINE_COMMENT }
|
||||||
|
|
||||||
fun isId(text: String) =
|
fun isId(text: String) =
|
||||||
type == Type.ID && value == text
|
type == Type.ID && value == text
|
||||||
@ -41,7 +41,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
SHUTTLE,
|
SHUTTLE,
|
||||||
AND, BITAND, OR, BITOR, BITXOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
|
AND, BITAND, OR, BITOR, BITXOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
|
||||||
SHL, SHR,
|
SHL, SHR,
|
||||||
SINGLE_LINE_COMMENT, MULTILINE_COMMENT,
|
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||||
LABEL, ATLABEL, // label@ at@label
|
LABEL, ATLABEL, // label@ at@label
|
||||||
// type-checking/casting
|
// type-checking/casting
|
||||||
AS, ASNULL, OBJECT,
|
AS, ASNULL, OBJECT,
|
||||||
|
|||||||
@ -67,7 +67,7 @@ private fun kindOf(type: Type, value: String): HighlightKind? = when (type) {
|
|||||||
Type.REGEX -> HighlightKind.Regex
|
Type.REGEX -> HighlightKind.Regex
|
||||||
|
|
||||||
// comments
|
// comments
|
||||||
Type.SINGLE_LINE_COMMENT, Type.MULTILINE_COMMENT -> HighlightKind.Comment
|
Type.SINLGE_LINE_COMMENT, Type.MULTILINE_COMMENT -> HighlightKind.Comment
|
||||||
|
|
||||||
// punctuation
|
// punctuation
|
||||||
Type.LPAREN, Type.RPAREN, Type.LBRACE, Type.RBRACE, Type.LBRACKET, Type.RBRACKET,
|
Type.LPAREN, Type.RPAREN, Type.LBRACE, Type.RBRACE, Type.LBRACKET, Type.RBRACKET,
|
||||||
|
|||||||
@ -148,8 +148,6 @@ class ModuleDocsBuilder internal constructor(private val moduleName: String) {
|
|||||||
body = null,
|
body = null,
|
||||||
doc = md,
|
doc = md,
|
||||||
nameStart = Pos.builtIn,
|
nameStart = Pos.builtIn,
|
||||||
isExtern = false,
|
|
||||||
isStatic = false
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,8 +167,6 @@ class ModuleDocsBuilder internal constructor(private val moduleName: String) {
|
|||||||
initRange = null,
|
initRange = null,
|
||||||
doc = md,
|
doc = md,
|
||||||
nameStart = Pos.builtIn,
|
nameStart = Pos.builtIn,
|
||||||
isExtern = false,
|
|
||||||
isStatic = false
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,6 @@ data class CompletionItem(
|
|||||||
val kind: Kind,
|
val kind: Kind,
|
||||||
val tailText: String? = null,
|
val tailText: String? = null,
|
||||||
val typeText: String? = null,
|
val typeText: String? = null,
|
||||||
val priority: Double = 0.0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class Kind { Function, Class_, Enum, Value, Method, Field }
|
enum class Kind { Function, Class_, Enum, Value, Method, Field }
|
||||||
@ -108,7 +107,7 @@ object CompletionEngineLight {
|
|||||||
val locals = DocLookupUtils.extractLocalsAt(text, caret)
|
val locals = DocLookupUtils.extractLocalsAt(text, caret)
|
||||||
for (name in locals) {
|
for (name in locals) {
|
||||||
if (name.startsWith(prefix, true)) {
|
if (name.startsWith(prefix, true)) {
|
||||||
out.add(CompletionItem(name, Kind.Value, priority = 150.0))
|
out.add(CompletionItem(name, Kind.Value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,17 +168,17 @@ object CompletionEngineLight {
|
|||||||
when (node) {
|
when (node) {
|
||||||
is MiniFunDecl -> {
|
is MiniFunDecl -> {
|
||||||
for (p in node.params) {
|
for (p in node.params) {
|
||||||
add(CompletionItem(p.name, Kind.Value, typeText = typeOf(p.type), priority = 200.0))
|
add(CompletionItem(p.name, Kind.Value, typeText = typeOf(p.type)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MiniClassDecl -> {
|
is MiniClassDecl -> {
|
||||||
// Propose constructor parameters (ctorFields)
|
// Propose constructor parameters (ctorFields)
|
||||||
for (p in node.ctorFields) {
|
for (p in node.ctorFields) {
|
||||||
add(CompletionItem(p.name, if (p.mutable) Kind.Value else Kind.Field, typeText = typeOf(p.type), priority = 200.0))
|
add(CompletionItem(p.name, if (p.mutable) Kind.Value else Kind.Field, typeText = typeOf(p.type)))
|
||||||
}
|
}
|
||||||
// Propose class-level fields
|
// Propose class-level fields
|
||||||
for (p in node.classFields) {
|
for (p in node.classFields) {
|
||||||
add(CompletionItem(p.name, if (p.mutable) Kind.Value else Kind.Field, typeText = typeOf(p.type), priority = 100.0))
|
add(CompletionItem(p.name, if (p.mutable) Kind.Value else Kind.Field, typeText = typeOf(p.type)))
|
||||||
}
|
}
|
||||||
// Process members (methods/fields)
|
// Process members (methods/fields)
|
||||||
for (m in node.members) {
|
for (m in node.members) {
|
||||||
@ -190,10 +189,10 @@ object CompletionEngineLight {
|
|||||||
when (m) {
|
when (m) {
|
||||||
is MiniMemberFunDecl -> {
|
is MiniMemberFunDecl -> {
|
||||||
val params = m.params.joinToString(", ") { it.name }
|
val params = m.params.joinToString(", ") { it.name }
|
||||||
add(CompletionItem(m.name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType), priority = 100.0))
|
add(CompletionItem(m.name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType)))
|
||||||
}
|
}
|
||||||
is MiniMemberValDecl -> {
|
is MiniMemberValDecl -> {
|
||||||
add(CompletionItem(m.name, if (m.mutable) Kind.Value else Kind.Field, typeText = typeOf(m.type), priority = 100.0))
|
add(CompletionItem(m.name, if (m.mutable) Kind.Value else Kind.Field, typeText = typeOf(m.type)))
|
||||||
}
|
}
|
||||||
is MiniInitDecl -> {}
|
is MiniInitDecl -> {}
|
||||||
}
|
}
|
||||||
@ -201,7 +200,7 @@ object CompletionEngineLight {
|
|||||||
}
|
}
|
||||||
is MiniMemberFunDecl -> {
|
is MiniMemberFunDecl -> {
|
||||||
for (p in node.params) {
|
for (p in node.params) {
|
||||||
add(CompletionItem(p.name, Kind.Value, typeText = typeOf(p.type), priority = 200.0))
|
add(CompletionItem(p.name, Kind.Value, typeText = typeOf(p.type)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
@ -251,7 +250,7 @@ object CompletionEngineLight {
|
|||||||
"Array" -> listOf("Collection", "Iterable").forEach { if (visited.add(it)) addMembersOf(it, false) }
|
"Array" -> listOf("Collection", "Iterable").forEach { if (visited.add(it)) addMembersOf(it, false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>, groupPriority: Double) {
|
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>) {
|
||||||
for (name in map.keys.sortedBy { it.lowercase() }) {
|
for (name in map.keys.sortedBy { it.lowercase() }) {
|
||||||
val variants = map[name] ?: continue
|
val variants = map[name] ?: continue
|
||||||
// Prefer a method with a known return type; else any method; else first variant
|
// Prefer a method with a known return type; else any method; else first variant
|
||||||
@ -266,7 +265,7 @@ object CompletionEngineLight {
|
|||||||
val params = rep.params.joinToString(", ") { it.name }
|
val params = rep.params.joinToString(", ") { it.name }
|
||||||
val extra = variants.count { it is MiniMemberFunDecl } - 1
|
val extra = variants.count { it is MiniMemberFunDecl } - 1
|
||||||
val ov = if (extra > 0) " (+$extra overloads)" else ""
|
val ov = if (extra > 0) " (+$extra overloads)" else ""
|
||||||
val ci = CompletionItem(name, Kind.Method, tailText = "(${params})$ov", typeText = typeOf(rep.returnType), priority = groupPriority)
|
val ci = CompletionItem(name, Kind.Method, tailText = "(${params})$ov", typeText = typeOf(rep.returnType))
|
||||||
if (ci.name.startsWith(prefix, true)) out += ci
|
if (ci.name.startsWith(prefix, true)) out += ci
|
||||||
}
|
}
|
||||||
is MiniMemberValDecl -> {
|
is MiniMemberValDecl -> {
|
||||||
@ -274,7 +273,7 @@ object CompletionEngineLight {
|
|||||||
val chosen = variants.asSequence()
|
val chosen = variants.asSequence()
|
||||||
.filterIsInstance<MiniMemberValDecl>()
|
.filterIsInstance<MiniMemberValDecl>()
|
||||||
.firstOrNull { it.type != null } ?: rep
|
.firstOrNull { it.type != null } ?: rep
|
||||||
val ci = CompletionItem(name, Kind.Field, typeText = typeOf(chosen.type), priority = groupPriority)
|
val ci = CompletionItem(name, Kind.Field, typeText = typeOf(chosen.type))
|
||||||
if (ci.name.startsWith(prefix, true)) out += ci
|
if (ci.name.startsWith(prefix, true)) out += ci
|
||||||
}
|
}
|
||||||
is MiniInitDecl -> {}
|
is MiniInitDecl -> {}
|
||||||
@ -282,8 +281,8 @@ object CompletionEngineLight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitGroup(directMap, 100.0)
|
emitGroup(directMap)
|
||||||
emitGroup(inheritedMap, 0.0)
|
emitGroup(inheritedMap)
|
||||||
|
|
||||||
// Supplement with extension members (both stdlib and local)
|
// Supplement with extension members (both stdlib and local)
|
||||||
run {
|
run {
|
||||||
@ -297,22 +296,22 @@ object CompletionEngineLight {
|
|||||||
val ci = when (m) {
|
val ci = when (m) {
|
||||||
is MiniMemberFunDecl -> {
|
is MiniMemberFunDecl -> {
|
||||||
val params = m.params.joinToString(", ") { it.name }
|
val params = m.params.joinToString(", ") { it.name }
|
||||||
CompletionItem(name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType), priority = 50.0)
|
CompletionItem(name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType))
|
||||||
}
|
}
|
||||||
is MiniFunDecl -> {
|
is MiniFunDecl -> {
|
||||||
val params = m.params.joinToString(", ") { it.name }
|
val params = m.params.joinToString(", ") { it.name }
|
||||||
CompletionItem(name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType), priority = 50.0)
|
CompletionItem(name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType))
|
||||||
}
|
}
|
||||||
is MiniMemberValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type), priority = 50.0)
|
is MiniMemberValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type))
|
||||||
is MiniValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type), priority = 50.0)
|
is MiniValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type))
|
||||||
else -> CompletionItem(name, Kind.Method, tailText = "()", typeText = null, priority = 50.0)
|
else -> CompletionItem(name, Kind.Method, tailText = "()", typeText = null)
|
||||||
}
|
}
|
||||||
if (ci.name.startsWith(prefix, true)) {
|
if (ci.name.startsWith(prefix, true)) {
|
||||||
out += ci
|
out += ci
|
||||||
already.add(name)
|
already.add(name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val ci = CompletionItem(name, Kind.Method, tailText = "()", typeText = null, priority = 50.0)
|
val ci = CompletionItem(name, Kind.Method, tailText = "()", typeText = null)
|
||||||
if (ci.name.startsWith(prefix, true)) {
|
if (ci.name.startsWith(prefix, true)) {
|
||||||
out += ci
|
out += ci
|
||||||
already.add(name)
|
already.add(name)
|
||||||
|
|||||||
@ -87,7 +87,6 @@ sealed interface MiniNamedDecl : MiniNode {
|
|||||||
// Start position of the declaration name identifier in source; end can be derived as start + name.length
|
// Start position of the declaration name identifier in source; end can be derived as start + name.length
|
||||||
val nameStart: Pos
|
val nameStart: Pos
|
||||||
val isExtern: Boolean
|
val isExtern: Boolean
|
||||||
val isStatic: Boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface MiniDecl : MiniNamedDecl
|
sealed interface MiniDecl : MiniNamedDecl
|
||||||
@ -115,8 +114,7 @@ data class MiniFunDecl(
|
|||||||
override val doc: MiniDoc?,
|
override val doc: MiniDoc?,
|
||||||
override val nameStart: Pos,
|
override val nameStart: Pos,
|
||||||
val receiver: MiniTypeRef? = null,
|
val receiver: MiniTypeRef? = null,
|
||||||
override val isExtern: Boolean = false,
|
override val isExtern: Boolean = false
|
||||||
override val isStatic: Boolean = false,
|
|
||||||
) : MiniDecl
|
) : MiniDecl
|
||||||
|
|
||||||
data class MiniValDecl(
|
data class MiniValDecl(
|
||||||
@ -128,8 +126,7 @@ data class MiniValDecl(
|
|||||||
override val doc: MiniDoc?,
|
override val doc: MiniDoc?,
|
||||||
override val nameStart: Pos,
|
override val nameStart: Pos,
|
||||||
val receiver: MiniTypeRef? = null,
|
val receiver: MiniTypeRef? = null,
|
||||||
override val isExtern: Boolean = false,
|
override val isExtern: Boolean = false
|
||||||
override val isStatic: Boolean = false,
|
|
||||||
) : MiniDecl
|
) : MiniDecl
|
||||||
|
|
||||||
data class MiniClassDecl(
|
data class MiniClassDecl(
|
||||||
@ -144,7 +141,6 @@ data class MiniClassDecl(
|
|||||||
// Built-in extension: list of member declarations (functions and fields)
|
// Built-in extension: list of member declarations (functions and fields)
|
||||||
val members: List<MiniMemberDecl> = emptyList(),
|
val members: List<MiniMemberDecl> = emptyList(),
|
||||||
override val isExtern: Boolean = false,
|
override val isExtern: Boolean = false,
|
||||||
override val isStatic: Boolean = false,
|
|
||||||
val isObject: Boolean = false
|
val isObject: Boolean = false
|
||||||
) : MiniDecl
|
) : MiniDecl
|
||||||
|
|
||||||
@ -154,8 +150,7 @@ data class MiniEnumDecl(
|
|||||||
val entries: List<String>,
|
val entries: List<String>,
|
||||||
override val doc: MiniDoc?,
|
override val doc: MiniDoc?,
|
||||||
override val nameStart: Pos,
|
override val nameStart: Pos,
|
||||||
override val isExtern: Boolean = false,
|
override val isExtern: Boolean = false
|
||||||
override val isStatic: Boolean = false,
|
|
||||||
) : MiniDecl
|
) : MiniDecl
|
||||||
|
|
||||||
data class MiniCtorField(
|
data class MiniCtorField(
|
||||||
@ -180,7 +175,7 @@ data class MiniIdentifier(
|
|||||||
|
|
||||||
// --- Class member declarations (for built-in/registry docs) ---
|
// --- Class member declarations (for built-in/registry docs) ---
|
||||||
sealed interface MiniMemberDecl : MiniNamedDecl {
|
sealed interface MiniMemberDecl : MiniNamedDecl {
|
||||||
override val isStatic: Boolean
|
val isStatic: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MiniMemberFunDecl(
|
data class MiniMemberFunDecl(
|
||||||
@ -324,7 +319,7 @@ class MiniAstBuilder : MiniAstSink {
|
|||||||
returnType = attach.returnType,
|
returnType = attach.returnType,
|
||||||
doc = attach.doc,
|
doc = attach.doc,
|
||||||
nameStart = attach.nameStart,
|
nameStart = attach.nameStart,
|
||||||
isStatic = attach.isStatic,
|
isStatic = false, // TODO: track static if needed
|
||||||
isExtern = attach.isExtern,
|
isExtern = attach.isExtern,
|
||||||
body = attach.body
|
body = attach.body
|
||||||
)
|
)
|
||||||
@ -364,7 +359,7 @@ class MiniAstBuilder : MiniAstSink {
|
|||||||
initRange = attach.initRange,
|
initRange = attach.initRange,
|
||||||
doc = attach.doc,
|
doc = attach.doc,
|
||||||
nameStart = attach.nameStart,
|
nameStart = attach.nameStart,
|
||||||
isStatic = attach.isStatic,
|
isStatic = false, // TODO: track static if needed
|
||||||
isExtern = attach.isExtern
|
isExtern = attach.isExtern
|
||||||
)
|
)
|
||||||
// Duplicates for vals are rare but possible if Compiler calls it twice
|
// Duplicates for vals are rare but possible if Compiler calls it twice
|
||||||
|
|||||||
@ -106,34 +106,6 @@ open class ObjClass(
|
|||||||
val classId: Long = ClassIdGen.nextId()
|
val classId: Long = ClassIdGen.nextId()
|
||||||
var layoutVersion: Int = 0
|
var layoutVersion: Int = 0
|
||||||
|
|
||||||
private val mangledNameCache = mutableMapOf<String, String>()
|
|
||||||
fun mangledName(name: String): String = mangledNameCache.getOrPut(name) { "$className::$name" }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map of public member names to their effective storage keys in instanceScope.objects.
|
|
||||||
* This is pre-calculated to avoid MRO traversal and string concatenation during common access.
|
|
||||||
*/
|
|
||||||
val publicMemberResolution: Map<String, String> by lazy {
|
|
||||||
val res = mutableMapOf<String, String>()
|
|
||||||
// Traverse MRO in REVERSED order so that child classes override parent classes in the map.
|
|
||||||
for (cls in mro.reversed()) {
|
|
||||||
if (cls.className == "Obj") continue
|
|
||||||
for ((name, rec) in cls.members) {
|
|
||||||
if (rec.visibility == Visibility.Public) {
|
|
||||||
val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
|
||||||
res[name] = key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
|
||||||
if (rec.visibility == Visibility.Public && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
|
|
||||||
val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
|
||||||
res[name] = key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
val classNameObj by lazy { ObjString(className) }
|
val classNameObj by lazy { ObjString(className) }
|
||||||
|
|
||||||
var constructorMeta: ArgsDeclaration? = null
|
var constructorMeta: ArgsDeclaration? = null
|
||||||
@ -270,43 +242,6 @@ open class ObjClass(
|
|||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Pre-calculated template for instanceScope.objects. */
|
|
||||||
private val instanceObjectsTemplate: Map<String, ObjRecord> by lazy {
|
|
||||||
val res = mutableMapOf<String, ObjRecord>()
|
|
||||||
for (cls in mro) {
|
|
||||||
// 1) members-defined methods and fields
|
|
||||||
for ((k, v) in cls.members) {
|
|
||||||
if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field)) {
|
|
||||||
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
|
|
||||||
if (!res.containsKey(key)) {
|
|
||||||
res[key] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 2) class-scope members registered during class-body execution
|
|
||||||
cls.classScope?.objects?.forEach { (k, rec) ->
|
|
||||||
// ONLY copy methods and delegated members from class scope to instance scope.
|
|
||||||
// Fields in class scope are static fields and must NOT be per-instance.
|
|
||||||
if (!rec.isAbstract && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
|
|
||||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
|
|
||||||
// if not already present, copy reference for dispatch
|
|
||||||
if (!res.containsKey(key)) {
|
|
||||||
res[key] = rec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
private val templateMethods: Map<String, ObjRecord> by lazy {
|
|
||||||
instanceObjectsTemplate.filter { it.value.type == ObjRecord.Type.Fun }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val templateOthers: List<Pair<String, ObjRecord>> by lazy {
|
|
||||||
instanceObjectsTemplate.filter { it.value.type != ObjRecord.Type.Fun }.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance of this class and initialize its [ObjInstance.instanceScope] with
|
* Create an instance of this class and initialize its [ObjInstance.instanceScope] with
|
||||||
* methods. Does NOT run initializers or constructors.
|
* methods. Does NOT run initializers or constructors.
|
||||||
@ -322,9 +257,28 @@ open class ObjClass(
|
|||||||
// Expose instance methods (and other callable members) directly in the instance scope for fast lookup
|
// Expose instance methods (and other callable members) directly in the instance scope for fast lookup
|
||||||
// This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust
|
// This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust
|
||||||
|
|
||||||
instance.instanceScope.objects.putAll(templateMethods)
|
for (cls in mro) {
|
||||||
for (p in templateOthers) {
|
// 1) members-defined methods and fields
|
||||||
instance.instanceScope.objects[p.first] = p.second.copy()
|
for ((k, v) in cls.members) {
|
||||||
|
if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field)) {
|
||||||
|
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.Delegated) "${cls.className}::$k" else k
|
||||||
|
if (!instance.instanceScope.objects.containsKey(key)) {
|
||||||
|
instance.instanceScope.objects[key] = if (v.type == ObjRecord.Type.Fun) v else v.copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2) class-scope members registered during class-body execution
|
||||||
|
cls.classScope?.objects?.forEach { (k, rec) ->
|
||||||
|
// ONLY copy methods and delegated members from class scope to instance scope.
|
||||||
|
// Fields in class scope are static fields and must NOT be per-instance.
|
||||||
|
if (!rec.isAbstract && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
|
||||||
|
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) "${cls.className}::$k" else k
|
||||||
|
// if not already present, copy reference for dispatch
|
||||||
|
if (!instance.instanceScope.objects.containsKey(key)) {
|
||||||
|
instance.instanceScope.objects[key] = if (rec.type == ObjRecord.Type.Fun) rec else rec.copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
@ -373,7 +327,7 @@ open class ObjClass(
|
|||||||
for (p in meta.params) {
|
for (p in meta.params) {
|
||||||
val rec = instance.instanceScope.objects[p.name]
|
val rec = instance.instanceScope.objects[p.name]
|
||||||
if (rec != null) {
|
if (rec != null) {
|
||||||
val mangled = c.mangledName(p.name)
|
val mangled = "${c.className}::${p.name}"
|
||||||
// Always point the mangled name to the current record to keep writes consistent
|
// Always point the mangled name to the current record to keep writes consistent
|
||||||
// across re-bindings
|
// across re-bindings
|
||||||
instance.instanceScope.objects[mangled] = rec
|
instance.instanceScope.objects[mangled] = rec
|
||||||
@ -407,7 +361,7 @@ open class ObjClass(
|
|||||||
for (p in meta.params) {
|
for (p in meta.params) {
|
||||||
val rec = instance.instanceScope.objects[p.name]
|
val rec = instance.instanceScope.objects[p.name]
|
||||||
if (rec != null) {
|
if (rec != null) {
|
||||||
val mangled = c.mangledName(p.name)
|
val mangled = "${c.className}::${p.name}"
|
||||||
instance.instanceScope.objects[mangled] = rec
|
instance.instanceScope.objects[mangled] = rec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,18 +34,6 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
|
|
||||||
// Fast path for public members when outside any class context
|
|
||||||
if (caller == null) {
|
|
||||||
objClass.publicMemberResolution[name]?.let { key ->
|
|
||||||
instanceScope.objects[key]?.let { rec ->
|
|
||||||
// Directly return fields to bypass resolveRecord overhead
|
|
||||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
|
|
||||||
return rec
|
|
||||||
return resolveRecord(scope, rec, name, rec.declaringClass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0. Private mangled of current class context
|
// 0. Private mangled of current class context
|
||||||
caller?.let { c ->
|
caller?.let { c ->
|
||||||
// Check for private methods/properties
|
// Check for private methods/properties
|
||||||
@ -55,7 +43,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check for private fields (stored in instanceScope)
|
// Check for private fields (stored in instanceScope)
|
||||||
val mangled = c.mangledName(name)
|
val mangled = "${c.className}::$name"
|
||||||
instanceScope.objects[mangled]?.let { rec ->
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
if (rec.visibility == Visibility.Private) {
|
if (rec.visibility == Visibility.Private) {
|
||||||
return resolveRecord(scope, rec, name, c)
|
return resolveRecord(scope, rec, name, c)
|
||||||
@ -66,7 +54,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
// 1. MRO mangled storage
|
// 1. MRO mangled storage
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
val mangled = cls.mangledName(name)
|
val mangled = "${cls.className}::$name"
|
||||||
instanceScope.objects[mangled]?.let { rec ->
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, cls, caller)) {
|
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, cls, caller)) {
|
||||||
return resolveRecord(scope, rec, name, cls)
|
return resolveRecord(scope, rec, name, cls)
|
||||||
@ -91,11 +79,11 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
if (obj.type.isArgument) return super.resolveRecord(scope, obj, name, decl)
|
if (obj.type.isArgument) return super.resolveRecord(scope, obj, name, decl)
|
||||||
if (obj.type == ObjRecord.Type.Delegated) {
|
if (obj.type == ObjRecord.Type.Delegated) {
|
||||||
val d = decl ?: obj.declaringClass
|
val d = decl ?: obj.declaringClass
|
||||||
val storageName = d?.mangledName(name) ?: name
|
val storageName = "${d?.className}::$name"
|
||||||
var del = instanceScope[storageName]?.delegate ?: obj.delegate
|
var del = instanceScope[storageName]?.delegate ?: obj.delegate
|
||||||
if (del == null) {
|
if (del == null) {
|
||||||
for (c in objClass.mro) {
|
for (c in objClass.mro) {
|
||||||
del = instanceScope[c.mangledName(name)]?.delegate
|
del = instanceScope["${c.className}::$name"]?.delegate
|
||||||
if (del != null) break
|
if (del != null) break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +97,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
var targetRec = obj
|
var targetRec = obj
|
||||||
val d = decl ?: obj.declaringClass
|
val d = decl ?: obj.declaringClass
|
||||||
if (d != null) {
|
if (d != null) {
|
||||||
val mangled = d.mangledName(name)
|
val mangled = "${d.className}::$name"
|
||||||
instanceScope.objects[mangled]?.let {
|
instanceScope.objects[mangled]?.let {
|
||||||
targetRec = it
|
targetRec = it
|
||||||
}
|
}
|
||||||
@ -132,24 +120,6 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
willMutate(scope)
|
willMutate(scope)
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
|
|
||||||
// Fast path for public members when outside any class context
|
|
||||||
if (caller == null) {
|
|
||||||
objClass.publicMemberResolution[name]?.let { key ->
|
|
||||||
instanceScope.objects[key]?.let { rec ->
|
|
||||||
if (rec.effectiveWriteVisibility == Visibility.Public) {
|
|
||||||
// Skip property/delegated overhead if it's a plain mutable field
|
|
||||||
if (rec.type == ObjRecord.Type.Field && rec.isMutable && !rec.isAbstract) {
|
|
||||||
if (rec.value.assign(scope, newValue) == null)
|
|
||||||
rec.value = newValue
|
|
||||||
return
|
|
||||||
}
|
|
||||||
updateRecord(scope, rec, name, newValue, rec.declaringClass)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0. Private mangled of current class context
|
// 0. Private mangled of current class context
|
||||||
caller?.let { c ->
|
caller?.let { c ->
|
||||||
// Check for private methods/properties
|
// Check for private methods/properties
|
||||||
@ -160,7 +130,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check for private fields (stored in instanceScope)
|
// Check for private fields (stored in instanceScope)
|
||||||
val mangled = c.mangledName(name)
|
val mangled = "${c.className}::$name"
|
||||||
instanceScope.objects[mangled]?.let { rec ->
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
if (rec.visibility == Visibility.Private) {
|
if (rec.visibility == Visibility.Private) {
|
||||||
updateRecord(scope, rec, name, newValue, c)
|
updateRecord(scope, rec, name, newValue, c)
|
||||||
@ -172,7 +142,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
// 1. MRO mangled storage
|
// 1. MRO mangled storage
|
||||||
for (cls in objClass.mro) {
|
for (cls in objClass.mro) {
|
||||||
if (cls.className == "Obj") break
|
if (cls.className == "Obj") break
|
||||||
val mangled = cls.mangledName(name)
|
val mangled = "${cls.className}::$name"
|
||||||
instanceScope.objects[mangled]?.let { rec ->
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, cls, caller)) {
|
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, cls, caller)) {
|
||||||
updateRecord(scope, rec, name, newValue, cls)
|
updateRecord(scope, rec, name, newValue, cls)
|
||||||
@ -221,42 +191,24 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
scope: Scope, name: String, args: Arguments,
|
scope: Scope, name: String, args: Arguments,
|
||||||
onNotFoundResult: (suspend () -> Obj?)?
|
onNotFoundResult: (suspend () -> Obj?)?
|
||||||
): Obj {
|
): Obj {
|
||||||
val caller = scope.currentClassCtx
|
|
||||||
|
|
||||||
// Fast path for public members when outside any class context
|
|
||||||
if (caller == null) {
|
|
||||||
objClass.publicMemberResolution[name]?.let { key ->
|
|
||||||
instanceScope.objects[key]?.let { rec ->
|
|
||||||
if (rec.visibility == Visibility.Public && !rec.isAbstract) {
|
|
||||||
val decl = rec.declaringClass
|
|
||||||
if (rec.type == ObjRecord.Type.Property) {
|
|
||||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
|
||||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
|
||||||
return rec.value.invoke(instanceScope, this, args, decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0. Prefer private member of current class context
|
// 0. Prefer private member of current class context
|
||||||
caller?.let { c ->
|
scope.currentClassCtx?.let { caller ->
|
||||||
val mangled = c.mangledName(name)
|
val mangled = "${caller.className}::$name"
|
||||||
instanceScope.objects[mangled]?.let { rec ->
|
instanceScope.objects[mangled]?.let { rec ->
|
||||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||||
if (rec.type == ObjRecord.Type.Property) {
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
|
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
|
||||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||||
return rec.value.invoke(instanceScope, this, args, c)
|
return rec.value.invoke(instanceScope, this, args, caller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.members[name]?.let { rec ->
|
caller.members[name]?.let { rec ->
|
||||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||||
if (rec.type == ObjRecord.Type.Property) {
|
if (rec.type == ObjRecord.Type.Property) {
|
||||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
|
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
|
||||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||||
return rec.value.invoke(instanceScope, this, args, c)
|
return rec.value.invoke(instanceScope, this, args, caller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,7 +220,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||||
if (rec != null && !rec.isAbstract) {
|
if (rec != null && !rec.isAbstract) {
|
||||||
if (rec.type == ObjRecord.Type.Delegated) {
|
if (rec.type == ObjRecord.Type.Delegated) {
|
||||||
val storageName = cls.mangledName(name)
|
val storageName = "${cls.className}::$name"
|
||||||
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
||||||
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
|
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
|
||||||
|
|
||||||
@ -281,8 +233,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
val decl = rec.declaringClass ?: cls
|
val decl = rec.declaringClass ?: cls
|
||||||
val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
|
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
||||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller))
|
if (!canAccessMember(rec.visibility, decl, caller))
|
||||||
scope.raiseError(
|
scope.raiseError(
|
||||||
ObjIllegalAccessException(
|
ObjIllegalAccessException(
|
||||||
scope,
|
scope,
|
||||||
|
|||||||
@ -114,39 +114,11 @@ class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
|||||||
}
|
}
|
||||||
return r.asReadonly
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val v = a.evalValue(scope)
|
|
||||||
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
|
||||||
val rFast: Obj? = when (op) {
|
|
||||||
UnaryOp.NOT -> if (v is ObjBool) if (!v.value) ObjTrue else ObjFalse else null
|
|
||||||
UnaryOp.NEGATE -> when (v) {
|
|
||||||
is ObjInt -> ObjInt(-v.value)
|
|
||||||
is ObjReal -> ObjReal(-v.value)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
UnaryOp.BITNOT -> if (v is ObjInt) ObjInt(v.value.inv()) else null
|
|
||||||
}
|
|
||||||
if (rFast != null) {
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
|
||||||
return rFast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return when (op) {
|
|
||||||
UnaryOp.NOT -> v.logicalNot(scope)
|
|
||||||
UnaryOp.NEGATE -> v.negate(scope)
|
|
||||||
UnaryOp.BITNOT -> v.bitNot(scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** R-value reference for binary operations. */
|
/** R-value reference for binary operations. */
|
||||||
class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val a = left.evalValue(scope)
|
val a = left.evalValue(scope)
|
||||||
val b = right.evalValue(scope)
|
val b = right.evalValue(scope)
|
||||||
|
|
||||||
@ -163,7 +135,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
}
|
}
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return r
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fast integer ops when both operands are ObjInt
|
// Fast integer ops when both operands are ObjInt
|
||||||
@ -191,7 +163,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
}
|
}
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return r
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fast string operations when both are strings
|
// Fast string operations when both are strings
|
||||||
@ -208,7 +180,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
}
|
}
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return r
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fast char vs char comparisons
|
// Fast char vs char comparisons
|
||||||
@ -226,7 +198,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
}
|
}
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return r
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fast concatenation for String with Int/Char on either side
|
// Fast concatenation for String with Int/Char on either side
|
||||||
@ -234,19 +206,19 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
when {
|
when {
|
||||||
a is ObjString && b is ObjInt -> {
|
a is ObjString && b is ObjInt -> {
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return ObjString(a.value + b.value.toString())
|
return ObjString(a.value + b.value.toString()).asReadonly
|
||||||
}
|
}
|
||||||
a is ObjString && b is ObjChar -> {
|
a is ObjString && b is ObjChar -> {
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return ObjString(a.value + b.value)
|
return ObjString(a.value + b.value).asReadonly
|
||||||
}
|
}
|
||||||
b is ObjString && a is ObjInt -> {
|
b is ObjString && a is ObjInt -> {
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return ObjString(a.value.toString() + b.value)
|
return ObjString(a.value.toString() + b.value).asReadonly
|
||||||
}
|
}
|
||||||
b is ObjString && a is ObjChar -> {
|
b is ObjString && a is ObjChar -> {
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return ObjString(a.value.toString() + b.value)
|
return ObjString(a.value.toString() + b.value).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,7 +242,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
}
|
}
|
||||||
if (rNum != null) {
|
if (rNum != null) {
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++
|
||||||
return rNum
|
return rNum.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,7 +277,7 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
BinOp.SLASH -> a.div(scope, b)
|
BinOp.SLASH -> a.div(scope, b)
|
||||||
BinOp.PERCENT -> a.mod(scope, b)
|
BinOp.PERCENT -> a.mod(scope, b)
|
||||||
}
|
}
|
||||||
return r
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,21 +288,14 @@ class ConditionalRef(
|
|||||||
private val ifFalse: ObjRef
|
private val ifFalse: ObjRef
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalCondition(scope).get(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
return evalCondition(scope).evalValue(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun evalCondition(scope: Scope): ObjRef {
|
|
||||||
val condVal = condition.evalValue(scope)
|
val condVal = condition.evalValue(scope)
|
||||||
val condTrue = when (condVal) {
|
val condTrue = when (condVal) {
|
||||||
is ObjBool -> condVal.value
|
is ObjBool -> condVal.value
|
||||||
is ObjInt -> condVal.value != 0L
|
is ObjInt -> condVal.value != 0L
|
||||||
else -> condVal.toBool()
|
else -> condVal.toBool()
|
||||||
}
|
}
|
||||||
return if (condTrue) ifTrue else ifFalse
|
val branch = if (condTrue) ifTrue else ifFalse
|
||||||
|
return branch.get(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +309,7 @@ class CastRef(
|
|||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val v0 = valueRef.evalValue(scope)
|
val v0 = valueRef.evalValue(scope)
|
||||||
val t = typeRef.evalValue(scope)
|
val t = typeRef.evalValue(scope)
|
||||||
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${t} is not the class instance")
|
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${'$'}t is not the class instance")
|
||||||
// unwrap qualified views
|
// unwrap qualified views
|
||||||
val v = when (v0) {
|
val v = when (v0) {
|
||||||
is ObjQualifiedView -> v0.instance
|
is ObjQualifiedView -> v0.instance
|
||||||
@ -355,26 +320,7 @@ class CastRef(
|
|||||||
if (v is ObjInstance) ObjQualifiedView(v, target).asReadonly else v.asReadonly
|
if (v is ObjInstance) ObjQualifiedView(v, target).asReadonly else v.asReadonly
|
||||||
} else {
|
} else {
|
||||||
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
|
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
|
||||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${target.className}"
|
"Cannot cast ${'$'}{(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${'$'}{target.className}"
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val v0 = valueRef.evalValue(scope)
|
|
||||||
val t = typeRef.evalValue(scope)
|
|
||||||
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${t} is not the class instance")
|
|
||||||
// unwrap qualified views
|
|
||||||
val v = when (v0) {
|
|
||||||
is ObjQualifiedView -> v0.instance
|
|
||||||
else -> v0
|
|
||||||
}
|
|
||||||
return if (v.isInstanceOf(target)) {
|
|
||||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
|
||||||
if (v is ObjInstance) ObjQualifiedView(v, target) else v
|
|
||||||
} else {
|
|
||||||
if (isNullable) ObjNull else scope.raiseClassCastError(
|
|
||||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${target.className}"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,38 +414,30 @@ class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
|||||||
/** Logical OR with short-circuit: a || b */
|
/** Logical OR with short-circuit: a || b */
|
||||||
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val a = left.evalValue(scope)
|
val a = left.evalValue(scope)
|
||||||
if ((a as? ObjBool)?.value == true) return ObjTrue
|
if ((a as? ObjBool)?.value == true) return ObjTrue.asReadonly
|
||||||
val b = right.evalValue(scope)
|
val b = right.evalValue(scope)
|
||||||
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||||
if (a is ObjBool && b is ObjBool) {
|
if (a is ObjBool && b is ObjBool) {
|
||||||
return if (a.value || b.value) ObjTrue else ObjFalse
|
return if (a.value || b.value) ObjTrue.asReadonly else ObjFalse.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return a.logicalOr(scope, b)
|
return a.logicalOr(scope, b).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Logical AND with short-circuit: a && b */
|
/** Logical AND with short-circuit: a && b */
|
||||||
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val a = left.evalValue(scope)
|
val a = left.evalValue(scope)
|
||||||
if ((a as? ObjBool)?.value == false) return ObjFalse
|
if ((a as? ObjBool)?.value == false) return ObjFalse.asReadonly
|
||||||
val b = right.evalValue(scope)
|
val b = right.evalValue(scope)
|
||||||
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||||
if (a is ObjBool && b is ObjBool) {
|
if (a is ObjBool && b is ObjBool) {
|
||||||
return if (a.value && b.value) ObjTrue else ObjFalse
|
return if (a.value && b.value) ObjTrue.asReadonly else ObjFalse.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return a.logicalAnd(scope, b)
|
return a.logicalAnd(scope, b).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,7 +446,6 @@ class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRe
|
|||||||
*/
|
*/
|
||||||
class ConstRef(private val record: ObjRecord) : ObjRef {
|
class ConstRef(private val record: ObjRecord) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord = record
|
override suspend fun get(scope: Scope): ObjRecord = record
|
||||||
override suspend fun evalValue(scope: Scope): Obj = record.value
|
|
||||||
// Expose constant value for compiler constant folding (pure, read-only)
|
// Expose constant value for compiler constant folding (pure, read-only)
|
||||||
val constValue: Obj get() = record.value
|
val constValue: Obj get() = record.value
|
||||||
}
|
}
|
||||||
@ -587,14 +524,7 @@ class FieldRef(
|
|||||||
val base = target.evalValue(scope)
|
val base = target.evalValue(scope)
|
||||||
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
||||||
if (fieldPic) {
|
if (fieldPic) {
|
||||||
val key: Long
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
val ver: Int
|
|
||||||
when (base) {
|
|
||||||
is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion }
|
|
||||||
is ObjClass -> { key = base.classId; ver = base.layoutVersion }
|
|
||||||
else -> { key = 0L; ver = -1 }
|
|
||||||
}
|
|
||||||
if (key != 0L) {
|
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
if (picCounters) PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
noteReadHit()
|
noteReadHit()
|
||||||
@ -685,21 +615,6 @@ class FieldRef(
|
|||||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ObjInstance -> {
|
|
||||||
val cls = base.objClass
|
|
||||||
val effectiveKey = cls.publicMemberResolution[name]
|
|
||||||
if (effectiveKey != null) {
|
|
||||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc ->
|
|
||||||
if (obj is ObjInstance && obj.objClass === cls) {
|
|
||||||
val rec = obj.instanceScope.objects[effectiveKey]
|
|
||||||
if (rec != null && rec.type != ObjRecord.Type.Delegated) rec
|
|
||||||
else obj.readField(sc, name)
|
|
||||||
} else obj.readField(sc, name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
else -> {
|
||||||
// For instances and other types, fall back to name-based lookup per access (slot index may differ per instance)
|
// For instances and other types, fall back to name-based lookup per access (slot index may differ per instance)
|
||||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||||
@ -707,7 +622,6 @@ class FieldRef(
|
|||||||
}
|
}
|
||||||
return rec
|
return rec
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return base.readField(scope, name)
|
return base.readField(scope, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -721,15 +635,9 @@ class FieldRef(
|
|||||||
}
|
}
|
||||||
// Read→write micro fast-path: reuse transient record captured by get()
|
// Read→write micro fast-path: reuse transient record captured by get()
|
||||||
if (fieldPic) {
|
if (fieldPic) {
|
||||||
val key: Long
|
val (k, v) = receiverKeyAndVersion(base)
|
||||||
val ver: Int
|
|
||||||
when (base) {
|
|
||||||
is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion }
|
|
||||||
is ObjClass -> { key = base.classId; ver = base.layoutVersion }
|
|
||||||
else -> { key = 0L; ver = -1 }
|
|
||||||
}
|
|
||||||
val rec = tRecord
|
val rec = tRecord
|
||||||
if (rec != null && tKey == key && tVer == ver && tFrameId == scope.frameId) {
|
if (rec != null && tKey == k && tVer == v && tFrameId == scope.frameId) {
|
||||||
// If it is a property, we must go through writeField (slow path for now)
|
// If it is a property, we must go through writeField (slow path for now)
|
||||||
// or handle it here.
|
// or handle it here.
|
||||||
if (rec.type != ObjRecord.Type.Property) {
|
if (rec.type != ObjRecord.Type.Property) {
|
||||||
@ -741,7 +649,9 @@ class FieldRef(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key != 0L) {
|
}
|
||||||
|
if (fieldPic) {
|
||||||
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) {
|
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) {
|
||||||
if (picCounters) PerfStats.fieldPicSetHit++
|
if (picCounters) PerfStats.fieldPicSetHit++
|
||||||
noteWriteHit()
|
noteWriteHit()
|
||||||
@ -803,22 +713,6 @@ class FieldRef(
|
|||||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ObjInstance -> {
|
|
||||||
val cls = base.objClass
|
|
||||||
val effectiveKey = cls.publicMemberResolution[name]
|
|
||||||
if (effectiveKey != null) {
|
|
||||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, nv ->
|
|
||||||
if (obj is ObjInstance && obj.objClass === cls) {
|
|
||||||
val rec = obj.instanceScope.objects[effectiveKey]
|
|
||||||
if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && rec.type == ObjRecord.Type.Field) {
|
|
||||||
if (rec.value.assign(sc, nv) == null) rec.value = nv
|
|
||||||
} else obj.writeField(sc, name, nv)
|
|
||||||
} else obj.writeField(sc, name, nv)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
else -> {
|
||||||
// For instances and other types, fall back to generic write (instance slot indices may differ per instance)
|
// For instances and other types, fall back to generic write (instance slot indices may differ per instance)
|
||||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||||
@ -826,7 +720,6 @@ class FieldRef(
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
base.writeField(scope, name, newValue)
|
base.writeField(scope, name, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -843,14 +736,7 @@ class FieldRef(
|
|||||||
val base = target.evalValue(scope)
|
val base = target.evalValue(scope)
|
||||||
if (base == ObjNull && isOptional) return ObjNull
|
if (base == ObjNull && isOptional) return ObjNull
|
||||||
if (fieldPic) {
|
if (fieldPic) {
|
||||||
val key: Long
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
val ver: Int
|
|
||||||
when (base) {
|
|
||||||
is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion }
|
|
||||||
is ObjClass -> { key = base.classId; ver = base.layoutVersion }
|
|
||||||
else -> { key = 0L; ver = -1 }
|
|
||||||
}
|
|
||||||
if (key != 0L) {
|
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
if (picCounters) PerfStats.fieldPicHit++
|
if (picCounters) PerfStats.fieldPicHit++
|
||||||
return g(base, scope).value
|
return g(base, scope).value
|
||||||
@ -882,9 +768,19 @@ class FieldRef(
|
|||||||
if (picCounters) PerfStats.fieldPicMiss++
|
if (picCounters) PerfStats.fieldPicMiss++
|
||||||
val rec = base.readField(scope, name)
|
val rec = base.readField(scope, name)
|
||||||
// install primary generic getter for this shape
|
// install primary generic getter for this shape
|
||||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
when (base) {
|
||||||
return rec.value
|
is ObjClass -> {
|
||||||
|
rKey1 = base.classId; rVer1 = base.layoutVersion; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||||
}
|
}
|
||||||
|
is ObjInstance -> {
|
||||||
|
val cls = base.objClass
|
||||||
|
rKey1 = cls.classId; rVer1 = cls.layoutVersion; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
rKey1 = 0L; rVer1 = -1; rGetter1 = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rec.value
|
||||||
}
|
}
|
||||||
return base.readField(scope, name).value
|
return base.readField(scope, name).value
|
||||||
}
|
}
|
||||||
@ -919,7 +815,6 @@ class IndexRef(
|
|||||||
val base = target.evalValue(scope)
|
val base = target.evalValue(scope)
|
||||||
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
||||||
val idx = index.evalValue(scope)
|
val idx = index.evalValue(scope)
|
||||||
val picCounters = PerfFlags.PIC_DEBUG_COUNTERS
|
|
||||||
if (PerfFlags.RVAL_FASTPATH) {
|
if (PerfFlags.RVAL_FASTPATH) {
|
||||||
// Primitive list index fast path: avoid virtual dispatch to getAt when shapes match
|
// Primitive list index fast path: avoid virtual dispatch to getAt when shapes match
|
||||||
if (base is ObjList && idx is ObjInt) {
|
if (base is ObjList && idx is ObjInt) {
|
||||||
@ -939,27 +834,25 @@ class IndexRef(
|
|||||||
}
|
}
|
||||||
if (PerfFlags.INDEX_PIC) {
|
if (PerfFlags.INDEX_PIC) {
|
||||||
// Polymorphic inline cache for other common shapes
|
// Polymorphic inline cache for other common shapes
|
||||||
val key: Long
|
val (key, ver) = when (base) {
|
||||||
val ver: Int
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
when (base) {
|
is ObjClass -> base.classId to base.layoutVersion
|
||||||
is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion }
|
else -> 0L to -1
|
||||||
is ObjClass -> { key = base.classId; ver = base.layoutVersion }
|
|
||||||
else -> { key = 0L; ver = -1 }
|
|
||||||
}
|
}
|
||||||
if (key != 0L) {
|
if (key != 0L) {
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
if (picCounters) PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
if (picCounters) PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
if (picCounters) PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
@ -967,7 +860,7 @@ class IndexRef(
|
|||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
if (picCounters) PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
@ -976,7 +869,7 @@ class IndexRef(
|
|||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
// Miss: resolve and install generic handler
|
// Miss: resolve and install generic handler
|
||||||
if (picCounters) PerfStats.indexPicMiss++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicMiss++
|
||||||
val v = base.getAt(scope, idx)
|
val v = base.getAt(scope, idx)
|
||||||
if (PerfFlags.INDEX_PIC_SIZE_4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) {
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
@ -995,7 +888,6 @@ class IndexRef(
|
|||||||
val base = target.evalValue(scope)
|
val base = target.evalValue(scope)
|
||||||
if (base == ObjNull && isOptional) return ObjNull
|
if (base == ObjNull && isOptional) return ObjNull
|
||||||
val idx = index.evalValue(scope)
|
val idx = index.evalValue(scope)
|
||||||
val picCounters = PerfFlags.PIC_DEBUG_COUNTERS
|
|
||||||
if (PerfFlags.RVAL_FASTPATH) {
|
if (PerfFlags.RVAL_FASTPATH) {
|
||||||
// Fast list[int] path
|
// Fast list[int] path
|
||||||
if (base is ObjList && idx is ObjInt) {
|
if (base is ObjList && idx is ObjInt) {
|
||||||
@ -1013,27 +905,25 @@ class IndexRef(
|
|||||||
}
|
}
|
||||||
if (PerfFlags.INDEX_PIC) {
|
if (PerfFlags.INDEX_PIC) {
|
||||||
// PIC path analogous to get(), but returning raw Obj
|
// PIC path analogous to get(), but returning raw Obj
|
||||||
val key: Long
|
val (key, ver) = when (base) {
|
||||||
val ver: Int
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
when (base) {
|
is ObjClass -> base.classId to base.layoutVersion
|
||||||
is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion }
|
else -> 0L to -1
|
||||||
is ObjClass -> { key = base.classId; ver = base.layoutVersion }
|
|
||||||
else -> { key = 0L; ver = -1 }
|
|
||||||
}
|
}
|
||||||
if (key != 0L) {
|
if (key != 0L) {
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
if (picCounters) PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
return g(base, scope, idx)
|
return g(base, scope, idx)
|
||||||
} }
|
} }
|
||||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
if (picCounters) PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx)
|
return g(base, scope, idx)
|
||||||
} }
|
} }
|
||||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
if (picCounters) PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
@ -1041,7 +931,7 @@ class IndexRef(
|
|||||||
return g(base, scope, idx)
|
return g(base, scope, idx)
|
||||||
} }
|
} }
|
||||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
if (picCounters) PerfStats.indexPicHit++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicHit++
|
||||||
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
@ -1049,7 +939,7 @@ class IndexRef(
|
|||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx)
|
return g(base, scope, idx)
|
||||||
} }
|
} }
|
||||||
if (picCounters) PerfStats.indexPicMiss++
|
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.indexPicMiss++
|
||||||
val v = base.getAt(scope, idx)
|
val v = base.getAt(scope, idx)
|
||||||
if (PerfFlags.INDEX_PIC_SIZE_4) {
|
if (PerfFlags.INDEX_PIC_SIZE_4) {
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
@ -1085,12 +975,10 @@ class IndexRef(
|
|||||||
}
|
}
|
||||||
if (PerfFlags.RVAL_FASTPATH && PerfFlags.INDEX_PIC) {
|
if (PerfFlags.RVAL_FASTPATH && PerfFlags.INDEX_PIC) {
|
||||||
// Polymorphic inline cache for index write
|
// Polymorphic inline cache for index write
|
||||||
val key: Long
|
val (key, ver) = when (base) {
|
||||||
val ver: Int
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
when (base) {
|
is ObjClass -> base.classId to base.layoutVersion
|
||||||
is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion }
|
else -> 0L to -1
|
||||||
is ObjClass -> { key = base.classId; ver = base.layoutVersion }
|
|
||||||
else -> { key = 0L; ver = -1 }
|
|
||||||
}
|
}
|
||||||
if (key != 0L) {
|
if (key != 0L) {
|
||||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s(base, scope, idx, newValue); return } }
|
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s(base, scope, idx, newValue); return } }
|
||||||
@ -1242,54 +1130,19 @@ class MethodCallRef(
|
|||||||
val base = receiver.evalValue(scope)
|
val base = receiver.evalValue(scope)
|
||||||
if (base == ObjNull && isOptional) return ObjNull.asReadonly
|
if (base == ObjNull && isOptional) return ObjNull.asReadonly
|
||||||
val callArgs = args.toArguments(scope, tailBlock)
|
val callArgs = args.toArguments(scope, tailBlock)
|
||||||
return performInvoke(scope, base, callArgs, methodPic, picCounters).asReadonly
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val methodPic = PerfFlags.METHOD_PIC
|
|
||||||
val picCounters = PerfFlags.PIC_DEBUG_COUNTERS
|
|
||||||
val base = receiver.evalValue(scope)
|
|
||||||
if (base == ObjNull && isOptional) return ObjNull
|
|
||||||
val callArgs = args.toArguments(scope, tailBlock)
|
|
||||||
return performInvoke(scope, base, callArgs, methodPic, picCounters)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun performInvoke(
|
|
||||||
scope: Scope,
|
|
||||||
base: Obj,
|
|
||||||
callArgs: Arguments,
|
|
||||||
methodPic: Boolean,
|
|
||||||
picCounters: Boolean
|
|
||||||
): Obj {
|
|
||||||
if (methodPic) {
|
if (methodPic) {
|
||||||
val key: Long
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
val ver: Int
|
mInvoker1?.let { inv -> if (key == mKey1 && ver == mVer1) {
|
||||||
when (base) {
|
|
||||||
is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion }
|
|
||||||
is ObjClass -> { key = base.classId; ver = base.layoutVersion }
|
|
||||||
else -> { key = 0L; ver = -1 }
|
|
||||||
}
|
|
||||||
if (key != 0L) {
|
|
||||||
mInvoker1?.let { inv ->
|
|
||||||
if (key == mKey1 && ver == mVer1) {
|
|
||||||
if (picCounters) PerfStats.methodPicHit++
|
if (picCounters) PerfStats.methodPicHit++
|
||||||
noteMethodHit()
|
noteMethodHit()
|
||||||
return inv(base, scope, callArgs)
|
return inv(base, scope, callArgs).asReadonly
|
||||||
}
|
} }
|
||||||
}
|
mInvoker2?.let { inv -> if (key == mKey2 && ver == mVer2) {
|
||||||
mInvoker2?.let { inv ->
|
|
||||||
if (key == mKey2 && ver == mVer2) {
|
|
||||||
if (picCounters) PerfStats.methodPicHit++
|
if (picCounters) PerfStats.methodPicHit++
|
||||||
noteMethodHit()
|
noteMethodHit()
|
||||||
// move-to-front: promote 2→1
|
return inv(base, scope, callArgs).asReadonly
|
||||||
val tK = mKey2; val tV = mVer2; val tI = mInvoker2
|
} }
|
||||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
if (size4MethodsEnabled()) mInvoker3?.let { inv -> if (key == mKey3 && ver == mVer3) {
|
||||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
|
||||||
return inv(base, scope, callArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (size4MethodsEnabled()) mInvoker3?.let { inv ->
|
|
||||||
if (key == mKey3 && ver == mVer3) {
|
|
||||||
if (picCounters) PerfStats.methodPicHit++
|
if (picCounters) PerfStats.methodPicHit++
|
||||||
noteMethodHit()
|
noteMethodHit()
|
||||||
// move-to-front: promote 3→1
|
// move-to-front: promote 3→1
|
||||||
@ -1297,11 +1150,9 @@ class MethodCallRef(
|
|||||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
||||||
return inv(base, scope, callArgs)
|
return inv(base, scope, callArgs).asReadonly
|
||||||
}
|
} }
|
||||||
}
|
if (size4MethodsEnabled()) mInvoker4?.let { inv -> if (key == mKey4 && ver == mVer4) {
|
||||||
if (size4MethodsEnabled()) mInvoker4?.let { inv ->
|
|
||||||
if (key == mKey4 && ver == mVer4) {
|
|
||||||
if (picCounters) PerfStats.methodPicHit++
|
if (picCounters) PerfStats.methodPicHit++
|
||||||
noteMethodHit()
|
noteMethodHit()
|
||||||
// move-to-front: promote 4→1
|
// move-to-front: promote 4→1
|
||||||
@ -1310,9 +1161,8 @@ class MethodCallRef(
|
|||||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
||||||
return inv(base, scope, callArgs)
|
return inv(base, scope, callArgs).asReadonly
|
||||||
}
|
} }
|
||||||
}
|
|
||||||
// Slow path
|
// Slow path
|
||||||
if (picCounters) PerfStats.methodPicMiss++
|
if (picCounters) PerfStats.methodPicMiss++
|
||||||
noteMethodMiss()
|
noteMethodMiss()
|
||||||
@ -1337,16 +1187,6 @@ class MethodCallRef(
|
|||||||
// Prefer resolved class member to avoid per-call lookup on hit
|
// Prefer resolved class member to avoid per-call lookup on hit
|
||||||
// BUT only if it's NOT a root object member (which can be shadowed by extensions)
|
// BUT only if it's NOT a root object member (which can be shadowed by extensions)
|
||||||
var hierarchyMember: ObjRecord? = null
|
var hierarchyMember: ObjRecord? = null
|
||||||
val cls0 = base.objClass
|
|
||||||
val keyInScope = cls0.publicMemberResolution[name]
|
|
||||||
if (keyInScope != null) {
|
|
||||||
val rec = base.instanceScope.objects[keyInScope]
|
|
||||||
if (rec != null && rec.type == ObjRecord.Type.Fun) {
|
|
||||||
hierarchyMember = rec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hierarchyMember == null) {
|
|
||||||
for (cls in base.objClass.mro) {
|
for (cls in base.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)
|
||||||
@ -1355,15 +1195,13 @@ class MethodCallRef(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (hierarchyMember != null) {
|
if (hierarchyMember != null) {
|
||||||
val visibility = hierarchyMember.visibility
|
val visibility = hierarchyMember.visibility
|
||||||
val callable = hierarchyMember.value
|
val callable = hierarchyMember.value
|
||||||
val decl = hierarchyMember.declaringClass ?: base.objClass
|
|
||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
||||||
val inst = obj as ObjInstance
|
val inst = obj as ObjInstance
|
||||||
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx))
|
if (!visibility.isPublic && !canAccessMember(visibility, hierarchyMember.declaringClass ?: inst.objClass, sc.currentClassCtx))
|
||||||
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
|
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
|
||||||
callable.invoke(inst.instanceScope, inst, a)
|
callable.invoke(inst.instanceScope, inst, a)
|
||||||
}
|
}
|
||||||
@ -1386,10 +1224,10 @@ class MethodCallRef(
|
|||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result.asReadonly
|
||||||
}
|
}
|
||||||
}
|
val result = base.invokeInstanceMethod(scope, name, callArgs)
|
||||||
return base.invokeInstanceMethod(scope, name, callArgs)
|
return result.asReadonly
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun receiverKeyAndVersion(obj: Obj): Pair<Long, Int> = when (obj) {
|
private fun receiverKeyAndVersion(obj: Obj): Pair<Long, Int> = when (obj) {
|
||||||
@ -1749,17 +1587,14 @@ class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asMutable
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
// Heuristic capacity hint: count element entries; spreads handled opportunistically
|
// Heuristic capacity hint: count element entries; spreads handled opportunistically
|
||||||
val elemCount = entries.count { it is ListEntry.Element }
|
val elemCount = entries.count { it is ListEntry.Element }
|
||||||
val list = ArrayList<Obj>(elemCount)
|
val list = ArrayList<Obj>(elemCount)
|
||||||
for (e in entries) {
|
for (e in entries) {
|
||||||
when (e) {
|
when (e) {
|
||||||
is ListEntry.Element -> {
|
is ListEntry.Element -> {
|
||||||
list += e.ref.evalValue(scope)
|
val v = if (PerfFlags.RVAL_FASTPATH) e.ref.evalValue(scope) else e.ref.evalValue(scope)
|
||||||
|
list += v
|
||||||
}
|
}
|
||||||
is ListEntry.Spread -> {
|
is ListEntry.Spread -> {
|
||||||
val elements = e.ref.evalValue(scope)
|
val elements = e.ref.evalValue(scope)
|
||||||
@ -1774,7 +1609,7 @@ class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ObjList(list)
|
return ObjList(list).asMutable
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
@ -1834,10 +1669,6 @@ sealed class MapLiteralEntry {
|
|||||||
|
|
||||||
class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
|
class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val result = ObjMap(mutableMapOf())
|
val result = ObjMap(mutableMapOf())
|
||||||
for (e in entries) {
|
for (e in entries) {
|
||||||
when (e) {
|
when (e) {
|
||||||
@ -1854,7 +1685,7 @@ class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1867,13 +1698,9 @@ class RangeRef(
|
|||||||
private val isEndInclusive: Boolean
|
private val isEndInclusive: Boolean
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val l = left?.evalValue(scope) ?: ObjNull
|
val l = left?.evalValue(scope) ?: ObjNull
|
||||||
val r = right?.evalValue(scope) ?: ObjNull
|
val r = right?.evalValue(scope) ?: ObjNull
|
||||||
return ObjRange(l, r, isEndInclusive = isEndInclusive)
|
return ObjRange(l, r, isEndInclusive = isEndInclusive).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1884,15 +1711,11 @@ class AssignIfNullRef(
|
|||||||
private val atPos: Pos,
|
private val atPos: Pos,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val current = target.evalValue(scope)
|
val current = target.evalValue(scope)
|
||||||
if (current != ObjNull) return current
|
if (current != ObjNull) return current.asReadonly
|
||||||
val newValue = value.evalValue(scope)
|
val newValue = value.evalValue(scope)
|
||||||
target.setAt(atPos, scope, newValue)
|
target.setAt(atPos, scope, newValue)
|
||||||
return newValue
|
return newValue.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1903,10 +1726,6 @@ class AssignRef(
|
|||||||
private val atPos: Pos,
|
private val atPos: Pos,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalValue(scope).asReadonly
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun evalValue(scope: Scope): Obj {
|
|
||||||
val v = value.evalValue(scope)
|
val v = value.evalValue(scope)
|
||||||
// For properties, we should not call get() on target because it invokes the getter.
|
// For properties, we should not call get() on target because it invokes the getter.
|
||||||
// Instead, we call setAt directly.
|
// Instead, we call setAt directly.
|
||||||
@ -1919,7 +1738,7 @@ class AssignRef(
|
|||||||
target.setAt(atPos, scope, v)
|
target.setAt(atPos, scope, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return v
|
return v.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -419,25 +419,6 @@ class MiniAstTest {
|
|||||||
assertEquals("Int", innerArg.segments.last().name)
|
assertEquals("Int", innerArg.segments.last().name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun verify_class_member_structure() = runTest {
|
|
||||||
val code = """
|
|
||||||
class A {
|
|
||||||
fun member() {}
|
|
||||||
val field = 1
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
val (_, sink) = compileWithMini(code)
|
|
||||||
val mini = sink.build()!!
|
|
||||||
println("[DEBUG_LOG] Mini declarations: ${mini.declarations.map { it.name }}")
|
|
||||||
assertEquals(1, mini.declarations.size, "Should only have one top-level declaration (class A)")
|
|
||||||
val cls = mini.declarations[0] as MiniClassDecl
|
|
||||||
assertEquals("A", cls.name)
|
|
||||||
assertEquals(2, cls.members.size, "Class A should have 2 members (member() and field)")
|
|
||||||
assertTrue(cls.members.any { it.name == "member" && it is MiniMemberFunDecl })
|
|
||||||
assertTrue(cls.members.any { it.name == "field" && it is MiniMemberValDecl })
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun inferTypeForValWithInference() = runTest {
|
fun inferTypeForValWithInference() = runTest {
|
||||||
val code = """
|
val code = """
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,25 +18,25 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
actual object PerfDefaults {
|
actual object PerfDefaults {
|
||||||
actual val LOCAL_SLOT_PIC: Boolean = true
|
actual val LOCAL_SLOT_PIC: Boolean = false
|
||||||
actual val EMIT_FAST_LOCAL_REFS: Boolean = true
|
actual val EMIT_FAST_LOCAL_REFS: Boolean = false
|
||||||
|
|
||||||
actual val ARG_BUILDER: Boolean = true
|
actual val ARG_BUILDER: Boolean = false
|
||||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = false
|
||||||
actual val SCOPE_POOL: Boolean = false
|
actual val SCOPE_POOL: Boolean = false
|
||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = false
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = false
|
||||||
actual val FIELD_PIC_SIZE_4: Boolean = true
|
actual val FIELD_PIC_SIZE_4: Boolean = false
|
||||||
actual val METHOD_PIC_SIZE_4: Boolean = true
|
actual val METHOD_PIC_SIZE_4: Boolean = false
|
||||||
actual val PIC_ADAPTIVE_2_TO_4: Boolean = true
|
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false
|
||||||
actual val PIC_ADAPTIVE_METHODS_ONLY: Boolean = true
|
actual val PIC_ADAPTIVE_METHODS_ONLY: Boolean = false
|
||||||
actual val PIC_ADAPTIVE_HEURISTIC: Boolean = true
|
actual val PIC_ADAPTIVE_HEURISTIC: Boolean = false
|
||||||
|
|
||||||
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
||||||
|
|
||||||
actual val PRIMITIVE_FASTOPS: Boolean = true
|
actual val PRIMITIVE_FASTOPS: Boolean = false
|
||||||
actual val RVAL_FASTPATH: Boolean = true
|
actual val RVAL_FASTPATH: Boolean = false
|
||||||
|
|
||||||
// Regex caching (JVM-first): enabled by default on JVM
|
// Regex caching (JVM-first): enabled by default on JVM
|
||||||
actual val REGEX_CACHE: Boolean = true
|
actual val REGEX_CACHE: Boolean = true
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,6 +18,7 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JVM actual: per-thread scope frame pool backed by ThreadLocal.
|
* JVM actual: per-thread scope frame pool backed by ThreadLocal.
|
||||||
@ -31,31 +32,26 @@ actual object ScopePool {
|
|||||||
|
|
||||||
actual fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope {
|
actual fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope {
|
||||||
val pool = threadLocalPool.get()
|
val pool = threadLocalPool.get()
|
||||||
if (pool.isNotEmpty()) {
|
val s = if (pool.isNotEmpty()) pool.removeLast() else Scope(parent, args, pos, thisObj)
|
||||||
val s = pool.removeLast()
|
return try {
|
||||||
try {
|
// Always reset state on borrow to guarantee fresh-frame semantics
|
||||||
// Re-initialize pooled instance
|
|
||||||
s.resetForReuse(parent, args, pos, thisObj)
|
s.resetForReuse(parent, args, pos, thisObj)
|
||||||
return s
|
s
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
// Defensive fallback: if a cycle in scope parent chain is detected during reuse,
|
// Defensive fallback: if a cycle in scope parent chain is detected during reuse,
|
||||||
// discard pooled instance for this call frame and allocate a fresh scope instead.
|
// discard pooled instance for this call frame and allocate a fresh scope instead.
|
||||||
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
||||||
return Scope(parent, args, pos, thisObj)
|
Scope(parent, args, pos, thisObj)
|
||||||
} else {
|
} else {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Scope(parent, args, pos, thisObj)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun release(scope: Scope) {
|
actual fun release(scope: Scope) {
|
||||||
val pool = threadLocalPool.get()
|
val pool = threadLocalPool.get()
|
||||||
if (pool.size < MAX_POOL_SIZE) {
|
// Scrub sensitive references to avoid accidental retention
|
||||||
// Scrub sensitive references to avoid accidental retention before returning to pool
|
scope.resetForReuse(parent = null, args = Arguments.EMPTY, pos = Pos.builtIn, thisObj = ObjVoid)
|
||||||
scope.scrub()
|
if (pool.size < MAX_POOL_SIZE) pool.addLast(scope)
|
||||||
pool.addLast(scope)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -88,57 +88,20 @@ class PicBenchmarkTest {
|
|||||||
|
|
||||||
// PIC OFF
|
// PIC OFF
|
||||||
PerfFlags.METHOD_PIC = false
|
PerfFlags.METHOD_PIC = false
|
||||||
PerfFlags.SCOPE_POOL = false
|
|
||||||
val scope1 = Scope()
|
val scope1 = Scope()
|
||||||
val t0 = System.nanoTime()
|
val t0 = System.nanoTime()
|
||||||
val r1 = (scope1.eval(script) as ObjInt).value
|
val r1 = (scope1.eval(script) as ObjInt).value
|
||||||
val t1 = System.nanoTime()
|
val t1 = System.nanoTime()
|
||||||
println("[DEBUG_LOG] [BENCH] Method PIC=OFF, POOL=OFF: ${(t1 - t0) / 1_000_000.0} ms")
|
println("[DEBUG_LOG] [BENCH] Method PIC=OFF: ${(t1 - t0) / 1_000_000.0} ms")
|
||||||
assertEquals(iterations.toLong(), r1)
|
assertEquals(iterations.toLong(), r1)
|
||||||
|
|
||||||
// PIC ON
|
// PIC ON
|
||||||
PerfFlags.METHOD_PIC = true
|
PerfFlags.METHOD_PIC = true
|
||||||
PerfFlags.SCOPE_POOL = true
|
|
||||||
val scope2 = Scope()
|
val scope2 = Scope()
|
||||||
val t2 = System.nanoTime()
|
val t2 = System.nanoTime()
|
||||||
val r2 = (scope2.eval(script) as ObjInt).value
|
val r2 = (scope2.eval(script) as ObjInt).value
|
||||||
val t3 = System.nanoTime()
|
val t3 = System.nanoTime()
|
||||||
println("[DEBUG_LOG] [BENCH] Method PIC=ON, POOL=ON: ${(t3 - t2) / 1_000_000.0} ms")
|
println("[DEBUG_LOG] [BENCH] Method PIC=ON: ${(t3 - t2) / 1_000_000.0} ms")
|
||||||
assertEquals(iterations.toLong(), r2)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun benchmarkLoopScopePooling() = runBlocking {
|
|
||||||
val iterations = 500_000
|
|
||||||
val script = """
|
|
||||||
var x = 0
|
|
||||||
var i = 0
|
|
||||||
while(i < $iterations) {
|
|
||||||
if(true) {
|
|
||||||
var y = 1
|
|
||||||
x = x + y
|
|
||||||
}
|
|
||||||
i = i + 1
|
|
||||||
}
|
|
||||||
x
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
// POOL OFF
|
|
||||||
PerfFlags.SCOPE_POOL = false
|
|
||||||
val scope1 = Scope()
|
|
||||||
val t0 = System.nanoTime()
|
|
||||||
val r1 = (scope1.eval(script) as ObjInt).value
|
|
||||||
val t1 = System.nanoTime()
|
|
||||||
println("[DEBUG_LOG] [BENCH] Loop Pool=OFF: ${(t1 - t0) / 1_000_000.0} ms")
|
|
||||||
assertEquals(iterations.toLong(), r1)
|
|
||||||
|
|
||||||
// POOL ON
|
|
||||||
PerfFlags.SCOPE_POOL = true
|
|
||||||
val scope2 = Scope()
|
|
||||||
val t2 = System.nanoTime()
|
|
||||||
val r2 = (scope2.eval(script) as ObjInt).value
|
|
||||||
val t3 = System.nanoTime()
|
|
||||||
println("[DEBUG_LOG] [BENCH] Loop Pool=ON: ${(t3 - t2) / 1_000_000.0} ms")
|
|
||||||
assertEquals(iterations.toLong(), r2)
|
assertEquals(iterations.toLong(), r2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,3 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.miniast
|
package net.sergeych.lyng.miniast
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@ -479,34 +462,6 @@ class CompletionEngineLightTest {
|
|||||||
assertTrue(ns.contains("size"), "String member 'size' should be suggested for local x[0] inside set(), but got: $ns")
|
assertTrue(ns.contains("size"), "String member 'size' should be suggested for local x[0] inside set(), but got: $ns")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun functionArgumentsInBody() = runBlocking {
|
|
||||||
val code = """
|
|
||||||
fun test(myArg1, myArg2) {
|
|
||||||
myA<caret>
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
val items = CompletionEngineLight.completeAtMarkerSuspend(code)
|
|
||||||
val ns = names(items)
|
|
||||||
assertTrue(ns.contains("myArg1"), "Function argument 'myArg1' should be proposed, but got: $ns")
|
|
||||||
assertTrue(ns.contains("myArg2"), "Function argument 'myArg2' should be proposed, but got: $ns")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun methodArgumentsInBody() = runBlocking {
|
|
||||||
val code = """
|
|
||||||
class MyClass {
|
|
||||||
fun test(myArg1, myArg2) {
|
|
||||||
myA<caret>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
val items = CompletionEngineLight.completeAtMarkerSuspend(code)
|
|
||||||
val ns = names(items)
|
|
||||||
assertTrue(ns.contains("myArg1"), "Method argument 'myArg1' should be proposed, but got: $ns")
|
|
||||||
assertTrue(ns.contains("myArg2"), "Method argument 'myArg2' should be proposed, but got: $ns")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun nestedShadowingCompletion() = runBlocking {
|
fun nestedShadowingCompletion() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
|
|||||||
@ -98,7 +98,6 @@ fun applyEnter(text: String, selStart: Int, selEnd: Int, tabSize: Int): EditResu
|
|||||||
val lineStart = lineStartAt(text, start)
|
val lineStart = lineStartAt(text, start)
|
||||||
val lineEnd = lineEndAt(text, start)
|
val lineEnd = lineEndAt(text, start)
|
||||||
val indent = countIndentSpaces(text, lineStart, lineEnd)
|
val indent = countIndentSpaces(text, lineStart, lineEnd)
|
||||||
val lineTrimmed = text.substring(lineStart, lineEnd).trim()
|
|
||||||
|
|
||||||
// Compute neighborhood characters early so rule precedence can use them
|
// Compute neighborhood characters early so rule precedence can use them
|
||||||
val prevIdx = prevNonWs(text, start)
|
val prevIdx = prevNonWs(text, start)
|
||||||
@ -108,7 +107,7 @@ fun applyEnter(text: String, selStart: Int, selEnd: Int, tabSize: Int): EditResu
|
|||||||
val before = text.substring(0, start)
|
val before = text.substring(0, start)
|
||||||
val after = text.substring(start)
|
val after = text.substring(start)
|
||||||
|
|
||||||
// Rule 4: Between braces on the same line {|}
|
// 1) Between braces { | } -> two lines, inner indented
|
||||||
if (prevCh == '{' && nextCh == '}') {
|
if (prevCh == '{' && nextCh == '}') {
|
||||||
val innerIndent = indent + tabSize
|
val innerIndent = indent + tabSize
|
||||||
val insertion = "\n" + " ".repeat(innerIndent) + "\n" + " ".repeat(indent)
|
val insertion = "\n" + " ".repeat(innerIndent) + "\n" + " ".repeat(indent)
|
||||||
@ -116,51 +115,22 @@ fun applyEnter(text: String, selStart: Int, selEnd: Int, tabSize: Int): EditResu
|
|||||||
val caret = start + 1 + innerIndent
|
val caret = start + 1 + innerIndent
|
||||||
return EditResult(out, caret, caret)
|
return EditResult(out, caret, caret)
|
||||||
}
|
}
|
||||||
|
// 2) After '{'
|
||||||
// Rule 2: On a brace-only line '}'
|
|
||||||
if (lineTrimmed == "}") {
|
|
||||||
val newIndent = (indent - tabSize).coerceAtLeast(0)
|
|
||||||
val newCurrentLine = " ".repeat(newIndent) + "}"
|
|
||||||
val insertion = "\n" + " ".repeat(newIndent)
|
|
||||||
val out = safeSubstring(text, 0, lineStart) + newCurrentLine + insertion + safeSubstring(text, lineEnd, text.length)
|
|
||||||
val caret = lineStart + newCurrentLine.length + insertion.length
|
|
||||||
return EditResult(out, caret, caret)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rule 1: After '{'
|
|
||||||
if (prevCh == '{') {
|
if (prevCh == '{') {
|
||||||
val insertion = "\n" + " ".repeat(indent + tabSize)
|
val insertion = "\n" + " ".repeat(indent + tabSize)
|
||||||
val out = before + insertion + after
|
val out = before + insertion + after
|
||||||
val caret = start + insertion.length
|
val caret = start + insertion.length
|
||||||
return EditResult(out, caret, caret)
|
return EditResult(out, caret, caret)
|
||||||
}
|
}
|
||||||
|
// 3) Before '}'
|
||||||
// Rule 3: End of a line before a brace-only next line
|
if (nextCh == '}') {
|
||||||
if (start == lineEnd && lineEnd < text.length) {
|
val insertion = "\n" + " ".repeat(indent)
|
||||||
val nextLineStart = lineEnd + 1
|
|
||||||
val nextLineEnd = lineEndAt(text, nextLineStart)
|
|
||||||
val nextLineTrimmed = text.substring(nextLineStart, nextLineEnd).trim()
|
|
||||||
if (nextLineTrimmed == "}") {
|
|
||||||
val nextLineIndent = countIndentSpaces(text, nextLineStart, nextLineEnd)
|
|
||||||
val newNextLineIndent = (nextLineIndent - tabSize).coerceAtLeast(0)
|
|
||||||
val newNextLine = " ".repeat(newNextLineIndent) + "}"
|
|
||||||
val out = text.substring(0, nextLineStart) + newNextLine + text.substring(nextLineEnd)
|
|
||||||
val caret = nextLineStart + newNextLineIndent
|
|
||||||
return EditResult(out, caret, caret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rule 5: After '}' with only spaces until end-of-line
|
|
||||||
val afterCaretOnLine = text.substring(start, lineEnd)
|
|
||||||
if (prevCh == '}' && afterCaretOnLine.trim().isEmpty()) {
|
|
||||||
val newIndent = (indent - tabSize).coerceAtLeast(0)
|
|
||||||
val insertion = "\n" + " ".repeat(newIndent)
|
|
||||||
val out = before + insertion + after
|
val out = before + insertion + after
|
||||||
val caret = start + insertion.length
|
val caret = start + insertion.length
|
||||||
return EditResult(out, caret, caret)
|
return EditResult(out, caret, caret)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 6: Default smart indent
|
// default keep same indent
|
||||||
val insertion = "\n" + " ".repeat(indent)
|
val insertion = "\n" + " ".repeat(indent)
|
||||||
val out = before + insertion + after
|
val out = before + insertion + after
|
||||||
val caret = start + insertion.length
|
val caret = start + insertion.length
|
||||||
|
|||||||
@ -329,7 +329,7 @@
|
|||||||
<!-- Top-left version ribbon -->
|
<!-- Top-left version ribbon -->
|
||||||
<div class="corner-ribbon bg-danger text-white">
|
<div class="corner-ribbon bg-danger text-white">
|
||||||
<span style="margin-left: -5em">
|
<span style="margin-left: -5em">
|
||||||
v1.2.0-SNAPSHOT
|
v1.1.1-SNAPSHOT
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Fixed top navbar for the whole site -->
|
<!-- Fixed top navbar for the whole site -->
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -41,11 +41,6 @@ class EditorE2ETest {
|
|||||||
// Programmatically type text into the textarea at current selection and dispatch an input event
|
// Programmatically type text into the textarea at current selection and dispatch an input event
|
||||||
private fun typeText(ta: HTMLTextAreaElement, s: String) {
|
private fun typeText(ta: HTMLTextAreaElement, s: String) {
|
||||||
for (ch in s) {
|
for (ch in s) {
|
||||||
val key = ch.toString()
|
|
||||||
val ev = js("new KeyboardEvent('keydown', {key: key, bubbles: true, cancelable: true})")
|
|
||||||
val wasPrevented = !ta.dispatchEvent(ev.unsafeCast<org.w3c.dom.events.Event>())
|
|
||||||
|
|
||||||
if (!wasPrevented) {
|
|
||||||
val start = ta.selectionStart ?: 0
|
val start = ta.selectionStart ?: 0
|
||||||
val end = ta.selectionEnd ?: start
|
val end = ta.selectionEnd ?: start
|
||||||
val before = ta.value.substring(0, start)
|
val before = ta.value.substring(0, start)
|
||||||
@ -55,9 +50,8 @@ class EditorE2ETest {
|
|||||||
ta.selectionStart = newPos
|
ta.selectionStart = newPos
|
||||||
ta.selectionEnd = newPos
|
ta.selectionEnd = newPos
|
||||||
// Fire input so EditorWithOverlay updates its state
|
// Fire input so EditorWithOverlay updates its state
|
||||||
val inputEv = js("new Event('input', {bubbles:true})")
|
val ev = js("new Event('input', {bubbles:true})")
|
||||||
ta.dispatchEvent(inputEv.unsafeCast<org.w3c.dom.events.Event>())
|
ta.dispatchEvent(ev.unsafeCast<org.w3c.dom.events.Event>())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,10 +243,10 @@ class EditorE2ETest {
|
|||||||
typeText(ta, "4"); nextFrame()
|
typeText(ta, "4"); nextFrame()
|
||||||
dispatchKey(ta, key = "Enter"); nextFrame(); nextFrame()
|
dispatchKey(ta, key = "Enter"); nextFrame(); nextFrame()
|
||||||
|
|
||||||
typeText(ta, "}"); nextFrame(); nextFrame(); nextFrame()
|
typeText(ta, "}"); nextFrame()
|
||||||
dispatchKey(ta, key = "Enter"); nextFrame(); nextFrame()
|
dispatchKey(ta, key = "Enter"); nextFrame(); nextFrame()
|
||||||
|
|
||||||
typeText(ta, "5"); nextFrame(); nextFrame(); nextFrame()
|
typeText(ta, "5"); nextFrame(); nextFrame()
|
||||||
|
|
||||||
val expected = (
|
val expected = (
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user