diff --git a/bin/lyng_test b/bin/lyng_test new file mode 100755 index 0000000..4726eaf --- /dev/null +++ b/bin/lyng_test @@ -0,0 +1,3 @@ +#!/usr/bin/env lyng + +println("Hello from lyng!") diff --git a/lyng/build.gradle.kts b/lyng/build.gradle.kts index ced753c..90e0bbe 100644 --- a/lyng/build.gradle.kts +++ b/lyng/build.gradle.kts @@ -29,11 +29,11 @@ kotlin { sourceSets { val commonMain by getting { dependencies { + implementation(kotlin("stdlib-common")) implementation(project(":lynglib")) implementation(libs.okio) - implementation(libs.clikt) - + implementation(kotlin("stdlib-common")) // optional support for rendering markdown in help messages // implementation(libs.clikt.markdown) } @@ -42,9 +42,15 @@ kotlin { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) + implementation(libs.kotlinx.coroutines.core) implementation(libs.okio.fakefilesystem) } } +// val nativeMain by getting { +// dependencies { +// implementation(kotlin("stdlib-common")) +// } +// } val linuxX64Main by getting { } diff --git a/lyng/src/commonMain/kotlin/Common.kt b/lyng/src/commonMain/kotlin/Common.kt index adeaf8e..c13e1a2 100644 --- a/lyng/src/commonMain/kotlin/Common.kt +++ b/lyng/src/commonMain/kotlin/Common.kt @@ -17,11 +17,28 @@ import okio.use expect fun exit(code: Int) +expect class ShellCommandExecutor { + fun executeCommand(command: String): CommandResult + + companion object { + fun create(): ShellCommandExecutor + } +} + +data class CommandResult( + val exitCode: Int, + val output: String, + val error: String +) + val baseContext = Context().apply { addFn("exit") { exit(requireOnlyArg().toInt()) ObjVoid } +// ObjString.type.addFn("shell") { +// +// } } class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() { @@ -90,11 +107,16 @@ class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() { } suspend fun executeFile(fileName: String) { - val text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource -> + var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource -> fileSource.buffer().use { bs -> bs.readUtf8() } } + if( text.startsWith("#!") ) { + // skip shebang + val pos = text.indexOf('\n') + text = text.substring(pos + 1) + } processErrors { Compiler().compile(Source(fileName, text)).execute(baseContext) } diff --git a/lyng/src/jvmMain/kotlin/Shell.jvm.kt b/lyng/src/jvmMain/kotlin/Shell.jvm.kt new file mode 100644 index 0000000..562b68a --- /dev/null +++ b/lyng/src/jvmMain/kotlin/Shell.jvm.kt @@ -0,0 +1,21 @@ +package net.sergeych + +// Alternative implementation for native targets +actual class ShellCommandExecutor() { + actual fun executeCommand(command: String): CommandResult { + val process = ProcessBuilder("/bin/sh", "-c", command).start() + val exitCode = process.waitFor() + val output = process.inputStream.bufferedReader().readText() + val error = process.errorStream.bufferedReader().readText() + + return CommandResult( + exitCode = exitCode, + output = output.trim(), + error = error.trim() + ) + } + + actual companion object { + actual fun create(): ShellCommandExecutor = ShellCommandExecutor() + } +} \ No newline at end of file diff --git a/lyng/src/nativeMain/kotlin/Common.native.kt b/lyng/src/nativeMain/kotlin/Common.native.kt index c6b76db..c64e241 100644 --- a/lyng/src/nativeMain/kotlin/Common.native.kt +++ b/lyng/src/nativeMain/kotlin/Common.native.kt @@ -1,7 +1,48 @@ +@file:OptIn(ExperimentalForeignApi::class, ExperimentalForeignApi::class, ExperimentalForeignApi::class) + package net.sergeych +import kotlinx.cinterop.* +import platform.posix.fgets +import platform.posix.pclose +import platform.posix.popen import kotlin.system.exitProcess +actual class ShellCommandExecutor() { + actual fun executeCommand(command: String): CommandResult { + val outputBuilder = StringBuilder() + val errorBuilder = StringBuilder() + + val fp = popen(command, "r") ?: return CommandResult( + exitCode = -1, + output = "", + error = "Failed to execute command" + ) + + val buffer = ByteArray(4096) + while (true) { + val bytesRead = buffer.usePinned { pinned -> + fgets(pinned.addressOf(0), buffer.size.convert(), fp) + } + if (bytesRead == null) break + outputBuilder.append(bytesRead.toKString()) + } + + val status = pclose(fp) + val exitCode = if (status == 0) 0 else 1 + + return CommandResult( + exitCode = exitCode, + output = outputBuilder.toString().trim(), + error = errorBuilder.toString().trim() + ) + } + + actual companion object { + actual fun create(): ShellCommandExecutor = ShellCommandExecutor() + } +} + actual fun exit(code: Int) { exitProcess(code) } \ No newline at end of file