diff --git a/build.gradle b/build.gradle index 2b44f8f2..0517d4f7 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { apply plugin: 'org.jetbrains.dokka' project.ext { - openrndrVersion = "0.3.42-rc.2" + openrndrVersion = "0.3.42-rc.3" kotlinVersion = "1.3.72" spekVersion = "2.0.10" libfreenectVersion = "0.5.7-1.5.3" @@ -57,6 +57,8 @@ allprojects { group 'org.openrndr.extra' + + repositories { mavenCentral() jcenter() diff --git a/orx-kotlin-parser/src/main/kotlin/ExtractProgram.kt b/orx-kotlin-parser/src/main/kotlin/ExtractProgram.kt index d095573e..91319f59 100644 --- a/orx-kotlin-parser/src/main/kotlin/ExtractProgram.kt +++ b/orx-kotlin-parser/src/main/kotlin/ExtractProgram.kt @@ -3,6 +3,7 @@ package org.openrndr.extra.kotlinparser import org.antlr.v4.runtime.CharStreams import org.antlr.v4.runtime.CommonTokenStream import org.antlr.v4.runtime.ParserRuleContext +import org.antlr.v4.runtime.RuleContext import org.antlr.v4.runtime.misc.Interval import org.antlr.v4.runtime.tree.ParseTreeWalker import org.openrndr.extra.kotlin.antlr.KotlinLexer @@ -10,14 +11,14 @@ import org.openrndr.extra.kotlin.antlr.KotlinParser import org.openrndr.extra.kotlin.antlr.KotlinParserBaseListener import java.io.File -fun ParserRuleContext.verbatimText(marginLeft:Int = 0, marginRight:Int = 0): String { +fun ParserRuleContext.verbatimText(marginLeft: Int = 0, marginRight: Int = 0): String { val startIndex = start.startIndex + marginLeft val stopIndex = stop.stopIndex - marginRight val interval = Interval(startIndex, stopIndex) return start.inputStream.getText(interval) } -class ImportsExtractor : KotlinParserBaseListener() { +class ImportsExtractor(val ruleNames: List) : KotlinParserBaseListener() { var result: String? = null override fun enterImportList(ctx: KotlinParser.ImportListContext) { @@ -25,29 +26,34 @@ class ImportsExtractor : KotlinParserBaseListener() { } } -class LambdaExtractor(val lambdaName: String) : KotlinParserBaseListener() { +class LambdaExtractor(val ruleNames: List, val lambdaName: String) : KotlinParserBaseListener() { + fun RuleContext.named(): String { + return ruleNames[this.ruleIndex] + } var result: String? = null override fun enterAnnotatedLambda(ctx: KotlinParser.AnnotatedLambdaContext?) { val puec = ctx!!.parent!!.parent!!.parent!! as KotlinParser.PostfixUnaryExpressionContext val identifier = puec.primaryExpression().simpleIdentifier().Identifier().text if (identifier == lambdaName) { - result = ctx.verbatimText(1, 1) + if (result == null) { + result = ctx.verbatimText(1, 1) + } } } } + class ProgramSource(val imports: String, val programLambda: String) - - -fun extractProgram(source: String) : ProgramSource { +fun extractProgram(source: String, programIdentifier: String = "program"): ProgramSource { val parser = KotlinParser(CommonTokenStream(KotlinLexer(CharStreams.fromString(source)))) val root = parser.kotlinFile() - - val importsExtractor = ImportsExtractor() +// val rules = parser.ruleNames.toList() +// val pt = TreeUtils.toPrettyTree(root, rules) + val ruleNames = parser.ruleNames.toList() + val importsExtractor = ImportsExtractor(ruleNames) ParseTreeWalker.DEFAULT.walk(importsExtractor, root) - val lambdaExtractor = LambdaExtractor("program") + val lambdaExtractor = LambdaExtractor(ruleNames, programIdentifier) ParseTreeWalker.DEFAULT.walk(lambdaExtractor, root) - return ProgramSource(importsExtractor.result!!, lambdaExtractor.result!!) } diff --git a/orx-kotlin-parser/src/main/kotlin/TreeUtils.kt b/orx-kotlin-parser/src/main/kotlin/TreeUtils.kt index 7e47fc8d..a8443ac4 100644 --- a/orx-kotlin-parser/src/main/kotlin/TreeUtils.kt +++ b/orx-kotlin-parser/src/main/kotlin/TreeUtils.kt @@ -1,3 +1,5 @@ +package org.openrndr.extra.kotlinparser + import org.antlr.v4.runtime.misc.Utils import org.antlr.v4.runtime.tree.Tree import org.antlr.v4.runtime.tree.Trees diff --git a/orx-olive/src/demo/kotlin/DemoOlive01.kt b/orx-olive/src/demo/kotlin/DemoOlive01.kt index 352130c6..1119a27c 100644 --- a/orx-olive/src/demo/kotlin/DemoOlive01.kt +++ b/orx-olive/src/demo/kotlin/DemoOlive01.kt @@ -13,7 +13,6 @@ fun main() = application { extend(Olive()) { script = "orx-olive/src/demo/kotlin/demo-olive-01.kts" - // -- this block is for automation purposes only if (System.getProperty("takeScreenshot") == "true") { scriptLoaded.listen { diff --git a/orx-olive/src/demo/kotlin/DemoOliveScriptless01.kt b/orx-olive/src/demo/kotlin/DemoOliveScriptless01.kt index 6716508f..8100a887 100644 --- a/orx-olive/src/demo/kotlin/DemoOliveScriptless01.kt +++ b/orx-olive/src/demo/kotlin/DemoOliveScriptless01.kt @@ -1,28 +1,36 @@ import org.openrndr.Extension -import org.openrndr.Program import org.openrndr.application import org.openrndr.color.ColorRGBa import org.openrndr.extensions.SingleScreenshot -import org.openrndr.extra.olive.Olive -import org.openrndr.math.IntVector2 +import org.openrndr.extra.olive.oliveProgram +import kotlin.math.cos fun main() { application { configure { - position = IntVector2(3440 / 2 + 200, 360) width = 1280 height = 720 } - program { - extend(Olive()) { - // -- this block is for automation purposes only - if (System.getProperty("takeScreenshot") == "true") { - scriptLoaded.listen { + oliveProgram { + extend { + drawer.clear(ColorRGBa.GRAY) + drawer.fill = ColorRGBa.WHITE + for (i in 0 until 100) { + drawer.circle( + width / 2.0 + cos(seconds + i) * 320.0, + i * 7.2, + cos(i + seconds * 0.5) * 20.0 + 20.0) + } + } + } + // -- this is only needed for the automated screenshots + .olive.scriptLoaded.listen { + if (System.getProperty("takeScreenshot") == "true") { // -- this is a bit of hack, we need to push the screenshot extension in front of the olive one - fun Program.extendHead(extension: T, configure: T.() -> Unit): T { - extensions.add(0, extension) + fun extendHead(extension: T, configure: T.() -> Unit): T { + program.extensions.add(0, extension) extension.configure() - extension.setup(this) + extension.setup(program) return extension } extendHead(SingleScreenshot()) { @@ -30,17 +38,5 @@ fun main() { } } } - script = "orx-olive/src/demo/kotlin/DemoOliveScriptless01.kt" - program { - extend { - drawer.background(ColorRGBa.PINK) - drawer.circle(width /2.0, height / 2.0, 300.0 + Math.cos(seconds)*100.0) - - drawer.fill = ColorRGBa.RED - drawer.circle(mouse.position, 100.0) - } - } - } - } } } \ No newline at end of file diff --git a/orx-olive/src/demo/kotlin/demo-olive-01.kts b/orx-olive/src/demo/kotlin/demo-olive-01.kts index 8448507c..d7350400 100644 --- a/orx-olive/src/demo/kotlin/demo-olive-01.kts +++ b/orx-olive/src/demo/kotlin/demo-olive-01.kts @@ -6,7 +6,7 @@ import org.openrndr.draw.* { program: Program -> program.apply { extend { - drawer.background(ColorRGBa.GRAY) + drawer.clear(ColorRGBa.GRAY) drawer.fill = ColorRGBa.PINK drawer.circle(width/2.0, height/2.0 ,200.0) } diff --git a/orx-olive/src/main/kotlin/Olive.kt b/orx-olive/src/main/kotlin/Olive.kt index 8ba4521c..57bbe15f 100644 --- a/orx-olive/src/main/kotlin/Olive.kt +++ b/orx-olive/src/main/kotlin/Olive.kt @@ -35,7 +35,12 @@ enum class OliveScriptHost { data class ScriptLoadedEvent(val scriptFile: String) -class Olive

(val resources: Resources? = null, private var scriptless: Boolean = false) : Extension { +enum class ScriptMode { + KOTLIN_SCRIPT, + OLIVE_PROGRAM +} + +class Olive

(val resources: Resources? = null, private var scriptMode: ScriptMode = ScriptMode.KOTLIN_SCRIPT) : Extension { override var enabled: Boolean = true var session: Session? = null var scriptHost = OliveScriptHost.JSR223_REUSE @@ -44,12 +49,16 @@ class Olive

(val resources: Resources? = null, private var scriptles internal var scriptChange: (String) -> Unit = {} - var script = if (!scriptless) "src/main/kotlin/${stackRootClassName().split(".").last()}.kts" - else "src/main/kotlin/${stackRootClassName().split(".").last()}.kt" + var script = when (scriptMode) { + ScriptMode.KOTLIN_SCRIPT -> "src/main/kotlin/${stackRootClassName().split(".").last()}.kts" + else -> "src/main/kotlin/${stackRootClassName().split(".").last()}.kt" + } set(value) { +// require(scriptMode == ScriptMode.KOTLIN_SCRIPT) { +// "can only change the script in KOTLIN_SCRIPT mode" +// } field = value scriptChange(value) - scriptless = value.endsWith(".kt") } /** @@ -115,12 +124,13 @@ class Olive

(val resources: Resources? = null, private var scriptles try { logger.info("change detected, reloading script") - val scriptContents = if (!scriptless) { - it.readText() - } else { - val source = it.readText() - val programSource = extractProgram(source) - generateScript(programSource) + val scriptContents = when(scriptMode) { + ScriptMode.KOTLIN_SCRIPT -> it.readText() + ScriptMode.OLIVE_PROGRAM -> { + val source = it.readText() + val programSource = extractProgram(source, programIdentifier = "oliveProgram") + generateScript(programSource) + } } val futureFunc = GlobalScope.async { @@ -181,10 +191,5 @@ class Olive

(val resources: Resources? = null, private var scriptles } } - fun program(f: Program.() -> Unit) { - require(script.endsWith(".kt")) { - """program bodies are only allowed in 'scriptless' mode""" - } - } } \ No newline at end of file diff --git a/orx-olive/src/main/kotlin/OliveProgram.kt b/orx-olive/src/main/kotlin/OliveProgram.kt new file mode 100644 index 00000000..e74e9227 --- /dev/null +++ b/orx-olive/src/main/kotlin/OliveProgram.kt @@ -0,0 +1,43 @@ +package org.openrndr.extra.olive + +import org.openrndr.ApplicationBuilder +import org.openrndr.Program +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.streams.toList + +open class OliveProgram(private val sourceLocation: String) : Program() { + val olive = extend(Olive(scriptMode = ScriptMode.OLIVE_PROGRAM)) { + script = sourceLocation + } +} + +fun stackRootClassName(thread: Thread = Thread.currentThread(), sanitize: Boolean = true): String { + val root = Thread.currentThread().stackTrace.last() + val rootClass = root.className + return if (sanitize) rootClass.replace(Regex("Kt$"), "") else rootClass +} + +fun ApplicationBuilder.oliveProgram(init: OliveProgram.() -> Unit): OliveProgram { + val rootClassName = stackRootClassName(sanitize = true) + + var sourceLocation = "src/main/kotlin/$rootClassName.kt" + val candidateFile = File(sourceLocation) + if (!candidateFile.exists()) { + val otherCandidates = Files.walk(Paths.get(".")) + .filter { Files.isRegularFile(it) && it.toString().endsWith("$rootClassName.kt") }.toList() + if (otherCandidates.size == 1) { + sourceLocation = otherCandidates.first().toString() + } else { + error("multiple source candidates found: $otherCandidates") + } + } + program = object : OliveProgram(sourceLocation) { + override fun setup() { + super.setup() + init() + } + } + return program as OliveProgram +} \ No newline at end of file diff --git a/orx-olive/src/main/kotlin/ScriptGenerator.kt b/orx-olive/src/main/kotlin/ScriptGenerator.kt index c4ea94d2..e7c653ff 100644 --- a/orx-olive/src/main/kotlin/ScriptGenerator.kt +++ b/orx-olive/src/main/kotlin/ScriptGenerator.kt @@ -2,16 +2,19 @@ package org.openrndr.extra.olive import org.openrndr.extra.kotlinparser.ProgramSource -fun generateScript(programSource: ProgramSource): String { - return """ +inline fun generateScript(programSource: ProgramSource): String { + val script = """ @file:Suppress("UNUSED_LAMBDA_EXPRESSION") +import org.openrndr.extra.olive.OliveProgram ${programSource.imports} -{ program: Program -> +{ program: ${T::class.simpleName} -> program.apply { ${programSource.programLambda} } } """ + println(script) + return script } \ No newline at end of file diff --git a/orx-olive/src/main/kotlin/ScriptObjectLoaderJSR233.kt b/orx-olive/src/main/kotlin/ScriptObjectLoaderJSR233.kt index 31e7bf9c..3e05f2c3 100644 --- a/orx-olive/src/main/kotlin/ScriptObjectLoaderJSR233.kt +++ b/orx-olive/src/main/kotlin/ScriptObjectLoaderJSR233.kt @@ -28,6 +28,7 @@ class ScriptObjectLoader(classLoader: ClassLoader? = Thread.currentThread().cont fun safeEval(evaluation: () -> R?) = try { evaluation() } catch (e: Exception) { + e.printStackTrace() throw LoadException("Cannot load script", e) }