Skip to content

Commit

Permalink
Improved symbol resolution and type computation APIs
Browse files Browse the repository at this point in the history
Signed-off-by: Lorenzo Addazi <[email protected]>
  • Loading branch information
loradd committed Oct 13, 2023
1 parent 14dc3b3 commit 0d6e299
Show file tree
Hide file tree
Showing 17 changed files with 564 additions and 469 deletions.
1 change: 1 addition & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ generateTestGrammarSource {
arguments += ['-package', 'com.strumenta.simplelang']
outputDirectory = new File("generated-test-src/antlr/main/com/strumenta/simplelang".toString())
}
runKtlintCheckOverMainSourceSet.dependsOn generateGrammarSource
compileKotlin.dependsOn generateGrammarSource
compileJava.dependsOn generateGrammarSource
compileTestKotlin.dependsOn generateTestGrammarSource
Expand Down
32 changes: 32 additions & 0 deletions core/src/main/kotlin/com/strumenta/kolasu/model/Naming.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.strumenta.kolasu.model

import java.io.Serializable
import kotlin.reflect.*
import kotlin.reflect.full.createType
import kotlin.reflect.full.isSubtypeOf

/**
* An entity that can have a name
Expand Down Expand Up @@ -99,3 +102,32 @@ fun <N> ReferenceByName<N>.tryToResolve(possibleValue: N?): Boolean where N : Po
true
}
}

/**
* Typealias representing reference properties.
**/
typealias KReferenceByName<S> = KProperty1<S, ReferenceByName<out PossiblyNamed>?>

/**
* Builds a type representation for a reference
**/
fun kReferenceByNameType(targetClass: KClass<out PossiblyNamed> = PossiblyNamed::class): KType {
return ReferenceByName::class.createType(
arguments = listOf(KTypeProjection(variance = KVariance.OUT, type = targetClass.createType())),
nullable = true
)
}

/**
* Retrieves the referred type for a given reference property.
**/
@Suppress("unchecked_cast")
fun KReferenceByName<*>.getReferredType(): KClass<out PossiblyNamed> {
return this.returnType.arguments[0].type!!.classifier!! as KClass<out PossiblyNamed>
}

/**
* Retrieves all reference properties for a given node.
**/
fun Node.kReferenceByNameProperties(targetClass: KClass<out PossiblyNamed> = PossiblyNamed::class) =
this.nodeProperties.filter { it.returnType.isSubtypeOf(kReferenceByNameType(targetClass)) }
105 changes: 105 additions & 0 deletions core/src/main/kotlin/com/strumenta/kolasu/semantics/ScopeProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.strumenta.kolasu.semantics

import com.strumenta.kolasu.model.KReferenceByName
import com.strumenta.kolasu.model.Node
import com.strumenta.kolasu.model.PossiblyNamed
import com.strumenta.kolasu.utils.memoize
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf

// instance

class ScopeProvider(
private val scopeResolutionRules: MutableMap<KReferenceByName<out Node>, (Node) -> Scope> = mutableMapOf(),
private val scopeConstructionRules: MutableMap<KClass<out Node>, (Node) -> Scope> = mutableMapOf()
) {
fun loadFrom(configuration: ScopeProviderConfiguration, semantics: Semantics) {
configuration.scopeResolutionRules.mapValuesTo(this.scopeResolutionRules) {
(_, scopeResolutionRule) ->
{ node: Node -> semantics.scopeResolutionRule(node) }
}
configuration.scopeConstructionRules.mapValuesTo(this.scopeConstructionRules) {
(_, scopeConstructionRule) ->
{ node: Node -> semantics.scopeConstructionRule(node) }
}
}

fun scopeFor(referenceByName: KReferenceByName<out Node>, node: Node? = null): Scope {
return node?.let { this.scopeResolutionRules[referenceByName] }?.invoke(node) ?: Scope()
}

fun scopeFrom(node: Node? = null): Scope {
return node?.let {
this.scopeConstructionRules.keys
.filter { it.isSuperclassOf(node::class) }
.sortedWith { left, right ->
when {
left.isSuperclassOf(right) -> 1
right.isSuperclassOf(left) -> -1
else -> 0
}
}.firstOrNull()
}?.let { this.scopeConstructionRules[it] }?.invoke(node) ?: Scope()
}
}

// configuration

class ScopeProviderConfiguration(
val scopeResolutionRules: MutableMap<KReferenceByName<out Node>, Semantics.(Node) -> Scope> = mutableMapOf(),
val scopeConstructionRules: MutableMap<KClass<out Node>, Semantics.(Node) -> Scope> = mutableMapOf()
) {
inline fun <reified N : Node> scopeFor(
referenceByName: KReferenceByName<N>,
crossinline scopeResolutionRule: Semantics.(N) -> Scope
) {
this.scopeResolutionRules.putIfAbsent(
referenceByName,
{ semantics: Semantics, node: Node ->
if (node is N) semantics.scopeResolutionRule(node) else Scope()
}.memoize()
)
}

inline fun <reified N : Node> scopeFrom(
nodeType: KClass<N>,
crossinline scopeConstructionRule: Semantics.(N) -> Scope
) {
this.scopeConstructionRules.putIfAbsent(
nodeType,
{ semantics: Semantics, node: Node ->
if (node is N) semantics.scopeConstructionRule(node) else Scope()
}.memoize()
)
}
}

// builder

fun scopeProvider(init: ScopeProviderConfiguration.() -> Unit) = ScopeProviderConfiguration().apply(init)

// scopes

