diff --git a/Makefile b/Makefile
index a29ae78a5..8685d318d 100644
--- a/Makefile
+++ b/Makefile
@@ -71,7 +71,7 @@ idris2: src/YafflePaths.idr check_version
idris2c: dist/idris2.c
${MAKE} -C dist
-src/YafflePaths.idr: FORCE
+src/YafflePaths.idr:
echo 'module YafflePaths; import IdrisJvm.IO; import Java.Lang; export yversion : ((Nat,Nat,Nat), String); yversion = ((${MAJOR},${MINOR},${PATCH}), "${GIT_SHA1}")' > src/YafflePaths.idr
echo 'export yprefix : String' >> src/YafflePaths.idr
echo 'yprefix = home ++ "/.idris2boot" where' >> src/YafflePaths.idr
@@ -84,8 +84,6 @@ src/YafflePaths.idr: FORCE
echo ' idrisHomeEnv <- System.getenv "IDRIS2_BOOT_HOME"' >> src/YafflePaths.idr
echo ' maybe (System.getPropertyWithDefault "user.home" "") pure idrisHomeEnv' >> src/YafflePaths.idr
-FORCE:
-
prelude:
${MAKE} -C libs/prelude IDRIS2=../../idris2boot
diff --git a/compiler/pom.xml b/compiler/pom.xml
index 545c6686e..167d2de78 100644
--- a/compiler/pom.xml
+++ b/compiler/pom.xml
@@ -15,9 +15,8 @@
8
8
-
false
- false
+ false
@@ -40,7 +39,6 @@
idris2
- ${project.build.directory}
${project.build.directory}
@@ -52,34 +50,17 @@
exec
- ${skipIdrisCompile}
+ ${skipIdrisInstallLibrary}
make
${project.parent.basedir}
install-libs
- ${project.build.directory}
${project.build.directory}
-
- idris-test
- test
-
- exec
-
-
- ${skipTests}
- make
- ${project.parent.basedir}
-
- test
- ${idris.tests}
-
-
-
idris-clean
clean
diff --git a/jvm-assembler/pom.xml b/jvm-assembler/pom.xml
index d016d2078..9490a66e5 100644
--- a/jvm-assembler/pom.xml
+++ b/jvm-assembler/pom.xml
@@ -12,6 +12,11 @@
jvm-assembler
Idris 2 JVM Bootstrap Assembler
+
+
+ false
+
+
@@ -24,11 +29,13 @@
copy-resources
- ${project.build.directory}/idris2-boot-jvm/.idris2boot/idris2-0.1.1
+ ${project.build.directory}/idris2-boot-jvm/.idris2boot/idris2-0.1.1
+
true
- ${user.home}/.idris2boot/idris2-0.1.1
+ ${project.parent.basedir}/compiler/target/.idris2boot/idris2-0.1.1
+
@@ -59,7 +66,6 @@
-
maven-assembly-plugin
@@ -78,6 +84,28 @@
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ idris-test
+ integration-test
+
+ exec
+
+
+ ${skipTests}
+ make
+ ${project.parent.basedir}
+
+ test
+ ${idris.tests}
+
+
+
+
+
diff --git a/libs/base/System/File.idr b/libs/base/System/File.idr
index 3906427cb..85357563f 100644
--- a/libs/base/System/File.idr
+++ b/libs/base/System/File.idr
@@ -35,16 +35,16 @@ prim_error : FilePtr -> PrimIO Int
prim_fileErrno : PrimIO Int
%foreign support "idris2_readLine"
- jvm' fileClass "getLine" fileClass "String"
+ jvm' fileClass "readLine" fileClass "String"
prim__readLine : FilePtr -> PrimIO (Ptr String)
%foreign support "idris2_readChars"
- jvm' fileClass "getChars" ("int " ++ fileClass) "String"
+ jvm' fileClass "readChars" ("int " ++ fileClass) "String"
prim__readChars : Int -> FilePtr -> PrimIO (Ptr String)
%foreign support "fgetc"
- jvm' fileClass "getChar" fileClass "char"
+ jvm' fileClass "readChar" fileClass "char"
prim__readChar : FilePtr -> PrimIO Char
%foreign support "idris2_writeLine"
- jvm' fileClass "writeString" (fileClass ++ " String") "int"
+ jvm' fileClass "writeLine" (fileClass ++ " String") "int"
prim__writeLine : FilePtr -> String -> PrimIO Int
%foreign support "idris2_eof"
jvm' fileClass "isEof" fileClass "int"
@@ -63,13 +63,13 @@ prim__fileSize : FilePtr -> PrimIO Int
prim__fPoll : FilePtr -> PrimIO Int
%foreign support "idris2_fileAccessTime"
- jvm' fileClass "getFileAccessTime" fileClass "int"
+ jvm' fileClass "getAccessTime" fileClass "int"
prim__fileAccessTime : FilePtr -> PrimIO Int
%foreign support "idris2_fileModifiedTime"
- jvm' fileClass "getFileModifiedTime" fileClass "int"
+ jvm' fileClass "getModifiedTime" fileClass "int"
prim__fileModifiedTime : FilePtr -> PrimIO Int
%foreign support "idris2_fileStatusTime"
- jvm' fileClass "getFileStatusTime" fileClass "int"
+ jvm' fileClass "getStatusTime" fileClass "int"
prim__fileStatusTime : FilePtr -> PrimIO Int
%foreign support "idris2_stdin"
diff --git a/libs/network/Network/Socket.idr b/libs/network/Network/Socket.idr
index 2d201c04b..4693e3488 100644
--- a/libs/network/Network/Socket.idr
+++ b/libs/network/Network/Socket.idr
@@ -7,8 +7,42 @@ module Network.Socket
import public Network.Socket.Data
import Network.Socket.Raw
import Data.List
+import System.FFI
+
+idrisSocketClass : String
+idrisSocketClass = "io/github/mmhelloworld/idris2boot/runtime/IdrisSocket"
-- ----------------------------------------------------- [ Network Socket API. ]
+%foreign
+ jvm' idrisSocketClass "create" "int int int" idrisSocketClass
+prim_createSocket : Int -> Int -> Int -> PrimIO SocketDescriptor
+
+%foreign
+ jvm' idrisSocketClass ".close" idrisSocketClass "void"
+prim_closeSocket : AnyPtr -> PrimIO ()
+
+%foreign
+ jvm' idrisSocketClass ".bind"
+ "io/github/mmhelloworld/idris2boot/runtime/IdrisSocket int int java/lang/String int" "int"
+prim_bindSocket : AnyPtr -> Int -> Int -> String -> Int -> PrimIO Int
+
+%foreign
+ jvm' idrisSocketClass ".connect"
+ "io/github/mmhelloworld/idris2boot/runtime/IdrisSocket int int java/lang/String int" "int"
+prim_connectSocket : AnyPtr -> Int -> Int -> String -> Int -> PrimIO Int
+
+%foreign
+ jvm' idrisSocketClass ".listen" "io/github/mmhelloworld/idris2boot/runtime/IdrisSocket int" "int"
+prim_listenSocket : AnyPtr -> Int -> PrimIO Int
+
+%foreign
+ jvm' idrisSocketClass ".accept"
+ "io/github/mmhelloworld/idris2boot/runtime/IdrisSocket java/lang/Object" idrisSocketClass
+prim_acceptSocket : AnyPtr -> AnyPtr -> PrimIO SocketDescriptor
+
+%foreign
+ jvm idrisSocketClass "createSocketAddress"
+prim_createSocketAddress : PrimIO AnyPtr
||| Creates a UNIX socket with the given family, socket type and protocol
||| number. Returns either a socket or an error.
@@ -18,16 +52,16 @@ socket : (fam : SocketFamily)
-> (pnum : ProtocolNumber)
-> IO (Either SocketError Socket)
socket sf st pn = do
- socket_res <- cCall Int "idrnet_socket" [toCode sf, toCode st, pn]
-
- if socket_res == -1
- then map Left getErrno
+ socket_res <- primIO $ prim_createSocket (toCode sf) (toCode st) pn
+ errorNumber <- getErrno
+ if errorNumber /= 0
+ then pure $ Left errorNumber
else pure $ Right (MkSocket socket_res sf st pn)
||| Close a socket
export
close : Socket -> IO ()
-close sock = cCall () "close" [descriptor sock]
+close sock = primIO $ prim_closeSocket (descriptor sock)
||| Binds a socket to the given socket address and port.
||| Returns 0 on success, an error code otherwise.
@@ -37,10 +71,8 @@ bind : (sock : Socket)
-> (port : Port)
-> IO Int
bind sock addr port = do
- bind_res <- cCall Int "idrnet_bind"
- [ descriptor sock, toCode $ family sock
- , toCode $ socketType sock, saString addr, port
- ]
+ bind_res <- primIO $ prim_bindSocket (descriptor sock) (toCode $ family sock) (toCode $ socketType sock)
+ (saString addr) port
if bind_res == (-1)
then getErrno
else pure 0
@@ -57,9 +89,8 @@ connect : (sock : Socket)
-> (port : Port)
-> IO ResultCode
connect sock addr port = do
- conn_res <- cCall Int "idrnet_connect"
- [ descriptor sock, toCode $ family sock, toCode $ socketType sock, show addr, port]
-
+ conn_res <- primIO $ prim_connectSocket (descriptor sock) (toCode $ family sock) (toCode $ socketType sock)
+ (show addr) port
if conn_res == (-1)
then getErrno
else pure 0
@@ -70,7 +101,7 @@ connect sock addr port = do
export
listen : (sock : Socket) -> IO Int
listen sock = do
- listen_res <- cCall Int "listen" [ descriptor sock, BACKLOG ]
+ listen_res <- primIO $ prim_listenSocket (descriptor sock) BACKLOG
if listen_res == (-1)
then getErrno
else pure 0
@@ -91,17 +122,23 @@ accept sock = do
-- We need a pointer to a sockaddr structure. This is then passed into
-- idrnet_accept and populated. We can then query it for the SocketAddr and free it.
- sockaddr_ptr <- cCall AnyPtr "idrnet_create_sockaddr" []
+ sockaddr_ptr <- primIO prim_createSocketAddress
- accept_res <- cCall Int "idrnet_accept" [ descriptor sock, sockaddr_ptr ]
- if accept_res == (-1)
- then map Left getErrno
+ accept_res <- primIO $ prim_acceptSocket (descriptor sock) sockaddr_ptr
+ errorNumber <- getErrno
+ if errorNumber /= 0
+ then pure $ Left errorNumber
else do
let (MkSocket _ fam ty p_num) = sock
sockaddr <- getSockAddr (SAPtr sockaddr_ptr)
sockaddr_free (SAPtr sockaddr_ptr)
pure $ Right ((MkSocket accept_res fam ty p_num), sockaddr)
+%foreign
+ jvm' idrisSocketClass ".send"
+ "io/github/mmhelloworld/idris2boot/runtime/IdrisSocket java/lang/String" "int"
+prim_sendSocket : SocketDescriptor -> String -> PrimIO Int
+
||| Send data on the specified socket.
|||
||| Returns on failure a `SocketError`.
@@ -114,12 +151,17 @@ send : (sock : Socket)
-> (msg : String)
-> IO (Either SocketError ResultCode)
send sock dat = do
- send_res <- cCall Int "idrnet_send" [ descriptor sock, dat ]
+ send_res <- primIO $ prim_sendSocket (descriptor sock) dat
if send_res == (-1)
then map Left getErrno
else pure $ Right send_res
+%foreign
+ jvm' idrisSocketClass ".receive"
+ "io/github/mmhelloworld/idris2boot/runtime/IdrisSocket int" "java/lang/String"
+prim_receiveSocket : SocketDescriptor -> Int -> PrimIO String
+
||| Receive data on the specified socket.
|||
||| Returns on failure a `SocketError`
@@ -136,23 +178,11 @@ recv : (sock : Socket)
recv sock len = do
-- Firstly make the request, get some kind of recv structure which
-- contains the result of the recv and possibly the retrieved payload
- recv_struct_ptr <- cCall AnyPtr "idrnet_recv" [ descriptor sock, len]
- recv_res <- cCall Int "idrnet_get_recv_res" [ recv_struct_ptr ]
-
- if recv_res == (-1)
- then do
- errno <- getErrno
- freeRecvStruct (RSPtr recv_struct_ptr)
- pure $ Left errno
- else
- if recv_res == 0
- then do
- freeRecvStruct (RSPtr recv_struct_ptr)
- pure $ Left 0
- else do
- payload <- cCall String "idrnet_get_recv_payload" [ recv_struct_ptr ]
- freeRecvStruct (RSPtr recv_struct_ptr)
- pure $ Right (payload, recv_res)
+ payload <- primIO $ prim_receiveSocket (descriptor sock) len
+ errorNumber <- getErrno
+ if errorNumber /= 0
+ then pure $ Left errorNumber
+ else pure $ Right (payload, len)
||| Receive all the remaining data on the specified socket.
|||
diff --git a/libs/network/Network/Socket/Data.idr b/libs/network/Network/Socket/Data.idr
index 887a4d561..8d2639581 100644
--- a/libs/network/Network/Socket/Data.idr
+++ b/libs/network/Network/Socket/Data.idr
@@ -8,6 +8,7 @@ module Network.Socket.Data
import Data.List
import Data.List1
import Data.Strings
+import System.FFI
-- ------------------------------------------------------------ [ Type Aliases ]
@@ -36,7 +37,7 @@ SocketError = Int
||| SocketDescriptor: Native C Socket Descriptor
public export
SocketDescriptor : Type
-SocketDescriptor = Int
+SocketDescriptor = AnyPtr
public export
Port : Type
@@ -51,20 +52,20 @@ BACKLOG = 20
export
EAGAIN : Int
-EAGAIN =
- -- I'm sorry
- -- maybe
- unsafePerformIO $ cCall Int "idrnet_geteagain" []
+EAGAIN = 11
-- ---------------------------------------------------------------- [ Error Code ]
+%foreign
+ jvm runtimeClass "getErrorNumber"
+prim_getSocketErrorNumber : PrimIO Int
export
getErrno : IO SocketError
-getErrno = cCall Int "idrnet_errno" []
+getErrno = primIO prim_getSocketErrorNumber
export
nullPtr : AnyPtr -> IO Bool
-nullPtr p = cCall Bool "isNull" [p]
+nullPtr p = pure $ prim__nullAnyPtr p /= 0
-- -------------------------------------------------------------- [ Interfaces ]
diff --git a/libs/network/Network/Socket/Raw.idr b/libs/network/Network/Socket/Raw.idr
index e1373a4c4..633edabc6 100644
--- a/libs/network/Network/Socket/Raw.idr
+++ b/libs/network/Network/Socket/Raw.idr
@@ -6,7 +6,7 @@
module Network.Socket.Raw
import public Network.Socket.Data
-
+import System.FFI
-- ---------------------------------------------------------------- [ Pointers ]
public export
@@ -21,6 +21,9 @@ data BufPtr = BPtr AnyPtr
public export
data SockaddrPtr = SAPtr AnyPtr
+idrisSocketClass : String
+idrisSocketClass = "io/github/mmhelloworld/idris2boot/runtime/IdrisSocket"
+
-- ---------------------------------------------------------- [ Socket Utilies ]
||| Put a value in a buffer
@@ -33,14 +36,18 @@ export
sock_peek : BufPtr -> Int -> IO Int
sock_peek (BPtr ptr) offset = cCall Int "idrnet_peek" [ptr, offset]
+%foreign
+ jvm idrisSocketClass "free"
+prim_freeSocketPointer : AnyPtr -> PrimIO ()
+
||| Frees a given pointer
export
sock_free : BufPtr -> IO ()
-sock_free (BPtr ptr) = cCall () "idrnet_free" [ptr]
+sock_free (BPtr ptr) = primIO $ prim_freeSocketPointer ptr
export
sockaddr_free : SockaddrPtr -> IO ()
-sockaddr_free (SAPtr ptr) = cCall () "idrnet_free" [ptr]
+sockaddr_free (SAPtr ptr) = primIO $ prim_freeSocketPointer ptr
||| Allocates an amount of memory given by the ByteLength parameter.
|||
@@ -49,23 +56,33 @@ export
sock_alloc : ByteLength -> IO BufPtr
sock_alloc bl = map BPtr $ cCall AnyPtr "idrnet_malloc" [bl]
+%foreign
+ jvm' idrisSocketClass ".getSocketPort" idrisSocketClass "int"
+prim_getSocketPort : AnyPtr -> PrimIO Int
+
||| Retrieves the port the given socket is bound to
export
getSockPort : Socket -> IO Port
-getSockPort sock = cCall Int "idrnet_sockaddr_port" [descriptor sock]
+getSockPort sock = primIO $ prim_getSocketPort (descriptor sock)
+
+%foreign
+ jvm' idrisSocketClass "getSocketAddressFamily" "java/lang/Object" "int"
+prim_getSocketFamily : AnyPtr -> PrimIO Int
+%foreign
+ jvm' idrisSocketClass "getSocketAddressHostName" "java/lang/Object" "java/lang/String"
+prim_getSocketAddressHostName : AnyPtr -> PrimIO String
||| Retrieves a socket address from a sockaddr pointer
export
getSockAddr : SockaddrPtr -> IO SocketAddress
getSockAddr (SAPtr ptr) = do
- addr_family_int <- cCall Int "idrnet_sockaddr_family" [ptr]
+ addr_family_int <- primIO $ prim_getSocketFamily ptr
-- ASSUMPTION: Foreign call returns a valid int
assert_total (case getSocketFamily addr_family_int of
Just AF_INET => do
- ipv4_addr <- cCall String "idrnet_sockaddr_ipv4" [ptr]
-
+ ipv4_addr <- primIO $ prim_getSocketAddressHostName ptr
pure $ parseIPv4 ipv4_addr
Just AF_INET6 => pure IPv6Addr
Just AF_UNSPEC => pure InvalidAddress)
diff --git a/libs/prelude/PrimIO.idr b/libs/prelude/PrimIO.idr
index 518f602b1..12a702e47 100644
--- a/libs/prelude/PrimIO.idr
+++ b/libs/prelude/PrimIO.idr
@@ -139,13 +139,14 @@ export
getChar : IO Char
getChar = primIO prim__getChar
+%foreign "C:idris2_getStr,libidris2_support"
+ "jvm:fork(java/util/function/Function#apply#java/lang/Object#java/lang/Object java/lang/Thread),io/github/mmhelloworld/idris2boot/runtime/Runtime"
export
-fork : (1 prog : IO ()) -> IO ThreadID
-fork (MkIO act) = schemeCall ThreadID "blodwen-thread" [act]
+prim_fork : (1 prog : PrimIO ()) -> PrimIO ThreadID
export
-prim_fork : (1 prog : PrimIO ()) -> PrimIO ThreadID
-prim_fork act w = prim__schemeCall ThreadID "blodwen-thread" [act] w
+fork : (1 prog : IO ()) -> IO ThreadID
+fork (MkIO act) = primIO (prim_fork act)
%foreign "C:idris2_readString, libidris2_support"
export
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ChannelIo.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ChannelIo.java
index 14bde36c6..82ebaa7a5 100644
--- a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ChannelIo.java
+++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ChannelIo.java
@@ -25,7 +25,6 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
-import java.util.stream.IntStream;
import static io.github.mmhelloworld.idris2boot.runtime.Paths.createPath;
import static io.github.mmhelloworld.idris2boot.runtime.Runtime.setErrorNumber;
@@ -44,7 +43,7 @@
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toSet;
-public class ChannelIo implements ReadableByteChannel, WritableByteChannel, Closeable {
+public class ChannelIo implements ReadableByteChannel, WritableByteChannel, Closeable, IdrisFile {
private static final boolean IS_POSIX = FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
private static final Map modeToPermissions = new HashMap<>();
@@ -79,36 +78,34 @@ public class ChannelIo implements ReadableByteChannel, WritableByteChannel, Clos
this.byteBufferIo = new ByteBufferIo(reader, writer);
}
+ ChannelIo(Channel channel, ByteBufferIo byteBufferIo) {
+ this.path = null;
+ this.channel = channel;
+ this.byteBufferIo = byteBufferIo;
+ }
+
public ChannelIo(Path path) {
this(path, null);
}
- public static char getChar(ChannelIo file) {
- return file.getChar();
+ public static char readChar(ChannelIo file) {
+ return file.readChar();
}
- public static String getChars(int count, ChannelIo file) {
- return file.getChars(count);
+ public static String readChars(int count, ChannelIo file) {
+ return file.readChars(count);
}
- public static String getLine(ChannelIo file) {
- return file.getLine();
+ public static String readLine(ChannelIo file) {
+ return file.readLine();
}
- public static int writeString(ChannelIo file, String str) {
- file.writeString(str);
- return file.exception != null ? 0 : 1;
+ public static int writeLine(ChannelIo file, String str) {
+ return file.writeLine(str);
}
public static int isEof(ChannelIo file) {
- return file.isEof() || file.exception != null ? 1 : 0;
- }
-
- public static ChannelIo open(Path path, OpenOption... openOptions) throws IOException {
- if (path.getParent() != null) {
- Files.createDirectories(path.getParent());
- }
- return new ChannelIo(path, FileChannel.open(path, openOptions));
+ return file.isEof();
}
public static ChannelIo open(String name, String mode) {
@@ -119,7 +116,7 @@ public static ChannelIo open(String name, String mode) {
}
return open(path, getOpenOptions(mode).toArray(new OpenOption[]{}));
} catch (Exception exception) {
- setErrorNumber(getErrorNumber(exception));
+ setErrorNumber(ChannelIo.getErrorNumber(exception));
return null;
}
}
@@ -143,93 +140,91 @@ public static void writeFile(String pathString, String content) throws IOExcepti
Path path = createPath(pathString);
byte[] bytes = content.getBytes(UTF_8);
createDirectories(path.getParent());
- Files.write(path, bytes);
+ java.nio.file.Files.write(path, bytes);
}
public static int flush(ChannelIo file) {
- file.flush();
- return file.exception == null ? 0 : 1;
+ return file.flush();
}
public static int size(ChannelIo file) {
- return (int) file.size();
+ return file.size();
}
public static int delete(ChannelIo file) {
return file.delete();
}
- public static int getFileModifiedTime(ChannelIo file) {
- return (int) file.getFileModifiedTime();
+ public static int getModifiedTime(ChannelIo file) {
+ return (int) file.getModifiedTime();
}
- public static int getFileAccessTime(ChannelIo file) {
- return (int) file.getFileAccessTime();
+ public static int getAccessTime(ChannelIo file) {
+ return file.getAccessTime();
}
- public static int getFileStatusTime(ChannelIo file) {
- return (int) file.getFileStatusTime();
+ public static int getStatusTime(ChannelIo file) {
+ return (int) file.getStatusTime();
}
public static int getErrorNumber(ChannelIo file) {
- return getErrorNumber(file.exception);
+ return file.getErrorNumber();
}
- public char getChar() {
+ @Override
+ public char readChar() {
return (char) withExceptionHandling(byteBufferIo::getChar);
}
- public String getChars(int count) {
- return withExceptionHandling(() -> {
- String result = IntStream.range(0, count)
- .map(index -> getChar())
- .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
- .toString();
- return exception != null ? result : null;
- });
- }
-
- public String getLine() {
+ @Override
+ public String readLine() {
return withExceptionHandling(byteBufferIo::getLine);
}
public void handleException(Exception e) {
this.exception = e;
+ if (exception != null) {
+ exception.printStackTrace();
+ }
Runtime.setErrorNumber(getErrorNumber(e));
}
- public void writeString(String str) {
+ @Override
+ public int writeLine(String str) {
withExceptionHandling(() -> {
byteBufferIo.writeString(str);
flush();
return null;
});
+ return exception != null ? 0 : 1;
}
public int chmod(int mode) {
return withExceptionHandling(() -> {
- if (IS_POSIX) {
+ if (IS_POSIX && path != null) {
Files.setPosixFilePermissions(path, createPosixFilePermissions(mode));
}
return 0;
}, -1);
}
- public void flush() {
+ public int flush() {
withExceptionHandling(() -> {
if (channel instanceof FileChannel) {
((FileChannel) channel).force(true);
}
return null;
});
+ return exception == null ? 0 : 1;
}
- public boolean isEof() {
- return withExceptionHandling(() -> !byteBufferIo.hasChar(), true);
+ public int isEof() {
+ boolean isEof = withExceptionHandling(() -> !byteBufferIo.hasChar(), true);
+ return isEof || exception != null ? 1 : 0;
}
- public long size() {
- return withExceptionHandling(() -> {
+ public int size() {
+ return (int) withExceptionHandling(() -> {
if (channel instanceof SeekableByteChannel) {
return ((SeekableByteChannel) channel).size();
} else {
@@ -253,28 +248,52 @@ public void close() {
});
}
+ @Override
+ public int getErrorNumber() {
+ return getErrorNumber(exception);
+ }
+
+ @Override
+ public String readChars(int count) {
+ return withExceptionHandling(() -> {
+ int index = 0;
+ StringBuilder builder = new StringBuilder();
+ while (index < count && byteBufferIo.hasChar()) {
+ builder.append(readChar());
+ index++;
+ }
+ return exception != null ? builder.toString() : null;
+ });
+ }
+
public int delete() {
return withExceptionHandling(() -> {
- Files.delete(path);
+ if (path != null) {
+ Files.delete(path);
+ }
return 0;
}, -1);
}
- public long getFileModifiedTime() {
- return getTimeAttribute(BasicFileAttributes::lastModifiedTime);
+ @Override
+ public int getModifiedTime() {
+ return (int) getTimeAttribute(BasicFileAttributes::lastModifiedTime);
}
- public long getFileAccessTime() {
- return getTimeAttribute(BasicFileAttributes::lastAccessTime);
+ @Override
+ public int getAccessTime() {
+ return (int) getTimeAttribute(BasicFileAttributes::lastAccessTime);
}
- public long getFileStatusTime() {
- return getTimeAttribute(BasicFileAttributes::creationTime);
+ @Override
+ public int getStatusTime() {
+ return (int) getTimeAttribute(BasicFileAttributes::creationTime);
}
public long getTimeAttribute(Function attributeGetter) {
- return withExceptionHandling(() -> attributeGetter.apply(Files.readAttributes(path, BasicFileAttributes.class))
- .to(SECONDS), -1);
+ return withExceptionHandling(() ->
+ path == null ? 0 : attributeGetter.apply(Files.readAttributes(path, BasicFileAttributes.class))
+ .to(SECONDS), -1);
}
@Override
@@ -287,6 +306,45 @@ public int write(ByteBuffer src) throws IOException {
return ((WritableByteChannel) channel).write(src);
}
+ private static ChannelIo open(Path path, OpenOption... openOptions) throws IOException {
+ if (path.getParent() != null) {
+ java.nio.file.Files.createDirectories(path.getParent());
+ }
+ return new ChannelIo(path, FileChannel.open(path, openOptions));
+ }
+
+ private static void ensureParentDirectory(Path path) throws IOException {
+ Path parent = path.getParent();
+ if (parent != null) {
+ createDirectories(parent);
+ }
+ }
+
+ private static boolean isReadOnlyMode(String mode) {
+ return "r".equalsIgnoreCase(mode);
+ }
+
+ private static Collection getOpenOptions(String mode) {
+ switch (mode.toLowerCase()) {
+ case "r":
+ return singletonList(StandardOpenOption.READ);
+ case "w":
+ return asList(StandardOpenOption.CREATE, StandardOpenOption.WRITE,
+ StandardOpenOption.TRUNCATE_EXISTING);
+ case "a":
+ return asList(StandardOpenOption.CREATE, StandardOpenOption.APPEND);
+ case "r+":
+ return asList(StandardOpenOption.READ, StandardOpenOption.WRITE);
+ case "w+":
+ return asList(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
+ case "a+":
+ return asList(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.APPEND);
+ default:
+ throw new IllegalArgumentException("Unknown file mode " + mode);
+ }
+ }
+
+
static int getErrorNumber(Exception exception) {
if (exception == null) {
return 0;
@@ -355,35 +413,4 @@ private static Set createPosixFilePermissions(int mode) {
.map(Map.Entry::getValue)
.collect(toSet());
}
-
- private static void ensureParentDirectory(Path path) throws IOException {
- Path parent = path.getParent();
- if (parent != null) {
- createDirectories(parent);
- }
- }
-
- private static boolean isReadOnlyMode(String mode) {
- return "r".equalsIgnoreCase(mode);
- }
-
- private static Collection getOpenOptions(String mode) {
- switch (mode.toLowerCase()) {
- case "r":
- return singletonList(StandardOpenOption.READ);
- case "w":
- return asList(StandardOpenOption.CREATE, StandardOpenOption.WRITE,
- StandardOpenOption.TRUNCATE_EXISTING);
- case "a":
- return asList(StandardOpenOption.CREATE, StandardOpenOption.APPEND);
- case "r+":
- return asList(StandardOpenOption.READ, StandardOpenOption.WRITE);
- case "w+":
- return asList(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
- case "a+":
- return asList(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.APPEND);
- default:
- throw new IllegalArgumentException("Unknown file mode " + mode);
- }
- }
}
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientServerSocket.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientServerSocket.java
deleted file mode 100644
index 781f430b7..000000000
--- a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientServerSocket.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package io.github.mmhelloworld.idris2boot.runtime;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.SocketChannel;
-import java.nio.channels.WritableByteChannel;
-import java.util.concurrent.ExecutionException;
-
-public final class ClientServerSocket implements ReadableByteChannel, WritableByteChannel, Closeable {
- private final ServerSocket serverSocket;
- private final SocketChannel client;
- private final ByteBufferIo byteBufferIo;
-
- private ClientServerSocket(ServerSocket serverSocket, SocketChannel client) {
- this.serverSocket = serverSocket;
- this.client = client;
- this.byteBufferIo = new ByteBufferIo(this::read, this::write);
- }
-
- public static ClientServerSocket listenAndAccept(InetAddress host, int port) throws IOException, ExecutionException,
- InterruptedException {
- ServerSocket serverSocket = new ServerSocket();
- serverSocket.bind(host, port);
- serverSocket.listen();
- return accept(serverSocket);
- }
-
- public static ClientServerSocket listenAndAccept(String host, int port) throws IOException, ExecutionException,
- InterruptedException {
- return listenAndAccept(InetAddress.getByName(host), port);
- }
-
- public static ClientServerSocket accept(ServerSocket serverSocket) throws InterruptedException, ExecutionException,
- ClosedChannelException {
- SocketChannel client = serverSocket.accept().get();
- return new ClientServerSocket(serverSocket, client);
- }
-
- public int write(ByteBuffer buffer) {
- try {
- return serverSocket.write(client, buffer).get();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
- return 0;
- }
-
- public int read(ByteBuffer buffer) {
- try {
- return serverSocket.read(client, buffer).get();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
- return 0;
- }
-
- public char getChar() throws IOException {
- return byteBufferIo.getChar();
- }
-
- public String getLine() throws IOException {
- return byteBufferIo.getLine();
- }
-
- public void writeString(String str) throws IOException {
- byteBufferIo.writeString(str);
- }
-
- @Override
- public boolean isOpen() {
- return client.isOpen();
- }
-
- @Override
- public void close() throws IOException {
- serverSocket.close();
- }
-}
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientSocketReaderState.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientSocketReaderState.java
index a6bd54014..d9f336b4e 100644
--- a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientSocketReaderState.java
+++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientSocketReaderState.java
@@ -1,14 +1,13 @@
package io.github.mmhelloworld.idris2boot.runtime;
import java.nio.ByteBuffer;
-import java.util.concurrent.CountDownLatch;
final class ClientSocketReaderState {
- private final ByteBuffer buffer;
- private final CountDownLatch doneSignal;
+ private ByteBuffer buffer;
+ private final ResettableCountDownLatch doneSignal;
private int bytesRead;
- ClientSocketReaderState(ByteBuffer buffer, CountDownLatch doneSignal) {
+ ClientSocketReaderState(ByteBuffer buffer, ResettableCountDownLatch doneSignal) {
this.buffer = buffer;
this.doneSignal = doneSignal;
}
@@ -17,7 +16,7 @@ ByteBuffer getBuffer() {
return buffer;
}
- CountDownLatch getDoneSignal() {
+ ResettableCountDownLatch getDoneSignal() {
return doneSignal;
}
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientSocketWriterState.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientSocketWriterState.java
index 3dad437b4..c1214ab23 100644
--- a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientSocketWriterState.java
+++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ClientSocketWriterState.java
@@ -1,23 +1,26 @@
package io.github.mmhelloworld.idris2boot.runtime;
import java.nio.ByteBuffer;
-import java.util.concurrent.CountDownLatch;
final class ClientSocketWriterState {
- private final ByteBuffer buffer;
- private final CountDownLatch doneSignal;
+ private ByteBuffer buffer;
+ private final ResettableCountDownLatch doneSignal;
private int bytesWritten;
- ClientSocketWriterState(ByteBuffer buffer, CountDownLatch doneSignal) {
+ ClientSocketWriterState(ByteBuffer buffer, ResettableCountDownLatch doneSignal) {
this.buffer = buffer;
this.doneSignal = doneSignal;
}
+ void reset() {
+ doneSignal.reset();
+ }
+
ByteBuffer getBuffer() {
return buffer;
}
- CountDownLatch getDoneSignal() {
+ ResettableCountDownLatch getDoneSignal() {
return doneSignal;
}
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ErrorCodes.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ErrorCodes.java
new file mode 100644
index 000000000..eaaecfdbb
--- /dev/null
+++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ErrorCodes.java
@@ -0,0 +1,16 @@
+package io.github.mmhelloworld.idris2boot.runtime;
+
+public final class ErrorCodes {
+ public static final int SUCCESS = 0;
+ public static final int NO_SUCH_FILE = 2;
+ public static final int IO_ERROR = 5;
+ public static final int SOCKET_ACCESS_DENIED = 13;
+ public static final int INTERRUPTED = 4;
+ public static final int UNSUPPORTED_SOCKET_TYPE = 44;
+ public static final int CANNOT_ASSIGN_REQUESTED_ADDRESS = 49;
+ public static final int NO_ROUTE_TO_HOST = 65;
+ public static final int PROTOCOL_ERROR = 86;
+
+ private ErrorCodes() {
+ }
+}
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Files.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Files.java
new file mode 100644
index 000000000..072cd20db
--- /dev/null
+++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Files.java
@@ -0,0 +1,21 @@
+package io.github.mmhelloworld.idris2boot.runtime;
+
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collection;
+
+import static io.github.mmhelloworld.idris2boot.runtime.Paths.createPath;
+import static io.github.mmhelloworld.idris2boot.runtime.Runtime.setErrorNumber;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+public final class Files {
+ private Files() {
+ }
+
+
+}
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisFile.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisFile.java
new file mode 100644
index 000000000..12a57fc95
--- /dev/null
+++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisFile.java
@@ -0,0 +1,16 @@
+package io.github.mmhelloworld.idris2boot.runtime;
+
+public interface IdrisFile> {
+ void close();
+ int getErrorNumber();
+ String readLine();
+ String readChars(int numberOfCharacters);
+ char readChar();
+ int writeLine(String line);
+ int isEof();
+ int flush();
+ int size();
+ int getAccessTime();
+ int getModifiedTime();
+ int getStatusTime();
+}
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisSocket.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisSocket.java
new file mode 100644
index 000000000..2d68dfdf6
--- /dev/null
+++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisSocket.java
@@ -0,0 +1,288 @@
+package io.github.mmhelloworld.idris2boot.runtime;
+
+import java.io.Closeable;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.BindException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NoRouteToHostException;
+import java.net.ProtocolException;
+import java.net.StandardProtocolFamily;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.spi.AbstractSelectableChannel;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.NoSuchFileException;
+
+import static java.nio.channels.SelectionKey.OP_ACCEPT;
+import static java.nio.channels.SelectionKey.OP_READ;
+import static java.nio.channels.SelectionKey.OP_WRITE;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public final class IdrisSocket implements Closeable {
+
+ private static final int STREAM_SOCKET_TYPE = 1;
+ private static final int DATAGRAM_SOCKET_TYPE = 2;
+ private static final int INET_PROTOCOL_FAMILY = 2;
+ private static final int INET6_PROTOCOL_FAMILY = 10;
+ private AbstractSelectableChannel channel;
+ private Exception exception;
+ private int socketType;
+
+ public IdrisSocket(int socketType, AbstractSelectableChannel channel) throws IOException {
+ this.socketType = socketType;
+ if (channel != null) {
+ initialize(channel);
+ }
+ }
+
+ public static IdrisSocket create(int socketFamily, int socketType, int protocolNumber) {
+ try {
+ switch (socketType) { // socket type represents idris socket type values from Network.Socket.Data.idr
+ case STREAM_SOCKET_TYPE:
+ return new IdrisSocket(socketType, null);
+ case DATAGRAM_SOCKET_TYPE:
+ DatagramChannel channel = DatagramChannel.open(
+ socketFamily == INET6_PROTOCOL_FAMILY ? StandardProtocolFamily.INET6 :
+ StandardProtocolFamily.INET);
+ channel.configureBlocking(false);
+ return new IdrisSocket(socketType, channel);
+ default:
+ Runtime.setErrorNumber(ErrorCodes.UNSUPPORTED_SOCKET_TYPE);
+ return null;
+ }
+ } catch (Exception exception) {
+ exception.printStackTrace();
+ Runtime.setErrorNumber(getErrorNumber(exception));
+ return null;
+ }
+ }
+
+ public static Object createSocketAddress() {
+ return new Object[1];
+ }
+
+ public static void free(Object ptr) {
+ }
+
+ public static int getSocketAddressFamily(Object socketAddressPointer) {
+ InetAddress socketAddress = (InetAddress) ((Object[]) socketAddressPointer)[0];
+ return socketAddress instanceof Inet6Address ? INET6_PROTOCOL_FAMILY : INET_PROTOCOL_FAMILY;
+ }
+
+ public static String getSocketAddressHostName(Object socketAddressPointer) {
+ InetAddress socketAddress = (InetAddress) ((Object[]) socketAddressPointer)[0];
+ return socketAddress.getHostAddress();
+ }
+
+ public int bind(int socketFamily, int socketType, String hostName, int port) {
+ return withExceptionHandling(() -> {
+ InetSocketAddress socketAddress = new InetSocketAddress(hostName, port);
+ switch (socketType) {
+ case STREAM_SOCKET_TYPE:
+ channel = ServerSocketChannel.open();
+ ((ServerSocketChannel) channel).socket().bind(socketAddress);
+ initialize(channel);
+ break;
+ case DATAGRAM_SOCKET_TYPE:
+ ((DatagramChannel) channel).socket().bind(socketAddress);
+ break;
+ default:
+ int errorCode = ErrorCodes.UNSUPPORTED_SOCKET_TYPE;
+ Runtime.setErrorNumber(errorCode);
+ return errorCode;
+ }
+ this.socketType = socketType;
+ Server.register(channel, OP_ACCEPT);
+ Server.start();
+ return 0;
+ }, -1);
+ }
+
+ public int connect(int socketFamily, int socketType, String hostName, int port) {
+ return withExceptionHandling(() -> {
+ InetAddress inetAddress = InetAddress.getByName(hostName);
+ switch (socketType) {
+ case STREAM_SOCKET_TYPE:
+ channel = SocketChannel.open(new InetSocketAddress(inetAddress, port));
+ initialize(channel);
+ return 0;
+ case DATAGRAM_SOCKET_TYPE:
+ ((DatagramChannel) channel).socket().connect(inetAddress, port);
+ return 0;
+ default:
+ return -1;
+ }
+ }, -1);
+ }
+
+ public IdrisSocket accept(Object address) {
+ return withExceptionHandling(() -> {
+ SocketChannel client = Server.acceptClient();
+ ((Object[]) address)[0] = client.socket().getInetAddress();
+ return new IdrisSocket(socketType, client);
+ });
+ }
+
+ public void registerReadWrite() throws ClosedChannelException {
+ SelectionKey key = Server.getKey(channel);
+ if (key != null) {
+ key.interestOps(OP_READ + OP_WRITE);
+ } else {
+ Server.register(channel, OP_READ + OP_WRITE);
+ }
+ }
+
+ public int listen(int numberOfIncomingCalls) {
+ return 0;
+ }
+
+ public int getSocketPort() {
+ switch (socketType) {
+ case STREAM_SOCKET_TYPE:
+ return ((ServerSocketChannel) channel).socket().getLocalPort();
+ case DATAGRAM_SOCKET_TYPE:
+ return ((DatagramChannel) channel).socket().getLocalPort();
+ default:
+ return -1;
+ }
+ }
+
+ public ChannelIo toFile(String mode) {
+ SocketChannel clientChannel = (SocketChannel) channel;
+ return new ChannelIo(channel, new ByteBufferIo(clientChannel::read, clientChannel::write));
+ }
+
+ public int send(String data) {
+ return withExceptionHandling(() ->
+ ((SocketChannel) channel).write(UTF_8.encode(data)), -1);
+ }
+
+ public String receive(int length) {
+ return withExceptionHandling(() -> {
+ ByteBuffer buffer = ByteBuffer.allocate(length * Character.BYTES);
+ SocketChannel channel = (SocketChannel) this.channel;
+ int read;
+ do {
+ buffer.rewind();
+ } while (channel.read(buffer) <= 0);
+ StringBuilder builder = new StringBuilder(length);
+ do {
+ buffer.flip();
+ builder.append(UTF_8.decode(buffer));
+ buffer.rewind();
+ read = channel.read(buffer);
+ } while (read > 0 && builder.length() < length);
+ return builder.toString();
+ });
+ }
+
+ @Override
+ public void close() {
+ withExceptionHandling(() -> {
+ channel.close();
+ return null;
+ });
+ }
+
+ public void handleException(Exception e) {
+ this.exception = e;
+ if (exception != null) {
+ exception.printStackTrace();
+ }
+ Runtime.setErrorNumber(getErrorNumber(e));
+ }
+
+ private void initialize(AbstractSelectableChannel channel) throws IOException {
+ this.channel = channel;
+ channel.configureBlocking(false);
+ Server.register(channel, channel.validOps());
+ ClientSocketReaderState readerState = new ClientSocketReaderState(ByteBuffer.allocate(1024 * 8),
+ new ResettableCountDownLatch(1));
+ Server.putState(channel, readerState);
+ ClientSocketWriterState writerState = new ClientSocketWriterState(ByteBuffer.allocate(1024 * 8),
+ new ResettableCountDownLatch(1));
+ Server.putState(channel, writerState);
+ }
+
+ private T withExceptionHandling(SupplierE action) {
+ exception = null;
+ Runtime.setErrorNumber(0);
+ try {
+ return action.get();
+ } catch (Exception e) {
+ handleException(e);
+ return null;
+ }
+ }
+
+ private int withExceptionHandling(IntSupplierE extends Exception> action) {
+ return withExceptionHandling(action, 0);
+ }
+
+ private int withExceptionHandling(IntSupplierE extends Exception> action, int fallback) {
+ exception = null;
+ Runtime.setErrorNumber(0);
+ try {
+ return action.get();
+ } catch (Exception exception) {
+ handleException(exception);
+ return fallback;
+ }
+ }
+
+ private boolean withExceptionHandling(BooleanSupplierE extends Exception> action, boolean fallback) {
+ exception = null;
+ Runtime.setErrorNumber(0);
+ try {
+ return action.get();
+ } catch (Exception exception) {
+ handleException(exception);
+ return fallback;
+ }
+ }
+
+ private long withExceptionHandling(LongSupplierE extends Exception> action, long fallback) {
+ exception = null;
+ Runtime.setErrorNumber(0);
+ try {
+ return action.get();
+ } catch (Exception exception) {
+ handleException(exception);
+ return fallback;
+ }
+ }
+
+ static int getErrorNumber(Exception exception) {
+ if (exception != null) {
+ exception.printStackTrace();
+ }
+ // To return error codes to conform to Idris functions with C FFIs
+ if (exception == null) {
+ return 0;
+ } else if (exception instanceof FileNotFoundException || exception instanceof NoSuchFileException) {
+ return ErrorCodes.NO_SUCH_FILE;
+ } else if (exception instanceof InterruptedIOException || exception instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ return ErrorCodes.INTERRUPTED;
+ } else if (exception instanceof AccessDeniedException || exception instanceof SecurityException) {
+ return ErrorCodes.SOCKET_ACCESS_DENIED;
+ } else if (exception instanceof BindException) {
+ return ErrorCodes.CANNOT_ASSIGN_REQUESTED_ADDRESS;
+ } else if (exception instanceof ProtocolException) {
+ return ErrorCodes.PROTOCOL_ERROR;
+ } else if (exception instanceof NoRouteToHostException || exception instanceof UnknownHostException) {
+ return ErrorCodes.NO_ROUTE_TO_HOST;
+ } else {
+ return ErrorCodes.IO_ERROR;
+ }
+ }
+}
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ResettableCountDownLatch.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ResettableCountDownLatch.java
new file mode 100644
index 000000000..f36d42689
--- /dev/null
+++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ResettableCountDownLatch.java
@@ -0,0 +1,41 @@
+package io.github.mmhelloworld.idris2boot.runtime;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class ResettableCountDownLatch {
+ private final int initialCount;
+ private final AtomicReference latchHolder = new AtomicReference<>();
+
+ public ResettableCountDownLatch(int count) {
+ initialCount = count;
+ latchHolder.set(new CountDownLatch(initialCount));
+ }
+
+ public ResettableCountDownLatch reset() {
+ CountDownLatch oldLatch = latchHolder.getAndSet(new CountDownLatch(initialCount));
+ if (oldLatch != null) {
+ while (oldLatch.getCount() > 0L) {
+ oldLatch.countDown();
+ }
+ }
+ return this;
+ }
+
+ public int getCount() {
+ return initialCount;
+ }
+
+ public void countDown() {
+ latchHolder.get().countDown();
+ }
+
+ public void await() throws InterruptedException {
+ latchHolder.get().await();
+ }
+
+ public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
+ return latchHolder.get().await(timeout, unit);
+ }
+}
diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Runtime.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Runtime.java
index 5de8540ba..c5bc8e0d1 100644
--- a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Runtime.java
+++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Runtime.java
@@ -2,6 +2,7 @@
import java.nio.channels.Channels;
import java.util.List;
+import java.util.function.Function;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
@@ -97,4 +98,10 @@ public static double unwrapDoubleThunk(Object possibleThunk) {
return (double) possibleThunk;
}
}
+
+ public static Thread fork(Function