mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 08:17:40 +03:00
Merge pull request #3998 from roc-lang/cli-platform-improvements
Basic file I/O for CLI platform
This commit is contained in:
commit
8fbd09b9ea
@ -206,7 +206,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
||||
use roc_parse::ast::TypeAnnotation::*;
|
||||
|
||||
match self {
|
||||
Function(arguments, result) => {
|
||||
Function(args, ret) => {
|
||||
let needs_parens = parens != Parens::NotNeeded;
|
||||
|
||||
buf.indent(indent);
|
||||
@ -215,7 +215,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
||||
buf.push('(')
|
||||
}
|
||||
|
||||
let mut it = arguments.iter().enumerate().peekable();
|
||||
let mut it = args.iter().enumerate().peekable();
|
||||
let should_add_newlines = newlines == Newlines::Yes;
|
||||
|
||||
while let Some((index, argument)) = it.next() {
|
||||
@ -251,12 +251,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
||||
buf.push_str("->");
|
||||
buf.spaces(1);
|
||||
|
||||
(&result.value).format_with_options(
|
||||
buf,
|
||||
Parens::InFunctionType,
|
||||
Newlines::No,
|
||||
indent,
|
||||
);
|
||||
(&ret.value).format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
|
||||
|
||||
if needs_parens {
|
||||
buf.push(')')
|
||||
@ -439,7 +434,7 @@ fn format_assigned_field_help<'a, 'buf, T>(
|
||||
}
|
||||
|
||||
buf.spaces(separator_spaces);
|
||||
buf.push_str(":");
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
@ -544,7 +539,7 @@ impl<'a> Formattable for Tag<'a> {
|
||||
|
||||
impl<'a> Formattable for HasClause<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
self.var.value.is_multiline() || self.ability.is_multiline()
|
||||
self.ability.is_multiline()
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
|
@ -220,7 +220,7 @@ impl<'a> Formattable for ValueDef<'a> {
|
||||
}
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
buf.push_str(":");
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
loc_annotation.format_with_options(
|
||||
buf,
|
||||
|
@ -190,8 +190,9 @@ impl<'a> Formattable for TypedIdent<'a> {
|
||||
buf.indent(indent);
|
||||
buf.push_str(self.ident.value);
|
||||
fmt_default_spaces(buf, self.spaces_before_colon, indent);
|
||||
buf.push_str(":");
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
|
||||
self.ann.value.format(buf, indent);
|
||||
}
|
||||
}
|
||||
|
@ -6607,7 +6607,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum RocReturn {
|
||||
/// Return as normal
|
||||
Return,
|
||||
@ -6951,7 +6951,16 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
|
||||
builder.build_return(Some(&return_value));
|
||||
}
|
||||
RocReturn::ByPointer => {
|
||||
debug_assert!(matches!(cc_return, CCReturn::ByPointer));
|
||||
match cc_return {
|
||||
CCReturn::Return => {
|
||||
let result = call.try_as_basic_value().left().unwrap();
|
||||
env.builder.build_store(return_pointer, result);
|
||||
}
|
||||
|
||||
CCReturn::ByPointer | CCReturn::Void => {
|
||||
// the return value (if any) is already written to the return pointer
|
||||
}
|
||||
}
|
||||
|
||||
builder.build_return(None);
|
||||
}
|
||||
|
@ -1,10 +1,28 @@
|
||||
hosted Effect
|
||||
exposes [Effect, after, map, always, forever, loop, putLine, getLine, sendRequest]
|
||||
imports [InternalHttp.{ Request, Response }]
|
||||
exposes [
|
||||
Effect,
|
||||
after,
|
||||
map,
|
||||
always,
|
||||
forever,
|
||||
loop,
|
||||
stdoutLine,
|
||||
stderrLine,
|
||||
stdinLine,
|
||||
sendRequest,
|
||||
fileReadBytes,
|
||||
fileWriteUtf8,
|
||||
fileWriteBytes,
|
||||
]
|
||||
imports [InternalHttp.{ Request, Response }, InternalFile]
|
||||
generates Effect with [after, map, always, forever, loop]
|
||||
|
||||
putLine : Str -> Effect {}
|
||||
stdoutLine : Str -> Effect {}
|
||||
stderrLine : Str -> Effect {}
|
||||
stdinLine : Effect Str
|
||||
|
||||
getLine : Effect Str
|
||||
fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr)
|
||||
fileWriteUtf8 : List U8, Str -> Effect (Result {} InternalFile.WriteErr)
|
||||
fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr)
|
||||
|
||||
sendRequest : Box Request -> Effect Response
|
||||
|
121
examples/interactive/cli-platform/File.roc
Normal file
121
examples/interactive/cli-platform/File.roc
Normal file
@ -0,0 +1,121 @@
|
||||
interface File
|
||||
exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes]
|
||||
imports [Effect, Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath]
|
||||
|
||||
ReadErr : InternalFile.ReadErr
|
||||
|
||||
WriteErr : InternalFile.WriteErr
|
||||
|
||||
## For example, suppose you have a [JSON](https://en.wikipedia.org/wiki/JSON)
|
||||
## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format
|
||||
## to write some encodable data to a file as JSON, like so:
|
||||
##
|
||||
## File.write
|
||||
## (Path.fromStr "output.json")
|
||||
## { some: "json stuff" }
|
||||
## Json.toCompactUtf8
|
||||
## # Writes the following to the file `output.json`:
|
||||
## #
|
||||
## # {"some":"json stuff"}
|
||||
##
|
||||
## If writing to the file fails, for example because of a file permissions issue,
|
||||
## the task fails with [WriteErr].
|
||||
##
|
||||
## This opens the file first and closes it after writing to it.
|
||||
##
|
||||
## To write unformatted bytes to a file, you can use [File.writeBytes] instead.
|
||||
write : Path, val, fmt -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]* | val has Encode.Encoding, fmt has Encode.EncoderFormatting
|
||||
write = \path, val, fmt ->
|
||||
bytes = Encode.toBytes val fmt
|
||||
|
||||
# TODO handle encoding errors here, once they exist
|
||||
writeBytes path bytes
|
||||
|
||||
## Write bytes to a file.
|
||||
##
|
||||
## # Writes the bytes 1, 2, 3 to the file `myfile.dat`.
|
||||
## File.writeBytes (Path.fromStr "myfile.dat") [1, 2, 3]
|
||||
##
|
||||
## This opens the file first and closes it after writing to it.
|
||||
##
|
||||
## To format data before writing it to a file, you can use [File.write] instead.
|
||||
writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
||||
writeBytes = \path, bytes ->
|
||||
InternalPath.toBytes path
|
||||
|> Effect.fileWriteBytes bytes
|
||||
|> InternalTask.fromEffect
|
||||
|> Task.mapFail \err -> FileWriteErr path err
|
||||
|
||||
## Write a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8).
|
||||
##
|
||||
## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`.
|
||||
## File.writeUtf8 (Path.fromStr "myfile.txt") "Hello!"
|
||||
##
|
||||
## This opens the file first and closes it after writing to it.
|
||||
##
|
||||
## To write unformatted bytes to a file, you can use [File.writeBytes] instead.
|
||||
writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]* [Write [File]*]*
|
||||
writeUtf8 = \path, str ->
|
||||
InternalPath.toBytes path
|
||||
|> Effect.fileWriteUtf8 str
|
||||
|> InternalTask.fromEffect
|
||||
|> Task.mapFail \err -> FileWriteErr path err
|
||||
|
||||
## Read all the bytes in a file.
|
||||
##
|
||||
## # Read all the bytes in `myfile.txt`.
|
||||
## File.readBytes (Path.fromStr "myfile.txt")
|
||||
##
|
||||
## This opens the file first and closes it after reading its contents.
|
||||
##
|
||||
## To read and decode data from a file, you can use `File.read` instead.
|
||||
readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]* [Read [File]*]*
|
||||
readBytes = \path ->
|
||||
InternalPath.toBytes path
|
||||
|> Effect.fileReadBytes
|
||||
|> InternalTask.fromEffect
|
||||
|> Task.mapFail \err -> FileReadErr path err
|
||||
|
||||
## Read a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text.
|
||||
##
|
||||
## # Reads UTF-8 encoded text into a `Str` from the file `myfile.txt`.
|
||||
## File.readUtf8 (Path.fromStr "myfile.txt")
|
||||
##
|
||||
## This opens the file first and closes it after writing to it.
|
||||
## The task will fail with `FileReadUtf8Err` if the given file contains invalid UTF-8.
|
||||
##
|
||||
## To read unformatted bytes from a file, you can use [File.readBytes] instead.
|
||||
readUtf8 :
|
||||
Path
|
||||
-> Task
|
||||
Str
|
||||
[FileReadErr Path ReadErr, FileReadUtf8Err Path _]*
|
||||
[Read [File]*]*
|
||||
readUtf8 = \path ->
|
||||
effect = Effect.map (Effect.fileReadBytes (InternalPath.toBytes path)) \result ->
|
||||
when result is
|
||||
Ok bytes ->
|
||||
Str.fromUtf8 bytes
|
||||
|> Result.mapErr \err -> FileReadUtf8Err path err
|
||||
|
||||
Err readErr -> Err (FileReadErr path readErr)
|
||||
|
||||
InternalTask.fromEffect effect
|
||||
|
||||
# read :
|
||||
# Path,
|
||||
# fmt
|
||||
# -> Task
|
||||
# Str
|
||||
# [FileReadErr Path ReadErr, FileReadDecodeErr Path [Leftover (List U8)]Decode.DecodeError ]*
|
||||
# [Read [File]*]*
|
||||
# | val has Decode.Decoding, fmt has Decode.DecoderFormatting
|
||||
# read = \path, fmt ->
|
||||
# effect = Effect.map (Effect.fileReadBytes (InternalPath.toBytes path)) \result ->
|
||||
# when result is
|
||||
# Ok bytes ->
|
||||
# when Decode.fromBytes bytes fmt is
|
||||
# Ok val -> Ok val
|
||||
# Err decodingErr -> Err (FileReadDecodeErr decodingErr)
|
||||
# Err readErr -> Err (FileReadErr readErr)
|
||||
# InternalTask.fromEffect effect
|
71
examples/interactive/cli-platform/InternalFile.roc
Normal file
71
examples/interactive/cli-platform/InternalFile.roc
Normal file
@ -0,0 +1,71 @@
|
||||
interface InternalFile
|
||||
exposes [ReadErr, WriteErr]
|
||||
imports []
|
||||
|
||||
ReadErr : [
|
||||
NotFound,
|
||||
Interrupted,
|
||||
InvalidFilename,
|
||||
PermissionDenied,
|
||||
TooManySymlinks, # aka FilesystemLoop
|
||||
TooManyHardlinks,
|
||||
TimedOut,
|
||||
StaleNetworkFileHandle,
|
||||
OutOfMemory,
|
||||
Unsupported,
|
||||
Unrecognized I32 Str,
|
||||
]
|
||||
|
||||
WriteErr : [
|
||||
NotFound,
|
||||
Interrupted,
|
||||
InvalidFilename,
|
||||
PermissionDenied,
|
||||
TooManySymlinks, # aka FilesystemLoop
|
||||
TooManyHardlinks,
|
||||
TimedOut,
|
||||
StaleNetworkFileHandle,
|
||||
ReadOnlyFilesystem,
|
||||
AlreadyExists, # can this happen here?
|
||||
WasADirectory,
|
||||
WriteZero, # TODO come up with a better name for this, or roll it into another error tag
|
||||
StorageFull,
|
||||
FilesystemQuotaExceeded, # can this be combined with StorageFull?
|
||||
FileTooLarge,
|
||||
ResourceBusy,
|
||||
ExecutableFileBusy,
|
||||
OutOfMemory,
|
||||
Unsupported,
|
||||
Unrecognized I32 Str,
|
||||
]
|
||||
|
||||
# DirReadErr : [
|
||||
# NotFound,
|
||||
# Interrupted,
|
||||
# InvalidFilename,
|
||||
# PermissionDenied,
|
||||
# TooManySymlinks, # aka FilesystemLoop
|
||||
# TooManyHardlinks,
|
||||
# TimedOut,
|
||||
# StaleNetworkFileHandle,
|
||||
# NotADirectory,
|
||||
# OutOfMemory,
|
||||
# Unsupported,
|
||||
# Unrecognized I32 Str,
|
||||
# ]
|
||||
# RmDirError : [
|
||||
# NotFound,
|
||||
# Interrupted,
|
||||
# InvalidFilename,
|
||||
# PermissionDenied,
|
||||
# TooManySymlinks, # aka FilesystemLoop
|
||||
# TooManyHardlinks,
|
||||
# TimedOut,
|
||||
# StaleNetworkFileHandle,
|
||||
# NotADirectory,
|
||||
# ReadOnlyFilesystem,
|
||||
# DirectoryNotEmpty,
|
||||
# OutOfMemory,
|
||||
# Unsupported,
|
||||
# Unrecognized I32 Str,
|
||||
# ]
|
@ -4,6 +4,7 @@ interface InternalPath
|
||||
InternalPath,
|
||||
wrap,
|
||||
unwrap,
|
||||
toBytes,
|
||||
]
|
||||
imports []
|
||||
|
||||
@ -50,3 +51,13 @@ wrap = @InternalPath
|
||||
|
||||
unwrap : InternalPath -> UnwrappedPath
|
||||
unwrap = \@InternalPath raw -> raw
|
||||
|
||||
## TODO do this in the host, and iterate over the Str
|
||||
## bytes when possible instead of always converting to
|
||||
## a heap-allocated List.
|
||||
toBytes : InternalPath -> List U8
|
||||
toBytes = \@InternalPath path ->
|
||||
when path is
|
||||
FromOperatingSystem bytes -> bytes
|
||||
ArbitraryBytes bytes -> bytes
|
||||
FromStr str -> Str.toUtf8 str
|
||||
|
@ -1,9 +1,15 @@
|
||||
interface InternalTask
|
||||
exposes [Task, fromEffect, toEffect]
|
||||
exposes [Task, fromEffect, toEffect, succeed, fail]
|
||||
imports [Effect.{ Effect }]
|
||||
|
||||
Task ok err fx := Effect (Result ok err)
|
||||
|
||||
succeed : ok -> Task ok * *
|
||||
succeed = \ok -> @Task (Effect.always (Ok ok))
|
||||
|
||||
fail : err -> Task * err *
|
||||
fail = \err -> @Task (Effect.always (Err err))
|
||||
|
||||
fromEffect : Effect (Result ok err) -> Task ok err *
|
||||
fromEffect = \effect -> @Task effect
|
||||
|
||||
|
@ -2,13 +2,15 @@ interface Path
|
||||
exposes [
|
||||
Path,
|
||||
PathComponent,
|
||||
CanonicalizeErr,
|
||||
WindowsRoot,
|
||||
toComponents,
|
||||
walkComponents,
|
||||
# toComponents,
|
||||
# walkComponents,
|
||||
fromStr,
|
||||
fromBytes,
|
||||
withExtension,
|
||||
]
|
||||
imports [InternalPath.{ InternalPath }, Task.{ Task }]
|
||||
imports [InternalPath.{ InternalPath }]
|
||||
|
||||
## You can canonicalize a [Path] using [Path.canonicalize].
|
||||
##
|
||||
@ -36,6 +38,10 @@ interface Path
|
||||
## paths but is considered invalid in FAT32 paths.
|
||||
Path : InternalPath
|
||||
|
||||
CanonicalizeErr a : [
|
||||
PathCanonicalizeErr {},
|
||||
]a
|
||||
|
||||
## Note that the path may not be valid depending on the filesystem where it is used.
|
||||
## For example, paths containing `:` are valid on ext4 and NTFS filesystems, but not
|
||||
## on FAT ones. So if you have multiple disks on the same machine, but they have
|
||||
@ -70,8 +76,7 @@ fromBytes = \bytes ->
|
||||
## and can access the current working directory by turning a relative path into an
|
||||
## absolute one (which can prepend the absolute path of the current working directory to
|
||||
## the relative path).
|
||||
canonicalize : Path -> Task Path (CanonicalizeErr *) [Metadata, Read [Env]]*
|
||||
|
||||
# canonicalize : Path -> Task Path (CanonicalizeErr *) [Metadata, Read [Env]]*
|
||||
## Unfortunately, operating system paths do not include information about which charset
|
||||
## they were originally encoded with. It's most common (but not guaranteed) that they will
|
||||
## have been encoded with the same charset as the operating system's curent locale (which
|
||||
@ -81,8 +86,7 @@ canonicalize : Path -> Task Path (CanonicalizeErr *) [Metadata, Read [Env]]*
|
||||
##
|
||||
## For a conversion to [Str] that is lossy but does not return a [Result], see
|
||||
## [displayUtf8].
|
||||
toInner : Path -> [Str Str, Bytes (List U8)]
|
||||
|
||||
# toInner : Path -> [Str Str, Bytes (List U8)]
|
||||
## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8),
|
||||
## and converts it to a string using [Str.displayUtf8].
|
||||
##
|
||||
@ -104,45 +108,40 @@ toInner : Path -> [Str Str, Bytes (List U8)]
|
||||
##
|
||||
## If you happen to know the [Charset] that was used to encode the path, you can use
|
||||
## [toStrUsingCharset] instead of [displayUtf8].
|
||||
displayUtf8 : Path -> Str
|
||||
displayUtf8 = \path ->
|
||||
when InternalPath.unwrap path is
|
||||
FromStr str -> str
|
||||
NoInteriorNul bytes | ArbitraryBytes bytes ->
|
||||
Str.displayUtf8 bytes
|
||||
|
||||
isEq : Path, Path -> Bool
|
||||
isEq = \p1, p2 ->
|
||||
when InternalPath.unwrap p1 is
|
||||
NoInteriorNul bytes1 | ArbitraryBytes bytes1 ->
|
||||
when InternalPath.unwrap p2 is
|
||||
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> bytes1 == bytes2
|
||||
# We can't know the encoding that was originally used in the path, so we convert
|
||||
# the string to bytes and see if those bytes are equal to the path's bytes.
|
||||
#
|
||||
# This may sound unreliable, but it's how all paths are compared; since the OS
|
||||
# doesn't record which encoding was used to encode the path name, the only
|
||||
# reasonable# definition for path equality is byte-for-byte equality.
|
||||
FromStr str2 -> Str.isEqUtf8 str2 bytes1
|
||||
|
||||
FromStr str1 ->
|
||||
when InternalPath.unwrap p2 is
|
||||
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Str.isEqUtf8 str1 bytes2
|
||||
FromStr str2 -> str1 == str2
|
||||
|
||||
compare : Path, Path -> [Lt, Eq, Gt]
|
||||
compare = \p1, p2 ->
|
||||
when InternalPath.unwrap p1 is
|
||||
NoInteriorNul bytes1 | ArbitraryBytes bytes1 ->
|
||||
when InternalPath.unwrap p2 is
|
||||
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Ord.compare bytes1 bytes2
|
||||
FromStr str2 -> Str.compareUtf8 str2 bytes1 |> Ord.reverse
|
||||
|
||||
FromStr str1 ->
|
||||
when InternalPath.unwrap p2 is
|
||||
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Str.compareUtf8 str1 bytes2
|
||||
FromStr str2 -> Ord.compare str1 str2
|
||||
|
||||
# displayUtf8 : Path -> Str
|
||||
# displayUtf8 = \path ->
|
||||
# when InternalPath.unwrap path is
|
||||
# FromStr str -> str
|
||||
# FromOperatingSystem bytes | ArbitraryBytes bytes ->
|
||||
# Str.displayUtf8 bytes
|
||||
# isEq : Path, Path -> Bool
|
||||
# isEq = \p1, p2 ->
|
||||
# when InternalPath.unwrap p1 is
|
||||
# FromOperatingSystem bytes1 | ArbitraryBytes bytes1 ->
|
||||
# when InternalPath.unwrap p2 is
|
||||
# FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> bytes1 == bytes2
|
||||
# # We can't know the encoding that was originally used in the path, so we convert
|
||||
# # the string to bytes and see if those bytes are equal to the path's bytes.
|
||||
# #
|
||||
# # This may sound unreliable, but it's how all paths are compared; since the OS
|
||||
# # doesn't record which encoding was used to encode the path name, the only
|
||||
# # reasonable# definition for path equality is byte-for-byte equality.
|
||||
# FromStr str2 -> Str.isEqUtf8 str2 bytes1
|
||||
# FromStr str1 ->
|
||||
# when InternalPath.unwrap p2 is
|
||||
# FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> Str.isEqUtf8 str1 bytes2
|
||||
# FromStr str2 -> str1 == str2
|
||||
# compare : Path, Path -> [Lt, Eq, Gt]
|
||||
# compare = \p1, p2 ->
|
||||
# when InternalPath.unwrap p1 is
|
||||
# FromOperatingSystem bytes1 | ArbitraryBytes bytes1 ->
|
||||
# when InternalPath.unwrap p2 is
|
||||
# FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> Ord.compare bytes1 bytes2
|
||||
# FromStr str2 -> Str.compareUtf8 str2 bytes1 |> Ord.reverse
|
||||
# FromStr str1 ->
|
||||
# when InternalPath.unwrap p2 is
|
||||
# FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> Str.compareUtf8 str1 bytes2
|
||||
# FromStr str2 -> Ord.compare str1 str2
|
||||
## ## Path Components
|
||||
PathComponent : [
|
||||
ParentDir, # e.g. ".." on UNIX or Windows
|
||||
@ -158,151 +157,123 @@ PathComponent : [
|
||||
## Note that a root of Slash (`/`) has different meanings on UNIX and on Windows.
|
||||
## * On UNIX, `/` at the beginning of the path refers to the filesystem root, and means the path is absolute.
|
||||
## * On Windows, `/` at the beginning of the path refers to the current disk drive, and means the path is relative.
|
||||
PathRoot : [
|
||||
WindowsSpecificRoot WindowsRoot, # e.g. "C:" on Windows
|
||||
Slash,
|
||||
None,
|
||||
]
|
||||
|
||||
# PathRoot : [
|
||||
# WindowsSpecificRoot WindowsRoot, # e.g. "C:" on Windows
|
||||
# Slash,
|
||||
# None,
|
||||
# ]
|
||||
# TODO see https://doc.rust-lang.org/std/path/enum.Prefix.html
|
||||
WindowsRoot : []
|
||||
|
||||
## Returns the root of the path.
|
||||
root : Path -> PathRoot
|
||||
|
||||
components : Path -> { root : PathRoot, components : List PathComponent }
|
||||
|
||||
# root : Path -> PathRoot
|
||||
# components : Path -> { root : PathRoot, components : List PathComponent }
|
||||
## Walk over the path's [components].
|
||||
walk :
|
||||
Path,
|
||||
# None means it's a relative path
|
||||
(PathRoot -> state),
|
||||
(state, PathComponent -> state)
|
||||
-> state
|
||||
|
||||
# walk :
|
||||
# Path,
|
||||
# # None means it's a relative path
|
||||
# (PathRoot -> state),
|
||||
# (state, PathComponent -> state)
|
||||
# -> state
|
||||
## Returns the path without its last [`component`](#components).
|
||||
##
|
||||
## If the path was empty or contained only a [root](#PathRoot), returns the original path.
|
||||
dropLast : Path -> Path
|
||||
|
||||
# dropLast : Path -> Path
|
||||
# TODO see https://doc.rust-lang.org/std/path/struct.Path.html#method.join for
|
||||
# the definition of the term "adjoin" - should we use that term?
|
||||
append : Path, Path -> Path
|
||||
append = \prefix, suffix ->
|
||||
content =
|
||||
when InternalPath.unwrap prefix is
|
||||
NoInteriorNul prefixBytes ->
|
||||
when InternalPath.unwrap suffix is
|
||||
NoInteriorNul suffixBytes ->
|
||||
# Neither prefix nor suffix had interior nuls, so the answer won't either
|
||||
List.concat prefixBytes suffixBytes
|
||||
|> NoInteriorNul
|
||||
|
||||
ArbitraryBytes suffixBytes ->
|
||||
List.concat prefixBytes suffixBytes
|
||||
|> ArbitraryBytes
|
||||
|
||||
FromStr suffixStr ->
|
||||
# Append suffixStr by writing it to the end of prefixBytes
|
||||
Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|
||||
|> ArbitraryBytes
|
||||
|
||||
ArbitraryBytes prefixBytes ->
|
||||
when InternalPath.unwrap suffix is
|
||||
ArbitraryBytes suffixBytes | NoInteriorNul suffixBytes ->
|
||||
List.concat prefixBytes suffixBytes
|
||||
|> ArbitraryBytes
|
||||
|
||||
FromStr suffixStr ->
|
||||
# Append suffixStr by writing it to the end of prefixBytes
|
||||
Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|
||||
|> ArbitraryBytes
|
||||
|
||||
FromStr prefixStr ->
|
||||
when InternalPath.unwrap suffix is
|
||||
ArbitraryBytes suffixBytes | NoInteriorNul suffixBytes ->
|
||||
List.concat suffixBytes (Str.toUtf8 prefixStr)
|
||||
|> ArbitraryBytes
|
||||
|
||||
FromStr suffixStr ->
|
||||
Str.concat prefixStr suffixStr
|
||||
|> FromStr
|
||||
|
||||
InternalPath.wrap content
|
||||
|
||||
appendStr : Path, Str -> Path
|
||||
appendStr = \prefix, suffixStr ->
|
||||
content =
|
||||
when InternalPath.unwrap prefix is
|
||||
NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes ->
|
||||
# Append suffixStr by writing it to the end of prefixBytes
|
||||
Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|
||||
|> ArbitraryBytes
|
||||
|
||||
FromStr prefixStr ->
|
||||
Str.concat prefixStr suffixStr
|
||||
|> FromStr
|
||||
|
||||
InternalPath.wrap content
|
||||
|
||||
# append : Path, Path -> Path
|
||||
# append = \prefix, suffix ->
|
||||
# content =
|
||||
# when InternalPath.unwrap prefix is
|
||||
# FromOperatingSystem prefixBytes ->
|
||||
# when InternalPath.unwrap suffix is
|
||||
# FromOperatingSystem suffixBytes ->
|
||||
# # Neither prefix nor suffix had interior nuls, so the answer won't either
|
||||
# List.concat prefixBytes suffixBytes
|
||||
# |> FromOperatingSystem
|
||||
# ArbitraryBytes suffixBytes ->
|
||||
# List.concat prefixBytes suffixBytes
|
||||
# |> ArbitraryBytes
|
||||
# FromStr suffixStr ->
|
||||
# # Append suffixStr by writing it to the end of prefixBytes
|
||||
# Str.appendToUtf8 suffixStr prefixBytes (List.len prefixBytes)
|
||||
# |> ArbitraryBytes
|
||||
# ArbitraryBytes prefixBytes ->
|
||||
# when InternalPath.unwrap suffix is
|
||||
# ArbitraryBytes suffixBytes | FromOperatingSystem suffixBytes ->
|
||||
# List.concat prefixBytes suffixBytes
|
||||
# |> ArbitraryBytes
|
||||
# FromStr suffixStr ->
|
||||
# # Append suffixStr by writing it to the end of prefixBytes
|
||||
# Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|
||||
# |> ArbitraryBytes
|
||||
# FromStr prefixStr ->
|
||||
# when InternalPath.unwrap suffix is
|
||||
# ArbitraryBytes suffixBytes | FromOperatingSystem suffixBytes ->
|
||||
# List.concat suffixBytes (Str.toUtf8 prefixStr)
|
||||
# |> ArbitraryBytes
|
||||
# FromStr suffixStr ->
|
||||
# Str.concat prefixStr suffixStr
|
||||
# |> FromStr
|
||||
# InternalPath.wrap content
|
||||
# appendStr : Path, Str -> Path
|
||||
# appendStr = \prefix, suffixStr ->
|
||||
# content =
|
||||
# when InternalPath.unwrap prefix is
|
||||
# FromOperatingSystem prefixBytes | ArbitraryBytes prefixBytes ->
|
||||
# # Append suffixStr by writing it to the end of prefixBytes
|
||||
# Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|
||||
# |> ArbitraryBytes
|
||||
# FromStr prefixStr ->
|
||||
# Str.concat prefixStr suffixStr
|
||||
# |> FromStr
|
||||
# InternalPath.wrap content
|
||||
## Returns `True` if the first path begins with the second.
|
||||
startsWith : Path, Path -> Bool
|
||||
startsWith = \path, prefix ->
|
||||
when InternalPath.unwrap path is
|
||||
NoInteriorNul pathBytes | ArbitraryBytes pathBytes ->
|
||||
when InternalPath.unwrap prefix is
|
||||
NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes ->
|
||||
List.startsWith pathBytes prefixBytes
|
||||
|
||||
FromStr prefixStr ->
|
||||
strLen = Str.countUtf8Bytes str
|
||||
|
||||
if strLen == List.len pathBytes then
|
||||
# Grab the first N bytes of the list, where N = byte length of string.
|
||||
bytesPrefix = List.takeAt pathBytes 0 strLen
|
||||
|
||||
# Compare the two for equality.
|
||||
Str.isEqUtf8 prefixStr bytesPrefix
|
||||
else
|
||||
False
|
||||
|
||||
FromStr pathStr ->
|
||||
when InternalPath.unwrap prefix is
|
||||
NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes ->
|
||||
Str.startsWithUtf8 pathStr prefixBytes
|
||||
|
||||
FromStr prefixStr ->
|
||||
Str.startsWith pathStr prefixStr
|
||||
|
||||
# startsWith : Path, Path -> Bool
|
||||
# startsWith = \path, prefix ->
|
||||
# when InternalPath.unwrap path is
|
||||
# FromOperatingSystem pathBytes | ArbitraryBytes pathBytes ->
|
||||
# when InternalPath.unwrap prefix is
|
||||
# FromOperatingSystem prefixBytes | ArbitraryBytes prefixBytes ->
|
||||
# List.startsWith pathBytes prefixBytes
|
||||
# FromStr prefixStr ->
|
||||
# strLen = Str.countUtf8Bytes prefixStr
|
||||
# if strLen == List.len pathBytes then
|
||||
# # Grab the first N bytes of the list, where N = byte length of string.
|
||||
# bytesPrefix = List.takeAt pathBytes 0 strLen
|
||||
# # Compare the two for equality.
|
||||
# Str.isEqUtf8 prefixStr bytesPrefix
|
||||
# else
|
||||
# False
|
||||
# FromStr pathStr ->
|
||||
# when InternalPath.unwrap prefix is
|
||||
# FromOperatingSystem prefixBytes | ArbitraryBytes prefixBytes ->
|
||||
# Str.startsWithUtf8 pathStr prefixBytes
|
||||
# FromStr prefixStr ->
|
||||
# Str.startsWith pathStr prefixStr
|
||||
## Returns `True` if the first path ends with the second.
|
||||
endsWith : Path, Path -> Bool
|
||||
endsWith = \path, prefix ->
|
||||
when InternalPath.unwrap path is
|
||||
NoInteriorNul pathBytes | ArbitraryBytes pathBytes ->
|
||||
when InternalPath.unwrap suffix is
|
||||
NoInteriorNul suffixBytes | ArbitraryBytes suffixBytes ->
|
||||
List.endsWith pathBytes suffixBytes
|
||||
|
||||
FromStr suffixStr ->
|
||||
strLen = Str.countUtf8Bytes suffixStr
|
||||
|
||||
if strLen == List.len pathBytes then
|
||||
# Grab the last N bytes of the list, where N = byte length of string.
|
||||
bytesSuffix = List.takeAt pathBytes (strLen - 1) strLen
|
||||
|
||||
# Compare the two for equality.
|
||||
Str.startsWithUtf8 suffixStr bytesSuffix
|
||||
else
|
||||
False
|
||||
|
||||
FromStr pathStr ->
|
||||
when InternalPath.unwrap suffix is
|
||||
NoInteriorNul suffixBytes | ArbitraryBytes suffixBytes ->
|
||||
Str.endsWithUtf8 pathStr suffixBytes
|
||||
|
||||
FromStr suffixStr ->
|
||||
Str.endsWith pathStr suffixStr
|
||||
|
||||
# endsWith : Path, Path -> Bool
|
||||
# endsWith = \path, prefix ->
|
||||
# when InternalPath.unwrap path is
|
||||
# FromOperatingSystem pathBytes | ArbitraryBytes pathBytes ->
|
||||
# when InternalPath.unwrap suffix is
|
||||
# FromOperatingSystem suffixBytes | ArbitraryBytes suffixBytes ->
|
||||
# List.endsWith pathBytes suffixBytes
|
||||
# FromStr suffixStr ->
|
||||
# strLen = Str.countUtf8Bytes suffixStr
|
||||
# if strLen == List.len pathBytes then
|
||||
# # Grab the last N bytes of the list, where N = byte length of string.
|
||||
# bytesSuffix = List.takeAt pathBytes (strLen - 1) strLen
|
||||
# # Compare the two for equality.
|
||||
# Str.startsWithUtf8 suffixStr bytesSuffix
|
||||
# else
|
||||
# False
|
||||
# FromStr pathStr ->
|
||||
# when InternalPath.unwrap suffix is
|
||||
# FromOperatingSystem suffixBytes | ArbitraryBytes suffixBytes ->
|
||||
# Str.endsWithUtf8 pathStr suffixBytes
|
||||
# FromStr suffixStr ->
|
||||
# Str.endsWith pathStr suffixStr
|
||||
# TODO https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix
|
||||
# TODO idea: what if it's File.openRead and File.openWrite? And then e.g. File.metadata,
|
||||
# File.isDir, etc.
|
||||
@ -317,16 +288,16 @@ endsWith = \path, prefix ->
|
||||
withExtension : Path, Str -> Path
|
||||
withExtension = \path, extension ->
|
||||
when InternalPath.unwrap path is
|
||||
NoInteriorNul bytes | ArbitraryBytes bytes ->
|
||||
FromOperatingSystem bytes | ArbitraryBytes bytes ->
|
||||
beforeDot =
|
||||
when List.splitLast '.' is
|
||||
when List.splitLast bytes (Num.toU8 '.') is
|
||||
Ok { before } -> before
|
||||
Err NotFound -> bytes
|
||||
|
||||
beforeDot
|
||||
|> List.reserve (1 + List.len bytes)
|
||||
|> List.append '.'
|
||||
|> List.concat bytes
|
||||
|> List.reserve (1 + Str.countUtf8Bytes extension)
|
||||
|> List.append (Num.toU8 '.')
|
||||
|> List.concat (Str.toUtf8 extension)
|
||||
|> ArbitraryBytes
|
||||
|> InternalPath.wrap
|
||||
|
||||
@ -337,9 +308,9 @@ withExtension = \path, extension ->
|
||||
Err NotFound -> str
|
||||
|
||||
beforeDot
|
||||
|> Str.reserve (1 + Str.countUtf8Bytes str)
|
||||
|> Str.reserve (1 + Str.countUtf8Bytes extension)
|
||||
|> Str.concat "."
|
||||
|> Str.concat str
|
||||
|> Str.concat extension
|
||||
|> FromStr
|
||||
|> InternalPath.wrap
|
||||
|
||||
|
8
examples/interactive/cli-platform/Stderr.roc
Normal file
8
examples/interactive/cli-platform/Stderr.roc
Normal file
@ -0,0 +1,8 @@
|
||||
interface Stderr
|
||||
exposes [line]
|
||||
imports [Effect, Task.{ Task }, InternalTask]
|
||||
|
||||
line : Str -> Task {} * [Write [Stderr]*]*
|
||||
line = \str ->
|
||||
Effect.map (Effect.stderrLine str) (\_ -> Ok {})
|
||||
|> InternalTask.fromEffect
|
@ -4,6 +4,6 @@ interface Stdin
|
||||
|
||||
line : Task Str * [Read [Stdin]*]*
|
||||
line =
|
||||
Effect.getLine
|
||||
Effect.stdinLine
|
||||
|> Effect.map Ok
|
||||
|> InternalTask.fromEffect
|
||||
|
@ -4,5 +4,5 @@ interface Stdout
|
||||
|
||||
line : Str -> Task {} * [Write [Stdout]*]*
|
||||
line = \str ->
|
||||
Effect.map (Effect.putLine str) (\_ -> Ok {})
|
||||
Effect.map (Effect.stdoutLine str) (\_ -> Ok {})
|
||||
|> InternalTask.fromEffect
|
||||
|
@ -1,5 +1,5 @@
|
||||
interface Task
|
||||
exposes [Task, succeed, fail, await, map, onFail, attempt, forever, loop]
|
||||
exposes [Task, succeed, fail, await, map, mapFail, onFail, attempt, forever, loop]
|
||||
imports [Effect, InternalTask]
|
||||
|
||||
Task ok err fx : InternalTask.Task ok err fx
|
||||
@ -33,15 +33,11 @@ loop = \state, step ->
|
||||
Effect.loop state looper
|
||||
|> InternalTask.fromEffect
|
||||
|
||||
succeed : val -> Task val * *
|
||||
succeed = \val ->
|
||||
Effect.always (Ok val)
|
||||
|> InternalTask.fromEffect
|
||||
succeed : ok -> Task ok * *
|
||||
succeed = \ok -> InternalTask.succeed ok
|
||||
|
||||
fail : err -> Task * err *
|
||||
fail = \val ->
|
||||
Effect.always (Err val)
|
||||
|> InternalTask.fromEffect
|
||||
fail = \err -> InternalTask.fail err
|
||||
|
||||
attempt : Task a b fx, (Result a b -> Task c d fx) -> Task c d fx
|
||||
attempt = \task, transform ->
|
||||
@ -82,7 +78,18 @@ map = \task, transform ->
|
||||
(InternalTask.toEffect task)
|
||||
\result ->
|
||||
when result is
|
||||
Ok a -> Task.succeed (transform a) |> InternalTask.toEffect
|
||||
Ok ok -> Task.succeed (transform ok) |> InternalTask.toEffect
|
||||
Err err -> Task.fail err |> InternalTask.toEffect
|
||||
|
||||
InternalTask.fromEffect effect
|
||||
|
||||
mapFail : Task ok a fx, (a -> b) -> Task ok b fx
|
||||
mapFail = \task, transform ->
|
||||
effect = Effect.after
|
||||
(InternalTask.toEffect task)
|
||||
\result ->
|
||||
when result is
|
||||
Ok ok -> Task.succeed ok |> InternalTask.toEffect
|
||||
Err err -> Task.fail (transform err) |> InternalTask.toEffect
|
||||
|
||||
InternalTask.fromEffect effect
|
||||
|
2643
examples/interactive/cli-platform/src/file_glue.rs
Normal file
2643
examples/interactive/cli-platform/src/file_glue.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
mod file_glue;
|
||||
mod glue;
|
||||
|
||||
use core::alloc::Layout;
|
||||
@ -7,11 +8,16 @@ use core::ffi::c_void;
|
||||
use core::mem::MaybeUninit;
|
||||
use glue::Metadata;
|
||||
use libc;
|
||||
use roc_std::{RocList, RocStr};
|
||||
use std::ffi::CStr;
|
||||
use roc_std::{RocList, RocResult, RocStr};
|
||||
use std::ffi::{CStr, OsStr};
|
||||
use std::fs::File;
|
||||
use std::os::raw::c_char;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use file_glue::ReadErr;
|
||||
use file_glue::WriteErr;
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "roc__mainForHost_1_exposed_generic"]
|
||||
fn roc_main(output: *mut u8);
|
||||
@ -113,7 +119,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_getLine() -> RocStr {
|
||||
pub extern "C" fn roc_fx_stdinLine() -> RocStr {
|
||||
use std::io::{self, BufRead};
|
||||
|
||||
let stdin = io::stdin();
|
||||
@ -123,11 +129,94 @@ pub extern "C" fn roc_fx_getLine() -> RocStr {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_putLine(line: &RocStr) {
|
||||
pub extern "C" fn roc_fx_stdoutLine(line: &RocStr) {
|
||||
let string = line.as_str();
|
||||
println!("{}", string);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_stderrLine(line: &RocStr) {
|
||||
let string = line.as_str();
|
||||
eprintln!("{}", string);
|
||||
}
|
||||
|
||||
// #[no_mangle]
|
||||
// pub extern "C" fn roc_fx_fileWriteUtf8(
|
||||
// roc_path: &RocList<u8>,
|
||||
// roc_string: &RocStr,
|
||||
// // ) -> RocResult<(), WriteErr> {
|
||||
// ) -> (u8, u8) {
|
||||
// let _ = write_slice(roc_path, roc_string.as_str().as_bytes());
|
||||
|
||||
// (255, 255)
|
||||
// }
|
||||
|
||||
type Fail = Foo;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Foo {
|
||||
data: u8,
|
||||
tag: u8,
|
||||
}
|
||||
|
||||
// #[no_mangle]
|
||||
// pub extern "C" fn roc_fx_fileWriteUtf8(roc_path: &RocList<u8>, roc_string: &RocStr) -> Fail {
|
||||
// write_slice2(roc_path, roc_string.as_str().as_bytes())
|
||||
// }
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_fileWriteUtf8(
|
||||
roc_path: &RocList<u8>,
|
||||
roc_str: &RocStr,
|
||||
) -> RocResult<(), WriteErr> {
|
||||
write_slice(roc_path, roc_str.as_str().as_bytes())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_fileWriteBytes(
|
||||
roc_path: &RocList<u8>,
|
||||
roc_bytes: &RocList<u8>,
|
||||
) -> RocResult<(), WriteErr> {
|
||||
write_slice(roc_path, roc_bytes.as_slice())
|
||||
}
|
||||
|
||||
fn write_slice(roc_path: &RocList<u8>, bytes: &[u8]) -> RocResult<(), WriteErr> {
|
||||
use std::io::Write;
|
||||
|
||||
match File::create(path_from_roc_path(roc_path)) {
|
||||
Ok(mut file) => match file.write_all(bytes) {
|
||||
Ok(()) => RocResult::ok(()),
|
||||
Err(_) => {
|
||||
todo!("Report a file write error");
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
todo!("Report a file open error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: do this on Windows too. This may be trickier because it's unclear
|
||||
/// whether we want to use wide encoding (in which case we have to convert from
|
||||
/// &[u8] to &[u16] by converting UTF-8 to UTF-16) and then windows::OsStrExt::from_wide -
|
||||
/// https://doc.rust-lang.org/std/os/windows/ffi/trait.OsStringExt.html#tymethod.from_wide -
|
||||
/// or whether we want to try to set the Windows code page to UTF-8 instead.
|
||||
#[cfg(target_family = "unix")]
|
||||
fn path_from_roc_path(bytes: &RocList<u8>) -> &Path {
|
||||
Path::new(os_str_from_list(bytes))
|
||||
}
|
||||
|
||||
pub fn os_str_from_list(bytes: &RocList<u8>) -> &OsStr {
|
||||
std::os::unix::ffi::OsStrExt::from_bytes(bytes.as_slice())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_fileReadBytes(path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr> {
|
||||
let path = path_from_roc_path(path);
|
||||
println!("TODO read bytes from {:?}", path);
|
||||
|
||||
RocResult::ok(RocList::empty())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn roc_fx_sendRequest(roc_request: &glue::Request) -> glue::Response {
|
||||
let mut builder = reqwest::blocking::ClientBuilder::new();
|
||||
|
17
examples/interactive/file.roc
Normal file
17
examples/interactive/file.roc
Normal file
@ -0,0 +1,17 @@
|
||||
app "echo"
|
||||
packages { pf: "cli-platform/main.roc" }
|
||||
imports [pf.Stdout, pf.Stderr, pf.Task, pf.File, pf.Path]
|
||||
provides [main] to pf
|
||||
|
||||
main : Task.Task {} [] [Write [File, Stdout, Stderr]]
|
||||
main =
|
||||
task =
|
||||
_ <- Stdout.line "Writing a string to out.txt" |> Task.await
|
||||
File.writeUtf8 (Path.fromStr "out.txt") "a string!\n"
|
||||
|
||||
Task.attempt task \result ->
|
||||
when result is
|
||||
Err (FileWriteErr _ PermissionDenied) -> Stderr.line "Err: PermissionDenied"
|
||||
Err (FileWriteErr _ Unsupported) -> Stderr.line "Err: Unsupported"
|
||||
Err (FileWriteErr _ (Unrecognized _ other)) -> Stderr.line "Err: \(other)"
|
||||
_ -> Stdout.line "Successfully wrote a string to out.txt"
|
Loading…
Reference in New Issue
Block a user