
com.strumenta.kolasu.semantics.scope.description.ScopeDescription.kt Maven / Gradle / Ivy
package com.strumenta.kolasu.semantics.scope.description
import com.strumenta.kolasu.model.Node
import com.strumenta.kolasu.model.PossiblyNamed
import com.strumenta.kolasu.model.ReferenceByName
import com.strumenta.kolasu.semantics.symbol.description.SymbolDescription
/**
* Utility function for defining scope descriptions.
**/
fun scope(
ignoreCase: Boolean = false,
init: ScopeDescriptionApi.() -> Unit
): ScopeDescription = ScopeDescription(ignoreCase).apply(init)
/**
* A scope description containing all relevant information
* concerning the visible elements at a given point in space or time.
* - `ignoreCase`: whether the names associated to the symbols
* in this scope should be handled as case-sensitive or not;
* - `nodes`: the local symbols contained in this scope and
* their associated name;
* - `identifiers`: the external symbols contained in this scope
* and their associated name;
* - `parent`: the scope description *preceding* the current one - used
* as delegate if no symbol can be found for a given name in the current scope;
*
* Each scope description can be configured through the methods defined in
* the `ScopeDescriptionApi`. References can be resolved invoking `resolve(reference)`.
*
* Given a reference, the scope provider will follow the following plan:
* - try to retrieve a local symbol associated with the given reference name;
* - if not found, try to retrieve a global symbol associated with the given reference name;
* - if not found, delegate the resolution to its parent (if any)
*
**/
class ScopeDescription(
private val ignoreCase: Boolean = false
) : ScopeDescriptionApi {
private var parent: ScopeDescription? = null
private val namesToExternalSymbolIdentifiers: MutableMap = mutableMapOf()
private val namesToLocalSymbolNodes: MutableMap = mutableMapOf()
/**
* Resolves the given reference in the current scope (or its parents).
**/
fun resolve(reference: ReferenceByName) {
val name = reference.name.asKey()
val node by lazy { this.namesToLocalSymbolNodes[name] }
val identifier by lazy { this.namesToExternalSymbolIdentifiers[name] }
when {
node != null -> {
@Suppress("UNCHECKED_CAST")
(reference as ReferenceByName).referred = node
}
identifier != null -> {
reference.identifier = identifier
}
else -> {
this.parent?.resolve(reference)
}
}
}
override fun define(
name: String,
symbol: Node
) {
when (symbol) {
is SymbolDescription -> this.namesToExternalSymbolIdentifiers[name.asKey()] = symbol.identifier
is PossiblyNamed -> this.namesToLocalSymbolNodes[name.asKey()] = symbol
}
}
override fun define(symbol: PossiblyNamed) {
if (symbol is Node && symbol.name != null) {
this.define(symbol.name!!, symbol)
} else {
throw RuntimeException("Symbols must be SymbolDescription or Node instances with a non-null name property.")
}
}
override fun parent(parent: ScopeDescription) {
this.parent = parent
}
override fun parent(
ignoreCase: Boolean,
init: ScopeDescriptionApi.() -> Unit
) {
this.parent = scope(ignoreCase, init)
}
/**
* Handle case sensitivity if enabled.
**/
private fun String.asKey(): String {
return if (ignoreCase) this.lowercase() else this
}
}
/**
* Interface for defining scope descriptions. It provides
* the following methods:
* - `define(name, symbol)` - to associate the given local or global symbol with the given name;
* - `parent(parent)` - to update the parent scope from another description;
* - `parent(ignoreCase, init)` - to update the parent scope from a literal description;
**/
interface ScopeDescriptionApi {
/**
* Associates the given symbol with the given name.
**/
fun define(
name: String,
symbol: Node
)
/**
* Associates the given symbol with its name.
*
**/
fun define(symbol: PossiblyNamed)
/**
* Updates the parent of this scope from another description.
**/
fun parent(parent: ScopeDescription)
/**
* Updates the parent of this scope from a literal description.
**/
fun parent(
ignoreCase: Boolean = false,
init: ScopeDescriptionApi.() -> Unit
)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy