diff --git a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/State.kt b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/State.kt index 4042683..fc26b2f 100644 --- a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/State.kt +++ b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/State.kt @@ -72,16 +72,6 @@ class State(parentState: State? = null) { */ var currentDir = fileSystem.getPath(System.getProperty("user.dir")).toAbsolutePath() - /** - * The function used to wrap [InputStream]s into Metis objects. - */ - var inStreamWrapper: InputStreamWrapper = InputStreamWrapper { Value.Native(it, inStreamMetatable) } - - /** - * The function used to wrap [OutputStream]s into Metis objects. - */ - var outStreamWrapper: OutputStreamWrapper = OutputStreamWrapper { Value.Native(it, outStreamMetatable) } - internal val openUpvalues = ArrayDeque() private var throwingException: MetisRuntimeException? = null @@ -144,12 +134,12 @@ class State(parentState: State? = null) { } val io = Value.Table() - io["stdout"] = zeroArgFunction { outStreamWrapper.wrap(stdout) } - io["stderr"] = zeroArgFunction { outStreamWrapper.wrap(stderr) } - io["stdin"] = zeroArgFunction { inStreamWrapper.wrap(stdin) } + io["stdout"] = zeroArgFunction { Value.Native(stdout, NativeObjects.OUTPUT_STREAM) } + io["stderr"] = zeroArgFunction { Value.Native(stderr, NativeObjects.OUTPUT_STREAM) } + io["stdin"] = zeroArgFunction { Value.Native(stdin, NativeObjects.INPUT_STREAM) } - io["inStream"] = inStreamMetatable - io["outStream"] = outStreamMetatable + io["inStream"] = NativeObjects.INPUT_STREAM + io["outStream"] = NativeObjects.OUTPUT_STREAM globals["io"] = io globals["string"] = Value.String.metatable diff --git a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/IntrinsicFunctions.kt b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/IntrinsicFunctions.kt new file mode 100644 index 0000000..02491ef --- /dev/null +++ b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/IntrinsicFunctions.kt @@ -0,0 +1,152 @@ +@file:JvmName("IntrinsicFunctions") + +package io.github.seggan.metis.runtime.intrinsics + +import io.github.seggan.metis.runtime.* +import io.github.seggan.metis.runtime.chunk.StepResult +import io.github.seggan.metis.util.MutableLazy +import io.github.seggan.metis.util.pop +import io.github.seggan.metis.util.push +import java.io.FileNotFoundException +import java.io.IOException +import java.nio.file.InvalidPathException + + +/** + * A function that is guaranteed to finish in one step. Optimization for this is implemented. + * + * @param arity The arity of the function. + */ +abstract class OneShotFunction(override val arity: Arity) : CallableValue { + + override var metatable: Value.Table? by MutableLazy { + Value.Table(mutableMapOf("__str__".metisValue() to oneArgFunction { "OneShot".metisValue() })) + } + + final override fun call(nargs: Int): CallableValue.Executor = object : CallableValue.Executor { + override fun step(state: State): StepResult { + execute(state, nargs) + return StepResult.FINISHED + } + } + + abstract fun execute(state: State, nargs: Int) +} + +/** + * Creates a [OneShotFunction] with zero arguments. + * + * @param fn The function to execute. + * @return The created function. + * @see OneShotFunction + */ +inline fun zeroArgFunction(crossinline fn: State.() -> Value): OneShotFunction = object : OneShotFunction(Arity.ZERO) { + override fun execute(state: State, nargs: Int) { + state.stack.push(state.fn()) + } +} + +/** + * Creates a [OneShotFunction] with one argument. + * + * @param requiresSelf Whether the function requires a `self` argument. + * @param fn The function to execute. + * @return The created function. + * @see OneShotFunction + */ +inline fun oneArgFunction( + requiresSelf: Boolean = false, + crossinline fn: State.(Value) -> Value +): OneShotFunction = object : OneShotFunction(Arity(1, requiresSelf)) { + override fun execute(state: State, nargs: Int) { + state.stack.push(state.fn(state.stack.pop())) + } +} + +/** + * Creates a [OneShotFunction] with two arguments. + * + * @param requiresSelf Whether the function requires a `self` argument. + * @param fn The function to execute. + * @return The created function. + * @see OneShotFunction + */ +inline fun twoArgFunction( + requiresSelf: Boolean = false, + crossinline fn: State.(Value, Value) -> Value +): OneShotFunction = object : OneShotFunction(Arity(2, requiresSelf)) { + override fun execute(state: State, nargs: Int) { + val b = state.stack.pop() + val a = state.stack.pop() + state.stack.push(state.fn(a, b)) + } +} + +/** + * Creates a [OneShotFunction] with three arguments. + * + * @param requiresSelf Whether the function requires a `self` argument. + * @param fn The function to execute. + * @return The created function. + * @see OneShotFunction + */ +inline fun threeArgFunction( + requiresSelf: Boolean = false, + crossinline fn: State.(Value, Value, Value) -> Value +): OneShotFunction = object : OneShotFunction(Arity(3, requiresSelf)) { + override fun execute(state: State, nargs: Int) { + val c = state.stack.pop() + val b = state.stack.pop() + val a = state.stack.pop() + state.stack.push(state.fn(a, b, c)) + } +} + +/** + * Creates a [OneShotFunction] with four arguments. + * + * @param requiresSelf Whether the function requires a `self` argument. + * @param fn The function to execute. + * @return The created function. + * @see OneShotFunction + */ +inline fun fourArgFunction( + requiresSelf: Boolean = false, + crossinline fn: State.(Value, Value, Value, Value) -> Value +): OneShotFunction = object : OneShotFunction(Arity(4, requiresSelf)) { + override fun execute(state: State, nargs: Int) { + val d = state.stack.pop() + val c = state.stack.pop() + val b = state.stack.pop() + val a = state.stack.pop() + state.stack.push(state.fn(a, b, c, d)) + } +} + +/** + * Translates an [IOException] into a [MetisRuntimeException]. + * + * @param block The block to execute. + */ +inline fun translateIoError(block: () -> T): T { + val message = try { + null to block() + } catch (e: InvalidPathException) { + "Invalid path: ${e.message}" to null + } catch (e: FileNotFoundException) { + "File not found: ${e.message}" to null + } catch (e: NoSuchFileException) { + "File not found: ${e.message}" to null + } catch (e: FileAlreadyExistsException) { + "File already exists: ${e.message}" to null + } catch (e: SecurityException) { + "Permission denied: ${e.message}" to null + } catch (e: IOException) { + e.message to null + } + if (message.first != null) { + throw MetisRuntimeException("IoError", message.first!!) + } else { + return message.second!! + } +} \ No newline at end of file diff --git a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/Intrinsics.kt b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/Intrinsics.kt index 600a559..228743f 100644 --- a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/Intrinsics.kt +++ b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/Intrinsics.kt @@ -1,3 +1,5 @@ +@file:JvmName("Intrinsics") + package io.github.seggan.metis.runtime.intrinsics import io.github.seggan.metis.parsing.CodeSource @@ -54,116 +56,4 @@ object Intrinsics { } } } -} - - -/** - * A function that is guaranteed to finish in one step. Optimization for this is implemented. - * - * @param arity The arity of the function. - */ -abstract class OneShotFunction(override val arity: Arity) : CallableValue { - - override var metatable: Value.Table? by MutableLazy { - Value.Table(mutableMapOf("__str__".metisValue() to oneArgFunction { "OneShot".metisValue() })) - } - - final override fun call(nargs: Int): CallableValue.Executor = object : CallableValue.Executor { - override fun step(state: State): StepResult { - execute(state, nargs) - return StepResult.FINISHED - } - } - - abstract fun execute(state: State, nargs: Int) -} - -/** - * Creates a [OneShotFunction] with zero arguments. - * - * @param fn The function to execute. - * @return The created function. - * @see OneShotFunction - */ -inline fun zeroArgFunction(crossinline fn: State.() -> Value): OneShotFunction = object : OneShotFunction(Arity.ZERO) { - override fun execute(state: State, nargs: Int) { - state.stack.push(state.fn()) - } -} - -/** - * Creates a [OneShotFunction] with one argument. - * - * @param requiresSelf Whether the function requires a `self` argument. - * @param fn The function to execute. - * @return The created function. - * @see OneShotFunction - */ -inline fun oneArgFunction( - requiresSelf: Boolean = false, - crossinline fn: State.(Value) -> Value -): OneShotFunction = object : OneShotFunction(Arity(1, requiresSelf)) { - override fun execute(state: State, nargs: Int) { - state.stack.push(state.fn(state.stack.pop())) - } -} - -/** - * Creates a [OneShotFunction] with two arguments. - * - * @param requiresSelf Whether the function requires a `self` argument. - * @param fn The function to execute. - * @return The created function. - * @see OneShotFunction - */ -inline fun twoArgFunction( - requiresSelf: Boolean = false, - crossinline fn: State.(Value, Value) -> Value -): OneShotFunction = object : OneShotFunction(Arity(2, requiresSelf)) { - override fun execute(state: State, nargs: Int) { - val b = state.stack.pop() - val a = state.stack.pop() - state.stack.push(state.fn(a, b)) - } -} - -/** - * Creates a [OneShotFunction] with three arguments. - * - * @param requiresSelf Whether the function requires a `self` argument. - * @param fn The function to execute. - * @return The created function. - * @see OneShotFunction - */ -inline fun threeArgFunction( - requiresSelf: Boolean = false, - crossinline fn: State.(Value, Value, Value) -> Value -): OneShotFunction = object : OneShotFunction(Arity(3, requiresSelf)) { - override fun execute(state: State, nargs: Int) { - val c = state.stack.pop() - val b = state.stack.pop() - val a = state.stack.pop() - state.stack.push(state.fn(a, b, c)) - } -} - -/** - * Creates a [OneShotFunction] with four arguments. - * - * @param requiresSelf Whether the function requires a `self` argument. - * @param fn The function to execute. - * @return The created function. - * @see OneShotFunction - */ -inline fun fourArgFunction( - requiresSelf: Boolean = false, - crossinline fn: State.(Value, Value, Value, Value) -> Value -): OneShotFunction = object : OneShotFunction(Arity(4, requiresSelf)) { - override fun execute(state: State, nargs: Int) { - val d = state.stack.pop() - val c = state.stack.pop() - val b = state.stack.pop() - val a = state.stack.pop() - state.stack.push(state.fn(a, b, c, d)) - } } \ No newline at end of file diff --git a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/NativeLibrary.kt b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/NativeLibrary.kt index c845123..29142be 100644 --- a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/NativeLibrary.kt +++ b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/NativeLibrary.kt @@ -154,8 +154,8 @@ object PathLib : NativeLibrary("__path") { lib["createDir"] = pathFunction { it.createDirectory().absolutePathString().metisValue() } lib["createDirs"] = pathFunction { it.createDirectories().absolutePathString().metisValue() } lib["deleteRecursive"] = pathFunction { it.deleteRecursively(); Value.Null } - lib["openWrite"] = pathFunction { outStreamWrapper.wrap(it.outputStream()) } - lib["openRead"] = pathFunction { inStreamWrapper.wrap(it.inputStream()) } + lib["openWrite"] = pathFunction { Value.Native(it.outputStream(), NativeObjects.OUTPUT_STREAM) } + lib["openRead"] = pathFunction { Value.Native(it.inputStream(), NativeObjects.INPUT_STREAM) } } } diff --git a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/NativeObjects.kt b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/NativeObjects.kt index 3d9d543..bb62c33 100644 --- a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/NativeObjects.kt +++ b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/NativeObjects.kt @@ -1,144 +1,104 @@ -@file:JvmName("NativeObjects") - package io.github.seggan.metis.runtime.intrinsics import io.github.seggan.metis.runtime.* -import java.io.FileNotFoundException -import java.io.IOException import java.io.InputStream import java.io.OutputStream -import java.nio.file.InvalidPathException /** - * Translates an [IOException] into a [MetisRuntimeException]. - * - * @param block The block to execute. + * Contains the metatables of various native objects. */ -inline fun translateIoError(block: () -> T): T { - val message = try { - null to block() - } catch (e: InvalidPathException) { - "Invalid path: ${e.message}" to null - } catch (e: FileNotFoundException) { - "File not found: ${e.message}" to null - } catch (e: NoSuchFileException) { - "File not found: ${e.message}" to null - } catch (e: FileAlreadyExistsException) { - "File already exists: ${e.message}" to null - } catch (e: SecurityException) { - "Permission denied: ${e.message}" to null - } catch (e: IOException) { - e.message to null - } - if (message.first != null) { - throw MetisRuntimeException("IoError", message.first!!) - } else { - return message.second!! - } -} +object NativeObjects { -internal val outStreamMetatable = buildTable { table -> - table["write"] = fourArgFunction(true) { self, buffer, off, len -> - val toBeWritten = buffer.bytesValue() - val offset = if (off == Value.Null) 0 else off.intValue() - val length = if (len == Value.Null) toBeWritten.size else len.intValue() - translateIoError { self.asObj().write(toBeWritten, offset, length) } - toBeWritten.size.toDouble().metisValue() - } - table["flush"] = oneArgFunction(true) { self -> - translateIoError { self.asObj().flush() } - Value.Null - } - table["close"] = oneArgFunction(true) { self -> - translateIoError { self.asObj().close() } - Value.Null - } - table["__str__"] = oneArgFunction(true) { - "an output stream".metisValue() + val OUTPUT_STREAM = buildTable { table -> + table["write"] = fourArgFunction(true) { self, buffer, off, len -> + val toBeWritten = buffer.bytesValue() + val offset = if (off == Value.Null) 0 else off.intValue() + val length = if (len == Value.Null) toBeWritten.size else len.intValue() + translateIoError { self.asObj().write(toBeWritten, offset, length) } + toBeWritten.size.toDouble().metisValue() + } + table["flush"] = oneArgFunction(true) { self -> + translateIoError { self.asObj().flush() } + Value.Null + } + table["close"] = oneArgFunction(true) { self -> + translateIoError { self.asObj().close() } + Value.Null + } + table["__str__"] = oneArgFunction(true) { + "an output stream".metisValue() + } } -} -fun interface OutputStreamWrapper { - fun wrap(stream: OutputStream): Value -} - -internal val inStreamMetatable = buildTable { table -> - table["read"] = twoArgFunction(true) { self, buffer -> - if (buffer == Value.Null) { - // Read a single byte - self.asObj().read().toDouble().metisValue() - } else { - // Read into a buffer - val toBeRead = buffer.bytesValue() - val read = self.asObj().read(toBeRead) - if (read == -1) { - Value.Null + val INPUT_STREAM = buildTable { table -> + table["read"] = twoArgFunction(true) { self, buffer -> + if (buffer == Value.Null) { + // Read a single byte + self.asObj().read().toDouble().metisValue() } else { - read.toDouble().metisValue() + // Read into a buffer + val toBeRead = buffer.bytesValue() + val read = self.asObj().read(toBeRead) + if (read == -1) { + Value.Null + } else { + read.toDouble().metisValue() + } } } + table["close"] = oneArgFunction(true) { self -> + self.asObj().close() + Value.Null + } + table["__str__"] = oneArgFunction(true) { + "an input stream".metisValue() + } } - table["close"] = oneArgFunction(true) { self -> - self.asObj().close() - Value.Null - } - table["__str__"] = oneArgFunction(true) { - "an input stream".metisValue() - } -} - -fun interface InputStreamWrapper { - fun wrap(stream: InputStream): Value -} -private val sbMetatable = buildTable { table -> - table["__append"] = twoArgFunction(true) { self, value -> - self.asObj().append(value.stringValue()) - self - } - table["__index__"] = twoArgFunction(true) { self, value -> - if (value is Value.Number) { - self.asObj()[value.intValue()].toString().metisValue() - } else { - self.lookUp(value) ?: throw MetisRuntimeException( - "KeyError", - "Key not found: ${stringify(value)}", - buildTable { table -> - table["key"] = value - table["value"] = self - } - ) + val STRING_BUILDER = buildTable { table -> + table["__append"] = twoArgFunction(true) { self, value -> + self.asObj().append(value.stringValue()) + self } - } - table["__set"] = threeArgFunction(true) { self, index, value -> - if (value is Value.Number) { - self.asObj()[index.intValue()] = value.stringValue()[0] - } else { - self.setOrError(index, value) + table["__index__"] = twoArgFunction(true) { self, value -> + if (value is Value.Number) { + self.asObj()[value.intValue()].toString().metisValue() + } else { + self.lookUp(value) ?: throw MetisRuntimeException( + "KeyError", + "Key not found: ${stringify(value)}", + buildTable { table -> + table["key"] = value + table["value"] = self + } + ) + } + } + table["__set"] = threeArgFunction(true) { self, index, value -> + if (value is Value.Number) { + self.asObj()[index.intValue()] = value.stringValue()[0] + } else { + self.setOrError(index, value) + } + self + } + table["delete"] = threeArgFunction(true) { self, start, end -> + self.asObj().delete(start.intValue(), end.intValue()) + self + } + table["deleteAt"] = twoArgFunction(true) { self, index -> + self.asObj().deleteCharAt(index.intValue()) + self + } + table["clear"] = oneArgFunction(true) { self -> + self.asObj().clear() + self + } + table["size"] = oneArgFunction(true) { self -> + Value.Number.of(self.asObj().length) + } + table["__str__"] = oneArgFunction(true) { self -> + self.asObj().toString().metisValue() } - self - } - table["delete"] = threeArgFunction(true) { self, start, end -> - self.asObj().delete(start.intValue(), end.intValue()) - self - } - table["deleteAt"] = twoArgFunction(true) { self, index -> - self.asObj().deleteCharAt(index.intValue()) - self - } - table["clear"] = oneArgFunction(true) { self -> - self.asObj().clear() - self - } - table["size"] = oneArgFunction(true) { self -> - Value.Number.of(self.asObj().length) - } - table["__str__"] = oneArgFunction(true) { self -> - self.asObj().toString().metisValue() } -} - -/** - * Wraps a [StringBuilder] in a [Value]. - */ -fun wrapStringBuilder(sb: StringBuilder): Value = Value.Native(sb, sbMetatable) \ No newline at end of file +} \ No newline at end of file diff --git a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/ValueInit.kt b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/ValueInit.kt index 599cf66..9799026 100644 --- a/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/ValueInit.kt +++ b/metis-lang/src/main/kotlin/io/github/seggan/metis/runtime/intrinsics/ValueInit.kt @@ -100,9 +100,9 @@ internal fun initString() = buildTable { table -> table["builder"] = oneArgFunction { init -> when (init) { - Value.Null -> wrapStringBuilder(StringBuilder()) - is Value.String -> wrapStringBuilder(StringBuilder(init.stringValue())) - else -> wrapStringBuilder(StringBuilder(init.intValue())) + Value.Null -> Value.Native(StringBuilder(), NativeObjects.STRING_BUILDER) + is Value.String -> Value.Native(StringBuilder(init.stringValue()), NativeObjects.STRING_BUILDER) + else -> Value.Native(StringBuilder(init.stringValue()), NativeObjects.STRING_BUILDER) } } }