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 action) { + return withExceptionHandling(action, 0); + } + + private int withExceptionHandling(IntSupplierE action, int fallback) { + exception = null; + Runtime.setErrorNumber(0); + try { + return action.get(); + } catch (Exception exception) { + handleException(exception); + return fallback; + } + } + + private boolean withExceptionHandling(BooleanSupplierE action, boolean fallback) { + exception = null; + Runtime.setErrorNumber(0); + try { + return action.get(); + } catch (Exception exception) { + handleException(exception); + return fallback; + } + } + + private long withExceptionHandling(LongSupplierE 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 action) { + Thread thread = new Thread(() -> Runtime.unwrap(action.apply(null))); + thread.start(); + return thread; + } } diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Server.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Server.java new file mode 100644 index 000000000..ac04a87a0 --- /dev/null +++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Server.java @@ -0,0 +1,175 @@ +package io.github.mmhelloworld.idris2boot.runtime; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Channel; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.AbstractSelectableChannel; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.stream.Stream; + +public class Server { + private static final Map readerStates = new HashMap<>(); + private static final Map writerStates = new HashMap<>(); + private static final BlockingQueue newConnections = new ArrayBlockingQueue<>(10); + private static Selector selector; + private static boolean isStarted; + + static { + try { + selector = Selector.open(); + } catch (IOException exception) { + exception.printStackTrace(); + } + } + + public static synchronized void start() throws IOException { + if (!isStarted) { + isStarted = true; + Thread thread = new Thread(() -> { + try { + while (selector.isOpen()) { + try { + selector.select(); + Iterator selectedKeys = selector.selectedKeys().iterator(); + while (selectedKeys.hasNext()) { + SelectionKey key = selectedKeys.next(); + selectedKeys.remove(); + processKey(key); + } + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + thread.setDaemon(true); + thread.start(); + } + + } + + public static void stop() { + Stream.concat(readerStates.keySet().stream(), writerStates.keySet().stream()) + .forEach(Server::closeClient); + } + + public static SocketChannel acceptClient() throws InterruptedException { + return newConnections.take(); + } + + public static SelectionKey register(AbstractSelectableChannel channel, int key) throws ClosedChannelException { + return channel.register(selector, key); + } + + public static void putState(AbstractSelectableChannel channel, ClientSocketReaderState state) { + readerStates.put(channel, state); + } + + public static void putState(AbstractSelectableChannel channel, ClientSocketWriterState state) { + writerStates.put(channel, state); + } + + public static ClientSocketReaderState getReaderState(AbstractSelectableChannel channel) { + return readerStates.get(channel); + } + + public static ClientSocketWriterState getWriterState(AbstractSelectableChannel channel) { + return writerStates.get(channel); + } + + public static SelectionKey getKey(AbstractSelectableChannel channel) { + return channel.keyFor(selector); + } + + public static void wakeup() { + selector.wakeup(); + } + + private static void processKey(SelectionKey key) throws IOException, InterruptedException { + // Check what event is available and deal with it + if (key.isValid() && key.isAcceptable()) { + doAccept(key); + } + + if (key.isValid() && key.isReadable()) { + doRead(key); + } + + if (key.isValid() && key.isWritable()) { + doWrite(key); + } + } + + private static void doAccept(SelectionKey key) throws IOException { + ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); + SocketChannel clientChannel = serverSocketChannel.accept(); + if (clientChannel != null) { + newConnections.offer(clientChannel); + } + } + + private static void doRead(SelectionKey key) throws InterruptedException { + SocketChannel channel = (SocketChannel) key.channel(); + ClientSocketReaderState state = readerStates.get(channel); + if (state == null) { + return; + } + ByteBuffer readBuffer = state.getBuffer(); + int read; + try { + read = channel.read(readBuffer); + } catch (IOException e) { + key.cancel(); + closeClient(channel); + return; + } + if (read == -1) { + closeClient(channel); + key.cancel(); + } + state.setBytesRead(read); + state.getDoneSignal().countDown(); + } + + private static void doWrite(SelectionKey key) throws IOException { + SocketChannel socketChannel = (SocketChannel) key.channel(); + ClientSocketWriterState state = writerStates.get(socketChannel); + if (state == null) { + return; + } + ByteBuffer buffer = state.getBuffer(); + int bytesWritten = socketChannel.write(buffer); + state.setBytesWritten(bytesWritten); + state.getDoneSignal().countDown(); + } + + private static void closeClient(Channel channel) { + try { + channel.close(); + } catch (IOException e) { + e.printStackTrace(); + } + ClientSocketReaderState readerState = readerStates.get(channel); + ClientSocketWriterState writerState = writerStates.get(channel); + if (readerState != null) { + readerState.getDoneSignal().countDown(); + readerStates.remove(channel); + } + if (writerState != null) { + writerState.getDoneSignal().countDown(); + writerStates.remove(channel); + } + } +} diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ServerSocket.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ServerSocket.java deleted file mode 100644 index cd91f853e..000000000 --- a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/ServerSocket.java +++ /dev/null @@ -1,217 +0,0 @@ -package io.github.mmhelloworld.idris2boot.runtime; - -import java.io.Closeable; -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.nio.channels.spi.SelectorProvider; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -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.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.CompletableFuture.supplyAsync; - -public final class ServerSocket implements Runnable, Closeable { - - private final Map readerStates = new HashMap<>(); - private final Map writerStates = new HashMap<>(); - private final ExecutorService executorService = Executors.newFixedThreadPool( - java.lang.Runtime.getRuntime().availableProcessors() * 2); - private final BlockingQueue newConnections = new ArrayBlockingQueue<>(10); - private Selector selector; - private ServerSocketChannel serverChannel; - - public ServerSocket() throws IOException { - selector = SelectorProvider.provider().openSelector(); - } - - public void bind(InetAddress host, int port) throws IOException { - serverChannel = ServerSocketChannel.open(); - serverChannel.configureBlocking(false); - serverChannel.socket() - .bind(new InetSocketAddress(host, port)); - serverChannel.register(selector, OP_ACCEPT); - } - - public void listen() { - new Thread(this).start(); - } - - public Future accept() throws ClosedChannelException { - return supplyAsync(() -> { - try { - return newConnections.take(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - }, executorService); - } - - public Future read(SocketChannel socketChannel, ByteBuffer buffer) { - SelectionKey selectionKey = socketChannel.keyFor(selector); - if (selectionKey == null || !selectionKey.isValid()) { - return completedFuture(null); - } - CountDownLatch readSignal = new CountDownLatch(1); - ClientSocketReaderState state = new ClientSocketReaderState(buffer, readSignal); - readerStates.put(socketChannel, state); - selectionKey.interestOps(OP_READ); - return supplyAsync(() -> { - try { - readSignal.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - return state.getBytesRead(); - }, executorService); - } - - public Future write(SocketChannel socketChannel, ByteBuffer buffer) { - CountDownLatch writeSignal = new CountDownLatch(1); - ClientSocketWriterState state = new ClientSocketWriterState(buffer, writeSignal); - writerStates.put(socketChannel, state); - return supplyAsync(() -> { - try { - socketChannel.keyFor(selector).interestOps(OP_WRITE); - selector.wakeup(); - writeSignal.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - return state.getBytesWritten(); - }, executorService); - } - - @Override - public void run() { - while (serverChannel.isOpen() && selector.isOpen()) { - try { - selector.select(); - if (!selector.isOpen()) { - break; - } - Iterator selectedKeys = selector.selectedKeys().iterator(); - while (selectedKeys.hasNext()) { - SelectionKey key = selectedKeys.next(); - selectedKeys.remove(); - processKey(key); - } - } catch (IOException e) { - e.printStackTrace(); - break; - } - } - } - - private void processKey(SelectionKey key) throws IOException { - // Check what event is available and deal with it - if (key.isValid() && key.isAcceptable()) { - doAccept(key); - } - - if (key.isValid() && key.isReadable()) { - doRead(key); - } - - if (key.isValid() && key.isWritable()) { - doWrite(key); - } - } - - private void doRead(SelectionKey key) { - SocketChannel channel = (SocketChannel) key.channel(); - ClientSocketReaderState state = readerStates.get(channel); - if (state == null) { - return; - } - CountDownLatch readSignal = state.getDoneSignal(); - ByteBuffer readBuffer = state.getBuffer(); - readBuffer.clear(); - int read; - try { - read = channel.read(readBuffer); - } catch (IOException e) { - key.cancel(); - closeClient(channel); - return; - } - - if (read == -1) { - closeClient(channel); - key.cancel(); - } - state.setBytesRead(read); - readSignal.countDown(); - } - - private void closeClient(SocketChannel channel) { - try { - channel.close(); - } catch (IOException e) { - e.printStackTrace(); - } - ClientSocketReaderState readerState = readerStates.get(channel); - ClientSocketWriterState writerState = writerStates.get(channel); - if (readerState != null) { - readerState.getDoneSignal().countDown(); - readerStates.remove(channel); - } - if (writerState != null) { - writerState.getDoneSignal().countDown(); - writerStates.remove(channel); - } - } - - private void doAccept(SelectionKey key) throws IOException { - ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); - - SocketChannel socketChannel = serverSocketChannel.accept(); - socketChannel.configureBlocking(false) - .register(selector, OP_READ + OP_WRITE); - - newConnections.offer(socketChannel); - } - - private void doWrite(SelectionKey key) throws IOException { - SocketChannel socketChannel = (SocketChannel) key.channel(); - ClientSocketWriterState state = writerStates.get(socketChannel); - if (state == null) { - return; - } - int bytesWritten = socketChannel.write(state.getBuffer()); - state.setBytesWritten(bytesWritten); - state.getDoneSignal().countDown(); - key.interestOps(OP_READ); - } - - @Override - public void close() throws IOException { - selector.close(); - serverChannel.close(); - HashSet channels = new HashSet<>(readerStates.keySet()); - channels.addAll(writerStates.keySet()); - channels.forEach(this::closeClient); - executorService.shutdownNow(); - } - - public ServerSocketChannel getServerChannel() { - return serverChannel; - } -} diff --git a/src/Compiler/Jvm/Codegen.idr b/src/Compiler/Jvm/Codegen.idr index 374b86ad0..0a8e2653d 100644 --- a/src/Compiler/Jvm/Codegen.idr +++ b/src/Compiler/Jvm/Codegen.idr @@ -259,7 +259,7 @@ mutual assembleExprOp returnType fc fn args when isTailCall $ asmReturn returnType assembleExpr isTailCall returnType (NmExtPrim fc p args) = do - jvmExtPrim returnType (toPrim p) args + jvmExtPrim fc returnType (toPrim p) args when isTailCall $ asmReturn returnType assembleExpr isTailCall returnType (NmForce _ expr) = do assembleExpr False delayedType expr @@ -596,11 +596,11 @@ mutual asmCast inferredBigIntegerType returnType assembleExprOp returnType fc (Cast StringType IntegerType) [x] = do - New "java/math/BigInteger" + New "java/math/BigDecimal" Dup assembleExpr False inferredStringType x InvokeMethod InvokeSpecial "java/math/BigDecimal" "" "(Ljava/lang/String;)V" False - InvokeMethod InvokeVirtual "java/math/BigInteger" "toBigInteger" "()Ljava/math/BigInteger;" False + InvokeMethod InvokeVirtual "java/math/BigDecimal" "toBigInteger" "()Ljava/math/BigInteger;" False asmCast inferredBigIntegerType returnType assembleExprOp returnType fc (Cast IntegerType IntType) [x] = do @@ -1082,8 +1082,8 @@ mutual storeVar IInt IInt hashCodePositionVariableIndex Goto switchEndLabel - jvmExtPrim : InferredType -> ExtPrim -> List NamedCExp -> Asm () - jvmExtPrim returnType JvmInstanceMethodCall [ret, NmPrimVal fc (Str fn), fargs, world] = do + jvmExtPrim : FC -> InferredType -> ExtPrim -> List NamedCExp -> Asm () + jvmExtPrim _ returnType JvmInstanceMethodCall [ret, NmPrimVal fc (Str fn), fargs, world] = do (obj :: instanceMethodArgs) <- getFArgs fargs | [] => Throw fc ("JVM instance method must have at least one argument " ++ fn) let args = obj :: instanceMethodArgs @@ -1095,7 +1095,7 @@ mutual let (_, mname) = break (/= '.') mnameWithDot InvokeMethod InvokeVirtual cname mname methodDescriptor False asmCast methodReturnType returnType - jvmExtPrim returnType JvmStaticMethodCall [ret, NmPrimVal fc (Str fn), fargs, world] = do + jvmExtPrim _ returnType JvmStaticMethodCall [ret, NmPrimVal fc (Str fn), fargs, world] = do args <- getFArgs fargs argTypes <- traverse tySpec (map fst args) methodReturnType <- tySpec ret @@ -1105,39 +1105,39 @@ mutual let (_, mname) = break (/= '.') mnameWithDot InvokeMethod InvokeStatic cname mname methodDescriptor False asmCast methodReturnType returnType - jvmExtPrim returnType NewArray [_, size, val, world] = do + jvmExtPrim _ returnType NewArray [_, size, val, world] = do assembleExpr False IInt size assembleExpr False IUnknown val InvokeMethod InvokeStatic arraysClass "create" "(ILjava/lang/Object;)Ljava/util/ArrayList;" False asmCast arrayListType returnType - jvmExtPrim returnType ArrayGet [_, arr, pos, world] = do + jvmExtPrim _ returnType ArrayGet [_, arr, pos, world] = do assembleExpr False arrayListType arr assembleExpr False IInt pos InvokeMethod InvokeVirtual arrayListClass "get" "(I)Ljava/lang/Object;" False asmCast inferredObjectType returnType - jvmExtPrim returnType ArraySet [_, arr, pos, val, world] = do + jvmExtPrim _ returnType ArraySet [_, arr, pos, val, world] = do assembleExpr False arrayListType arr assembleExpr False IInt pos assembleExpr False IUnknown val InvokeMethod InvokeVirtual arrayListClass "set" "(ILjava/lang/Object;)Ljava/lang/Object;" False asmCast inferredObjectType returnType - jvmExtPrim returnType NewIORef [_, val, world] = do + jvmExtPrim _ returnType NewIORef [_, val, world] = do New refClass Dup assembleExpr False IUnknown val InvokeMethod InvokeSpecial refClass "" "(Ljava/lang/Object;)V" False asmCast refType returnType - jvmExtPrim returnType ReadIORef [_, ref, world] = do + jvmExtPrim _ returnType ReadIORef [_, ref, world] = do assembleExpr False refType ref InvokeMethod InvokeVirtual refClass "getValue" "()Ljava/lang/Object;" False asmCast inferredObjectType returnType - jvmExtPrim returnType WriteIORef [_, ref, val, world] = do + jvmExtPrim _ returnType WriteIORef [_, ref, val, world] = do assembleExpr False refType ref assembleExpr False IUnknown val InvokeMethod InvokeVirtual refClass "setValue" "(Ljava/lang/Object;)V" False Aconstnull asmCast inferredObjectType returnType - jvmExtPrim _ prim args = Throw emptyFC ("Unsupported external function " ++ show prim) + jvmExtPrim fc _ prim args = Throw fc ("Unsupported external function " ++ show prim) assembleDefinition : {auto c : Ref Ctxt Defs} -> Name -> FC -> NamedDef -> Asm () assembleDefinition idrisName fc def@(MkNmFun args expr) = do diff --git a/src/Compiler/Jvm/Optimizer.idr b/src/Compiler/Jvm/Optimizer.idr index d8dc3f29a..4f4f3669f 100644 --- a/src/Compiler/Jvm/Optimizer.idr +++ b/src/Compiler/Jvm/Optimizer.idr @@ -438,7 +438,7 @@ mutual inferExpr exprTy expr@(NmCon fc name tag args) = inferExprCon exprTy (fst $ getSourceLocation expr) name args inferExpr exprTy (NmOp _ fn args) = inferExprOp fn args - inferExpr exprTy (NmExtPrim _ fn args) = inferExtPrim exprTy (toPrim fn) args + inferExpr exprTy (NmExtPrim fc fn args) = inferExtPrim fc exprTy (toPrim fn) args inferExpr exprTy (NmForce _ expr) = inferExpr delayedType expr inferExpr exprTy (NmConCase _ sc [] Nothing) = Pure IUnknown @@ -553,9 +553,10 @@ mutual inferExtPrimArg : (NamedCExp, InferredType) -> Asm InferredType inferExtPrimArg (arg, ty) = inferExpr ty arg - inferExtPrim : InferredType -> ExtPrim -> List NamedCExp -> Asm InferredType - inferExtPrim returnType JvmInstanceMethodCall descriptors = inferExtPrim returnType JvmStaticMethodCall descriptors - inferExtPrim returnType JvmStaticMethodCall [ret, NmPrimVal fc (Str fn), fargs, world] + inferExtPrim : FC -> InferredType -> ExtPrim -> List NamedCExp -> Asm InferredType + inferExtPrim fc returnType JvmInstanceMethodCall descriptors = + inferExtPrim fc returnType JvmStaticMethodCall descriptors + inferExtPrim _ returnType JvmStaticMethodCall [ret, NmPrimVal fc (Str fn), fargs, world] = do args <- getFArgs fargs argTypes <- traverse tySpec (map fst args) methodReturnType <- tySpec ret @@ -563,30 +564,30 @@ mutual let (cname, mnameWithDot) = break (== '.') fn let (_, mname) = break (/= '.') mnameWithDot pure methodReturnType - inferExtPrim returnType NewArray [_, size, val, world] = do + inferExtPrim _ returnType NewArray [_, size, val, world] = do inferExpr IInt size inferExpr IUnknown val pure arrayListType - inferExtPrim returnType ArrayGet [_, arr, pos, world] = do + inferExtPrim _ returnType ArrayGet [_, arr, pos, world] = do inferExpr arrayListType arr inferExpr IInt pos pure IUnknown - inferExtPrim returnType ArraySet [_, arr, pos, val, world] = do + inferExtPrim _ returnType ArraySet [_, arr, pos, val, world] = do inferExpr arrayListType arr inferExpr IInt pos inferExpr IUnknown val pure inferredObjectType - inferExtPrim returnType NewIORef [_, val, world] = do + inferExtPrim _ returnType NewIORef [_, val, world] = do inferExpr IUnknown val pure refType - inferExtPrim returnType ReadIORef [_, ref, world] = do + inferExtPrim _ returnType ReadIORef [_, ref, world] = do inferExpr refType ref pure IUnknown - inferExtPrim returnType WriteIORef [_, ref, val, world] = do + inferExtPrim _ returnType WriteIORef [_, ref, val, world] = do inferExpr refType ref inferExpr IUnknown val pure inferredObjectType - inferExtPrim _ prim args = Throw emptyFC ("Unsupported external function " ++ show prim) + inferExtPrim fc _ prim args = Throw fc ("Unsupported external function " ++ show prim) inferExprLamWithParameterType : Maybe (Name, InferredType) -> (parameterValueExpr: Maybe (Asm ())) -> NamedCExp -> Asm InferredType diff --git a/src/Idris/CommandLine.idr b/src/Idris/CommandLine.idr index 8fc220b53..7e0e0f313 100644 --- a/src/Idris/CommandLine.idr +++ b/src/Idris/CommandLine.idr @@ -111,7 +111,7 @@ options = [MkOpt ["--check", "-c"] [] [CheckOnly] MkOpt ["--no-prelude"] [] [NoPrelude] (Just "Don't implicitly import Prelude"), MkOpt ["--codegen", "--cg"] ["backend"] (\f => [SetCG f]) - (Just "Set code generator (default chez)"), + (Just "Set code generator (default jvm)"), MkOpt ["--package", "-p"] ["package"] (\f => [PkgPath f]) (Just "Add a package as a dependency"), diff --git a/tests/Main.idr b/tests/Main.idr index 0dd030726..cea73867f 100644 --- a/tests/Main.idr +++ b/tests/Main.idr @@ -113,7 +113,7 @@ chezTests jvmTests : List String jvmTests = ["jvm001", "jvm002", "jvm003", "jvm004", "jvm005", "jvm006", - "jvm007", "jvm008", "jvm009", "jvm011", "jvm012", + "jvm007", "jvm008", "jvm009", "jvm011", "jvm012", "jvm014", "jvm015", "jvm016", "jvm017", "jvm018", "reg001"] diff --git a/tests/Makefile b/tests/Makefile index 9dc002aae..3cc0b5e5b 100755 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,8 +1,9 @@ -IDRIS2 = ../../../idris2boot INTERACTIVE ?= --interactive +IDRIS2_HOME:=$(shell realpath $(PWD)/jvm-assembler/target/idris2-boot-jvm) +PATH:=$(IDRIS2_HOME)/bin:$(PATH) test: - @../runtests $(IDRIS2) $(INTERACTIVE) --only $(only) + @../runtests $(IDRIS2_HOME)/bin/idris2 $(INTERACTIVE) --only $(only) clean: find . -name '*.ibc' | xargs rm -f diff --git a/tests/jvm/jvm014/Echo.idr b/tests/jvm/jvm014/Echo.idr new file mode 100644 index 000000000..49b71e0db --- /dev/null +++ b/tests/jvm/jvm014/Echo.idr @@ -0,0 +1,56 @@ +module Main + +import System +import Network.Socket +import Network.Socket.Data +import Network.Socket.Raw + +runServer : IO (Either String (Port, ThreadID)) +runServer = do + Right sock <- socket AF_INET Stream 0 + | Left fail => pure (Left $ "Failed to open socket: " ++ show fail) + res <- bind sock (Just (Hostname "localhost")) 0 + if res /= 0 + then pure (Left $ "Failed to bind socket with error: " ++ show res) + else do + port <- getSockPort sock + res <- listen sock + if res /= 0 + then pure (Left $ "Failed to listen on socket with error: " ++ show res) + else do + forked <- fork (serve port sock) + pure $ Right (port, forked) + + where + serve : Port -> Socket -> IO () + serve port sock = do + Right (s, _) <- accept sock + | Left err => putStrLn ("Failed to accept on socket with error: " ++ show err) + Right (str, _) <- recv s 1024 + | Left err => putStrLn ("Failed to accept on socket with error: " ++ show err) + putStrLn ("Received: " ++ str) + Right n <- send s ("echo: " ++ str) + | Left err => putStrLn ("Server failed to send data with error: " ++ show err) + pure () + +runClient : Port -> IO () +runClient serverPort = do + Right sock <- socket AF_INET Stream 0 + | Left fail => putStrLn ("Failed to open socket: " ++ show fail) + res <- connect sock (Hostname "localhost") serverPort + if res /= 0 + then putStrLn ("Failed to connect client to port " ++ show serverPort ++ ": " ++ show res) + else do + Right n <- send sock ("hello world!") + | Left err => putStrLn ("Client failed to send data with error: " ++ show err) + Right (str, _) <- recv sock 1024 + | Left err => putStrLn ("Client failed to receive on socket with error: " ++ show err) + -- assuming that stdout buffers get flushed in between system calls, this is "guaranteed" + -- to be printed after the server prints its own message + putStrLn ("Received: " ++ str) + +main : IO () +main = do + Right (serverPort, tid) <- runServer + | Left err => putStrLn $ "[server] " ++ err + runClient serverPort diff --git a/tests/jvm/jvm014/expected b/tests/jvm/jvm014/expected new file mode 100644 index 000000000..1ce1c93d4 --- /dev/null +++ b/tests/jvm/jvm014/expected @@ -0,0 +1,4 @@ +1/1: Building Echo (Echo.idr) +Main> Received: hello world! +Received: echo: hello world! +Main> Bye for now! diff --git a/tests/jvm/jvm014/input b/tests/jvm/jvm014/input new file mode 100644 index 000000000..fc5992c29 --- /dev/null +++ b/tests/jvm/jvm014/input @@ -0,0 +1,2 @@ +:exec main +:q diff --git a/tests/jvm/jvm014/run b/tests/jvm/jvm014/run new file mode 100755 index 000000000..7d1b656f3 --- /dev/null +++ b/tests/jvm/jvm014/run @@ -0,0 +1,3 @@ +$1 --no-banner -p network Echo.idr < input + +rm -rf build