Merge pull request #3998 from roc-lang/cli-platform-improvements

Basic file I/O for CLI platform
This commit is contained in:
Jan Van Bruggen 2022-09-11 19:33:22 -06:00 committed by GitHub
commit 8fbd09b9ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 3187 additions and 220 deletions

View File

@ -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>(

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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

View 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

View 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,
# ]

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -4,6 +4,6 @@ interface Stdin
line : Task Str * [Read [Stdin]*]*
line =
Effect.getLine
Effect.stdinLine
|> Effect.map Ok
|> InternalTask.fromEffect

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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();

View 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"