// TODO handle multiple symbols (e.g. function overloading)
// TODO allow other than name-based symbol binding (e.g. predicated, numbered, etc.)
data class Scope(
var parent: Scope? = null,
val symbolTable: MutableMap<String, MutableList<PossiblyNamed>> = mutableMapOf(),
val ignoreCase: Boolean = false
) {
fun define(symbol: PossiblyNamed) {
this.symbolTable.computeIfAbsent(symbol.name.toSymbolTableKey()) { mutableListOf() }.add(symbol)
}

fun resolve(name: String? = null, type: KClass<out PossiblyNamed> = PossiblyNamed::class): PossiblyNamed? {
val key = name.toSymbolTableKey()
return this.symbolTable.getOrDefault(key, mutableListOf()).find { type.isInstance(it) }
?: this.parent?.resolve(key, type)
}

private fun String?.toSymbolTableKey() = when {
this != null && ignoreCase -> this.lowercase()
this != null -> this
else -> throw IllegalArgumentException("The given symbol must have a name")
}
}
42 changes: 42 additions & 0 deletions core/src/main/kotlin/com/strumenta/kolasu/semantics/Semantics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.strumenta.kolasu.semantics

import com.strumenta.kolasu.validation.Issue

// instance

class Semantics(
issues: MutableList<Issue> = mutableListOf(),
configuration: SemanticsConfiguration
) {
val typeComputer = TypeComputer().apply {
configuration.typeComputer?.let {
this.loadFrom(it, this@Semantics)
}
}
val symbolResolver = SymbolResolver().apply {
configuration.symbolResolver?.let {
this.loadFrom(it, this@Semantics)
}
}
}

// configuration

class SemanticsConfiguration(
var typeComputer: TypeComputerConfiguration? = null,
var symbolResolver: SymbolResolverConfiguration? = null
) {
fun typeComputer(init: TypeComputerConfiguration.() -> Unit) {
this.typeComputer = TypeComputerConfiguration().apply(init)
}
fun symbolResolver(init: SymbolResolverConfiguration.() -> Unit) {
this.symbolResolver = SymbolResolverConfiguration().apply(init)
}
}

// builder

fun semantics(
issues: MutableList<Issue> = mutableListOf(),
init: SemanticsConfiguration.() -> Unit
) = Semantics(issues, SemanticsConfiguration().apply(init))
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.strumenta.kolasu.semantics

import com.strumenta.kolasu.model.*
import com.strumenta.kolasu.traversing.walkChildren
import kotlin.reflect.KClass

// instance

class SymbolResolver(
private val scopeProvider: ScopeProvider = ScopeProvider()
) {
fun loadFrom(configuration: SymbolResolverConfiguration, semantics: Semantics) {
this.scopeProvider.loadFrom(configuration.scopeProvider, semantics)
}

@Suppress("unchecked_cast")
fun resolve(node: Node) {
node.kReferenceByNameProperties().forEach { this.resolve(it as KReferenceByName<out Node>, node) }
node.walkChildren().forEach(this::resolve)
}

@Suppress("unchecked_cast")
fun resolve(property: KReferenceByName<out Node>, node: Node) {
(node.properties.find { it.name == property.name }?.value as ReferenceByName<PossiblyNamed>?)?.apply {
this.referred = scopeProvider.scopeFor(property, node).resolve(this.name, property.getReferredType())
}
}

fun scopeFor(property: KReferenceByName<out Node>, node: Node? = null): Scope {
return this.scopeProvider.scopeFor(property, node)
}

fun scopeFrom(node: Node? = null): Scope {
return this.scopeProvider.scopeFrom(node)
}
}

// configuration

class SymbolResolverConfiguration(
val scopeProvider: ScopeProviderConfiguration = ScopeProviderConfiguration()
) {
inline fun <reified N : Node> scopeFor(
referenceByName: KReferenceByName<N>,
crossinline scopeResolutionRule: Semantics.(N) -> Scope
) = this.scopeProvider.scopeFor(referenceByName, scopeResolutionRule)

inline fun <reified N : Node> scopeFrom(
nodeType: KClass<N>,
crossinline scopeConstructionRule: Semantics.(N) -> Scope
) = this.scopeProvider.scopeFrom(nodeType, scopeConstructionRule)
}

// builder

fun symbolResolver(init: SymbolResolverConfiguration.() -> Unit) = SymbolResolverConfiguration().apply(init)
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.strumenta.kolasu.semantics

import com.strumenta.kolasu.model.Node
import com.strumenta.kolasu.utils.memoize
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf

// instance

class TypeComputer(
private val typingRules: MutableMap<KClass<out Node>, (Node) -> Node?> = mutableMapOf()
) {
fun loadFrom(configuration: TypeComputerConfiguration, semantics: Semantics) {
configuration.typingRules.mapValuesTo(this.typingRules) {
(_, typingRule) ->
{ node: Node -> semantics.typingRule(node) }
}
}

fun typeFor(node: Node? = null): Node? {
return node?.let {
this.typingRules.keys
.filter { it.isSuperclassOf(node::class) }
.sortedWith { left, right ->
when {
left.isSuperclassOf(right) -> 1
right.isSuperclassOf(left) -> -1
else -> 0
}
}.firstOrNull()?.let { this.typingRules[it] }?.invoke(node)
}
}
}

// configuration

class TypeComputerConfiguration(
val typingRules: MutableMap<KClass<out Node>, Semantics.(Node) -> Node?> = mutableMapOf(
Node::class to { it }
)
) {
inline fun <reified N : Node> typeFor(nodeType: KClass<N>, crossinline typingRule: Semantics.(N) -> Node?) {
this.typingRules.putIfAbsent(
nodeType,
{ semantics: Semantics, node: Node ->
if (node is N) semantics.typingRule(node) else null
}.memoize()
)
}
}

// builder

fun typeComputer(init: TypeComputerConfiguration.() -> Unit) = TypeComputerConfiguration().apply(init)
Loading

0 comments on commit 0d6e299

Please sign in to comment.