Skip to content

Commit

Permalink
Add a zunch of path
Browse files Browse the repository at this point in the history
  • Loading branch information
Seggan committed Nov 2, 2023
1 parent e7afe44 commit e18895a
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 51 deletions.
7 changes: 6 additions & 1 deletion docs/lang/core/io.papyri
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,13 @@ Closes the stream.
@h3 { flush(self): null }
Flushes the stream.

@h3 { write(self, buffer: bytes): number }
@h3 { write(self, buffer: bytes, off: number \| null, len: number \| null): number }
Writes a sequence of bytes from the buffer to the stream. Returns the number of bytes written.
If `off` is provided, the function starts writing from the byte at that offset. If `len` is
provided, the function writes at most that many bytes. If `off` and `len` are both provided,
the function writes at most `len` bytes starting from the byte at offset `off`. If `off` is
not provided, the function starts writing from the first byte in the buffer. If `len` is not
provided, the function writes to the end of the buffer.

@h3 { write_line(self, line: string): null }
Writes a line to the stream.
Expand Down
33 changes: 32 additions & 1 deletion docs/lang/std/path.papyri
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,25 @@ The path separator for the current platform. This is either `/` or `\`.
@h3 { absolute(path: string): string }
Returns the absolute path of the given path.

@h3 { base_name(path: string): string }
@h3 { file_name(path: string): string }
Returns the filename of the given path.

@h3 { copy(from: string, to: string): null }
Copies the file at the given path `from` to the given path `to`.

@h3 { create_dir(path: string): string }
Creates a directory at the given path and returns the path of the created directory.

@h3 { create_dirs(path: string): string }
Creates a directory at the given path and all parent directories that do not exist yet. Returns
the path of the created directory.

@h3 { delete(path: string): null }
Deletes the file or directory at the given path. Directories must be empty.

@h3 { delete_recursive(path: string): null }
Deletes the file or directory at the given path recursively.

@h3 { exists(path: string): boolean }
Returns a boolean value indicating whether the file or directory at the given path exists.

Expand All @@ -41,6 +57,9 @@ Returns a boolean value indicating whether the given path is a symbolic link.
@h3 { list(path: string): list\[string\] }
Returns a list of the files and directories in the given directory.

@h3 { move(from: string, to: string): null }
Moves the file at the given path `from` to the given path `to`.

@h3 { normalize(path: string): string }
Normalizes the given path and returns the normalized path as a string.

Expand All @@ -53,8 +72,20 @@ Opens the file at the given path for writing and returns an output stream.
@h3 { parent(path: string): string }
Returns the parent directory of the given path.

@h3 { read_all(path: string): bytes }
Reads the entire file at the given path and returns the contents as a byte array.

@h3 { read_text(path: string): string }
Reads the entire file at the given path and returns the contents as a string.

@h3 { resolve(base: string, other: string): string }
Resolves the given path `other` against the base path `base` and returns the resolved path as a string.

@h3 { root(path: string): string }
Returns the root of the given path.

@h3 { write_all(path: string, data: bytes) }
Writes the given byte array to the file at the given path.

@h3 { write_text(path: string, data: string) }
Writes the given string to the file at the given path.
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,25 @@ inline fun threeArgFunction(
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))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package io.github.seggan.metis.runtime.intrinsics

import io.github.seggan.metis.runtime.*
import io.github.seggan.metis.util.push
import java.io.IOException
import java.nio.file.InvalidPathException
import java.nio.file.NoSuchFileException
import java.nio.file.Path
import java.util.regex.PatternSyntaxException
import kotlin.collections.set
Expand All @@ -18,9 +15,8 @@ import kotlin.math.*
*/
abstract class NativeLibrary(val name: String) : OneShotFunction(Arity.ZERO) {
final override fun execute(state: State, nargs: Int) {
val table = buildTable(::init)
state.globals[name] = table
state.stack.push(table)
state.globals.putAll(buildTable(::init))
state.stack.push(Value.Null)
}

abstract fun init(lib: MutableMap<String, Value>)
Expand Down Expand Up @@ -108,32 +104,31 @@ object OsLib : NativeLibrary("os") {
}

/**
* The `path` library
* The `path` library's native functions
*/
object PathLib : NativeLibrary("path") {
object PathLib : NativeLibrary("__path") {

private inline fun pathFunction(crossinline fn: (Path) -> Value) = oneArgFunction { self ->
try {
fn(currentDir.resolve(fileSystem.getPath(self.stringValue())))
} catch (e: InvalidPathException) {
Value.Null
} catch (e: IOException) {
throw MetisRuntimeException("IoError", e.message ?: "Unknown IO error", cause = e)
}
translateIoError { fn(toPath(self)) }
}

private fun State.toPath(value: Value) = currentDir.resolve(fileSystem.getPath(value.stringValue()))

@OptIn(ExperimentalPathApi::class)
override fun init(lib: MutableMap<String, Value>) {
lib["separator"] = Value.String(System.getProperty("file.separator"))
lib["normalize"] = pathFunction { it.normalize().toString().metisValue() }
lib["absolute"] = pathFunction { it.toAbsolutePath().toString().metisValue() }
lib["resolve"] = twoArgFunction { self, other ->
currentDir.resolve(fileSystem.getPath(self.stringValue()))
.resolve(other.stringValue())
.toString()
.metisValue()
translateIoError {
toPath(self)
.resolve(other.stringValue())
.toString()
.metisValue()
}
}
lib["parent"] = pathFunction { it.parent.toString().metisValue() }
lib["base_name"] = pathFunction { it.fileName.toString().metisValue() }
lib["file_name"] = pathFunction { it.fileName.toString().metisValue() }
lib["root"] = pathFunction { it.root.toString().metisValue() }
lib["is_absolute"] = pathFunction { it.isAbsolute.metisValue() }
lib["list"] = pathFunction { path ->
Expand All @@ -148,28 +143,18 @@ object PathLib : NativeLibrary("path") {
lib["is_dir"] = pathFunction { it.isDirectory().metisValue() }
lib["is_symlink"] = pathFunction { it.isSymbolicLink().metisValue() }
lib["is_hidden"] = pathFunction { it.isHidden().metisValue() }
lib["open_write"] = pathFunction {
try {
wrapOutStream(it.outputStream())
} catch (e: FileAlreadyExistsException) {
throw MetisRuntimeException(
"IoError",
"File already exists: ${it.absolutePathString()}",
Value.Table(mutableMapOf("path".metisValue() to it.absolutePathString().metisValue()))
)
}
}
lib["open_read"] = pathFunction {
try {
wrapInStream(it.inputStream())
} catch (e: NoSuchFileException) {
throw MetisRuntimeException(
"IoError",
"File not found: ${it.absolutePathString()}",
Value.Table(mutableMapOf("path".metisValue() to it.absolutePathString().metisValue()))
)
lib["move"] = twoArgFunction { src, dest ->
translateIoError {
toPath(src).moveTo(toPath(dest))
}
Value.Null
}
lib["delete"] = pathFunction { it.deleteIfExists().metisValue() }
lib["create_dir"] = pathFunction { it.createDirectory().absolutePathString().metisValue() }
lib["create_dirs"] = pathFunction { it.createDirectories().absolutePathString().metisValue() }
lib["delete_recursive"] = pathFunction { it.deleteRecursively(); Value.Null }
lib["open_write"] = pathFunction { wrapOutStream(it.outputStream()) }
lib["open_read"] = pathFunction { wrapInStream(it.inputStream()) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,46 @@
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.
*/
inline fun <T> translateIoError(block: () -> T): T = try {
block()
} catch (e: IOException) {
throw MetisRuntimeException("IoError", e.message ?: "Unknown IO error", cause = e)
inline fun <T> 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!!
}
}

internal val outStreamMetatable = buildTable { table ->
table["write"] = twoArgFunction(true) { self, value ->
val toBeWritten = value.convertTo<Value.Bytes>().value
translateIoError { self.asObj<OutputStream>().write(toBeWritten) }
table["write"] = fourArgFunction(true) { self, buffer, off, len ->
val toBeWritten = buffer.convertTo<Value.Bytes>().value
val offset = if (off == Value.Null) 0 else off.intValue()
val length = if (len == Value.Null) toBeWritten.size else len.intValue()
translateIoError { self.asObj<OutputStream>().write(toBeWritten, offset, length) }
toBeWritten.size.toDouble().metisValue()
}
table["flush"] = oneArgFunction(true) { self ->
Expand Down
13 changes: 11 additions & 2 deletions lang/src/main/resources/core/io.metis
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,18 @@ fn io.in_stream.read_line(self)
return str(result)
end

fn io.in_stream.copy_to(self, out)
let buffer = bytes.allocate(BUFSIZE)
let read = self.read(buffer)
while read is not null and read > 0
out.write(buffer, 0, read)
read = self.read(buffer)
end
end

fn io.out_stream.write_line(self, line)
self.write(line)
self.write("\n")
self.write(line.encode())
self.write('\n')
end

fn io.out_stream.write_text(self, text)
Expand Down
54 changes: 54 additions & 0 deletions lang/src/main/resources/std/path.metis
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import __path

globals().merge(__path)

global fn read_all(p)
let result = null
let stream = __path.open_read(p)
do
result = stream.read_all()
finally
stream.close()
end
return result
end

global fn read_text(p)
let result = null
let stream = __path.open_read(p)
do
result = stream.read_text()
finally
stream.close()
end
return result
end

global fn write_all(p, data)
let stream = __path.open_write(p)
do
stream.write(data)
finally
stream.close()
end
end

global fn write_text(p, data)
let stream = __path.open_write(p)
do
stream.write_text(data)
finally
stream.close()
end
end

global fn copy(src, dest)
let source = __path.open_read(src)
let destination = __path.open_write(dest)
do
source.copy_to(destination)
finally
source.close()
destination.close()
end
end

0 comments on commit e18895a

Please sign in to comment.