fixed import inside sources in InlineSourcesPacman
This commit is contained in:
parent
868379ce22
commit
1e2cb5e420
@ -20,6 +20,10 @@ class Compiler(
|
|||||||
// package level declarations
|
// package level declarations
|
||||||
do {
|
do {
|
||||||
val t = cc.current()
|
val t = cc.current()
|
||||||
|
if(t.type == Token.Type.NEWLINE || t.type == Token.Type.SINLGE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
|
||||||
|
cc.next()
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (t.type == Token.Type.ID) {
|
if (t.type == Token.Type.ID) {
|
||||||
when (t.value) {
|
when (t.value) {
|
||||||
"package" -> {
|
"package" -> {
|
||||||
@ -42,6 +46,7 @@ class Compiler(
|
|||||||
pacman.performImport(this,name,null)
|
pacman.performImport(this,name,null)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,6 +69,7 @@ class Compiler(
|
|||||||
t = cc.next()
|
t = cc.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cc.previous()
|
||||||
return result.toString()
|
return result.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1616,13 +1622,6 @@ class Compiler(
|
|||||||
return Compiler(CompilerContext(parseLyng(source)),pacman).parseScript()
|
return Compiler(CompilerContext(parseLyng(source)),pacman).parseScript()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun compilePackage(source: Source): Pair<String, Script> {
|
|
||||||
val c = Compiler(CompilerContext(parseLyng(source)))
|
|
||||||
val script = c.parseScript()
|
|
||||||
if (c.packageName == null) throw ScriptError(source.startPos, "package not set")
|
|
||||||
return c.packageName!! to script
|
|
||||||
}
|
|
||||||
|
|
||||||
private var lastPriority = 0
|
private var lastPriority = 0
|
||||||
val allOps = listOf(
|
val allOps = listOf(
|
||||||
// assignments, lowest priority
|
// assignments, lowest priority
|
||||||
|
@ -164,7 +164,10 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
* Note that [Token.Type.EOF] is not considered a whitespace token.
|
* Note that [Token.Type.EOF] is not considered a whitespace token.
|
||||||
*/
|
*/
|
||||||
fun skipWsTokens(): Token {
|
fun skipWsTokens(): Token {
|
||||||
while( current().type in wstokens ) next()
|
while( current().type in wstokens ) {
|
||||||
|
println("skipws ${current()}")
|
||||||
|
next()
|
||||||
|
}
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import net.sergeych.mp_tools.globalDefer
|
||||||
|
import net.sergeych.mp_tools.globalLaunch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Naive source-based pacman that compiles all sources first, before first import could be resolved.
|
||||||
|
* It supports imports between [sources] but does not resolve nor detect cyclic imports which
|
||||||
|
* are not supported.
|
||||||
|
*/
|
||||||
|
class InlineSourcesPacman(pacman: Pacman, val sources: List<Source>) : Pacman(pacman) {
|
||||||
|
|
||||||
|
data class Entry(val source: Source, val deferredModule: CompletableDeferred<ModuleScope> = CompletableDeferred())
|
||||||
|
|
||||||
|
|
||||||
|
private val modules = run {
|
||||||
|
val result = mutableMapOf<String, Entry>()
|
||||||
|
for (source in sources) {
|
||||||
|
val name = source.extractPackageName()
|
||||||
|
result[name] = Entry(source)
|
||||||
|
}
|
||||||
|
val inner = InitMan()
|
||||||
|
|
||||||
|
for ((name, entry) in result) {
|
||||||
|
globalLaunch {
|
||||||
|
val scope = ModuleScope(inner, entry.source.startPos, name)
|
||||||
|
Compiler.compile(entry.source, inner).execute(scope)
|
||||||
|
entry.deferredModule.complete(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class InitMan : Pacman(parent) {
|
||||||
|
override suspend fun createModuleScope(name: String): ModuleScope? = modules[name]?.deferredModule?.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val readyModules by lazy {
|
||||||
|
globalDefer {
|
||||||
|
modules.entries.map { it.key to it.value.deferredModule.await() }.toMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun createModuleScope(name: String): ModuleScope? =
|
||||||
|
readyModules.await()[name]
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module scope supports importing and contains the [pacman]; it should be the same
|
||||||
|
* used in [Compiler];
|
||||||
|
*/
|
||||||
|
class ModuleScope(
|
||||||
|
val pacman: Pacman,
|
||||||
|
pos: Pos = Pos.builtIn,
|
||||||
|
val packageName: String
|
||||||
|
) : Scope(pacman.rootScope, Arguments.EMPTY, pos) {
|
||||||
|
|
||||||
|
constructor(pacman: Pacman,source: Source) : this(pacman, source.startPos, source.fileName)
|
||||||
|
|
||||||
|
override suspend fun checkImport(pos: Pos, name: String, symbols: Map<String, String>?) {
|
||||||
|
pacman.prepareImport(pos, name, symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import symbols into the scope. It _is called_ after the module is imported by [checkImport].
|
||||||
|
* If [checkImport] was not called, the symbols will not be imported with exception as module is not found.
|
||||||
|
*/
|
||||||
|
override suspend fun importInto(scope: Scope, name: String, symbols: Map<String, String>?) {
|
||||||
|
pacman.performImport(scope, name, symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
val packageNameObj by lazy { ObjString(packageName).asReadonly}
|
||||||
|
|
||||||
|
override fun get(name: String): ObjRecord? {
|
||||||
|
return if( name == "__PACKAGE__")
|
||||||
|
packageNameObj
|
||||||
|
else
|
||||||
|
super.get(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import net.sergeych.mp_tools.globalDefer
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Package manager. Chained manager, too simple. Override [createModuleScope] to return either
|
* Package manager. Chained manager, too simple. Override [createModuleScope] to return either
|
||||||
@ -45,13 +43,11 @@ abstract class Pacman(
|
|||||||
val symbolsToImport = symbols?.keys?.toMutableSet()
|
val symbolsToImport = symbols?.keys?.toMutableSet()
|
||||||
for ((symbol, record) in module.objects) {
|
for ((symbol, record) in module.objects) {
|
||||||
if (record.visibility.isPublic) {
|
if (record.visibility.isPublic) {
|
||||||
println("import $name: $symbol: $record")
|
|
||||||
val newName = symbols?.let { ss: Map<String, String> ->
|
val newName = symbols?.let { ss: Map<String, String> ->
|
||||||
ss[symbol]
|
ss[symbol]
|
||||||
?.also { symbolsToImport!!.remove(it) }
|
?.also { symbolsToImport!!.remove(it) }
|
||||||
?: scope.raiseError("internal error: symbol $symbol not found though the module is cached")
|
?: scope.raiseError("internal error: symbol $symbol not found though the module is cached")
|
||||||
} ?: symbol
|
} ?: symbol
|
||||||
println("import $name.$symbol as $newName")
|
|
||||||
if (newName in scope.objects)
|
if (newName in scope.objects)
|
||||||
scope.raiseError("symbol $newName already exists, redefinition on import is not allowed")
|
scope.raiseError("symbol $newName already exists, redefinition on import is not allowed")
|
||||||
scope.objects[newName] = record
|
scope.objects[newName] = record
|
||||||
@ -70,61 +66,4 @@ abstract class Pacman(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Module scope supports importing and contains the [pacman]; it should be the same
|
|
||||||
* used in [Compiler];
|
|
||||||
*/
|
|
||||||
class ModuleScope(
|
|
||||||
val pacman: Pacman,
|
|
||||||
pos: Pos = Pos.builtIn,
|
|
||||||
val packageName: String
|
|
||||||
) : Scope(pacman.rootScope, Arguments.EMPTY, pos) {
|
|
||||||
|
|
||||||
constructor(pacman: Pacman,source: Source) : this(pacman, source.startPos, source.fileName)
|
|
||||||
|
|
||||||
override suspend fun checkImport(pos: Pos, name: String, symbols: Map<String, String>?) {
|
|
||||||
pacman.prepareImport(pos, name, symbols)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import symbols into the scope. It _is called_ after the module is imported by [checkImport].
|
|
||||||
* If [checkImport] was not called, the symbols will not be imported with exception as module is not found.
|
|
||||||
*/
|
|
||||||
override suspend fun importInto(scope: Scope, name: String, symbols: Map<String, String>?) {
|
|
||||||
pacman.performImport(scope, name, symbols)
|
|
||||||
}
|
|
||||||
|
|
||||||
val packageNameObj by lazy { ObjString(packageName).asReadonly}
|
|
||||||
|
|
||||||
override fun get(name: String): ObjRecord? {
|
|
||||||
return if( name == "__PACKAGE__")
|
|
||||||
packageNameObj
|
|
||||||
else
|
|
||||||
super.get(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class InlineSourcesPacman(pacman: Pacman,val sources: List<Source>) : Pacman(pacman) {
|
|
||||||
|
|
||||||
val modules: Deferred<Map<String,Deferred<ModuleScope>>> = globalDefer {
|
|
||||||
val result = mutableMapOf<String, Deferred<ModuleScope>>()
|
|
||||||
for (source in sources) {
|
|
||||||
// retrieve the module name and script for deferred execution:
|
|
||||||
val (name, script) = Compiler.compilePackage(source)
|
|
||||||
// scope is created used pacman's root scope:
|
|
||||||
val scope = ModuleScope(this@InlineSourcesPacman, source.startPos, name)
|
|
||||||
// we execute scripts in parallel which allow cross-imports to some extent:
|
|
||||||
result[name] = globalDefer { script.execute(scope); scope }
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun createModuleScope(name: String): ModuleScope? =
|
|
||||||
modules.await()[name]?.await()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ open class Scope(
|
|||||||
suspend fun eval(source: Source): Obj =
|
suspend fun eval(source: Source): Obj =
|
||||||
Compiler.compile(
|
Compiler.compile(
|
||||||
source,
|
source,
|
||||||
(this as? ModuleScope)?.pacman?.also { println("pacman found: $pacman")} ?: Pacman.emptyAllowAll
|
(this as? ModuleScope)?.pacman ?: Pacman.emptyAllowAll
|
||||||
).execute(this)
|
).execute(this)
|
||||||
|
|
||||||
fun containsLocal(name: String): Boolean = name in objects
|
fun containsLocal(name: String): Boolean = name in objects
|
||||||
|
@ -12,4 +12,15 @@ class Source(val fileName: String, text: String) {
|
|||||||
val startPos: Pos = Pos(this, 0, 0)
|
val startPos: Pos = Pos(this, 0, 0)
|
||||||
|
|
||||||
fun posAt(line: Int, column: Int): Pos = Pos(this, line, column)
|
fun posAt(line: Int, column: Int): Pos = Pos(this, line, column)
|
||||||
|
|
||||||
|
fun extractPackageName(): String {
|
||||||
|
for ((n,line) in lines.withIndex()) {
|
||||||
|
if( line.isBlank() || line.isEmpty() )
|
||||||
|
continue
|
||||||
|
if( line.startsWith("package ") )
|
||||||
|
return line.substring(8).trim()
|
||||||
|
else throw ScriptError(Pos(this, n, 0),"package declaration expected")
|
||||||
|
}
|
||||||
|
throw ScriptError(Pos(this, 0, 0),"package declaration expected")
|
||||||
|
}
|
||||||
}
|
}
|
@ -2333,19 +2333,19 @@ class ScriptTest {
|
|||||||
listOf(1,2,3).associateBy { it * 10 }
|
listOf(1,2,3).associateBy { it * 10 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
// @Test
|
||||||
fun testImports1() = runTest() {
|
// fun testImports1() = runTest() {
|
||||||
val foosrc = """
|
// val foosrc = """
|
||||||
package lyng.foo
|
// package lyng.foo
|
||||||
|
//
|
||||||
fun foo() { "foo1" }
|
// fun foo() { "foo1" }
|
||||||
""".trimIndent()
|
// """.trimIndent()
|
||||||
val pm = InlineSourcesPacman(Pacman.emptyAllowAll, listOf(Source("foosrc", foosrc)))
|
// val pm = InlineSourcesPacman(Pacman.emptyAllowAll, listOf(Source("foosrc", foosrc)))
|
||||||
assertNotNull(pm.modules.await()["lyng.foo"])
|
// assertNotNull(pm.modules["lyng.foo"])
|
||||||
assertIs<ModuleScope>(pm.modules.await()["lyng.foo"]!!.await())
|
// assertIs<ModuleScope>(pm.modules["lyng.foo"]!!.deferredModule.await())
|
||||||
|
|
||||||
assertEquals("foo1", pm.modules.await()["lyng.foo"]!!.await().eval("foo()").toString())
|
// assertEquals("foo1", pm.modules["lyng.foo"]!!.deferredModule.await().eval("foo()").toString())
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testImports2() = runTest() {
|
fun testImports2() = runTest() {
|
||||||
@ -2365,4 +2365,35 @@ class ScriptTest {
|
|||||||
val scope = ModuleScope(pm, src)
|
val scope = ModuleScope(pm, src)
|
||||||
assertEquals("foo1", scope.eval(src).toString())
|
assertEquals("foo1", scope.eval(src).toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testImports3() = runTest {
|
||||||
|
val foosrc = """
|
||||||
|
package lyng.foo
|
||||||
|
|
||||||
|
import lyng.bar
|
||||||
|
|
||||||
|
fun foo() { "foo1" }
|
||||||
|
""".trimIndent()
|
||||||
|
val barsrc = """
|
||||||
|
package lyng.bar
|
||||||
|
|
||||||
|
fun bar() { "bar1" }
|
||||||
|
""".trimIndent()
|
||||||
|
val pm = InlineSourcesPacman(
|
||||||
|
Pacman.emptyAllowAll, listOf(
|
||||||
|
Source("barsrc", barsrc),
|
||||||
|
Source("foosrc", foosrc),
|
||||||
|
))
|
||||||
|
|
||||||
|
val src = """
|
||||||
|
import lyng.foo
|
||||||
|
|
||||||
|
foo() + " / " + bar()
|
||||||
|
""".trimIndent().toSource("test")
|
||||||
|
|
||||||
|
val scope = ModuleScope(pm, src)
|
||||||
|
assertEquals("foo1 / bar1", scope.eval(src).toString())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
37
lynglib/src/jvmTest/kotlin/OtherTests.kt
Normal file
37
lynglib/src/jvmTest/kotlin/OtherTests.kt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import net.sergeych.lyng.*
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class OtherTests {
|
||||||
|
@Test
|
||||||
|
fun testImports3() = runBlocking {
|
||||||
|
val foosrc = """
|
||||||
|
package lyng.foo
|
||||||
|
|
||||||
|
import lyng.bar
|
||||||
|
|
||||||
|
fun foo() { "foo1" }
|
||||||
|
""".trimIndent()
|
||||||
|
val barsrc = """
|
||||||
|
package lyng.bar
|
||||||
|
|
||||||
|
fun bar() { "bar1" }
|
||||||
|
""".trimIndent()
|
||||||
|
val pm = InlineSourcesPacman(
|
||||||
|
Pacman.emptyAllowAll, listOf(
|
||||||
|
Source("foosrc", foosrc),
|
||||||
|
Source("barsrc", barsrc),
|
||||||
|
))
|
||||||
|
|
||||||
|
val src = """
|
||||||
|
import lyng.foo
|
||||||
|
|
||||||
|
foo() + " / " + bar()
|
||||||
|
""".trimIndent().toSource("test")
|
||||||
|
|
||||||
|
val scope = ModuleScope(pm, src)
|
||||||
|
assertEquals("foo1 / bar1", scope.eval(src).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user