feat(#3251): multiplatform console read

This commit is contained in:
maxonfjvipon 2024-08-14 21:17:28 +03:00
parent 946c424116
commit 63e1bf4b9b
No known key found for this signature in database
GPG Key ID: D8563899D473D273
15 changed files with 1120 additions and 632 deletions

View File

@ -25,21 +25,4 @@
# package name contains capital letter and such names are conventional.
---
exclude_paths:
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/CStdLib.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/GetpidSyscall.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/package-info.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/ReadSyscall.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Posix/WriteSyscall.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/BaseTSD.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/Kernel32.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/package-info.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/WinBase.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/Wincon.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/WinDef.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/WinNT.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/WriteFileFuncCall.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/EOposix$EOφ.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/EOwin32$EOφ.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Syscall.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/TupleToArray.java"
- "eo-runtime/src/test/java/EOorg/EOeolang/EOio/InputOutputTest.java"
- "eo-runtime/src/main/java/EOorg/EOeolang/EOsys/Win32/ReadFileFuncCall.java"

View File

@ -503,8 +503,6 @@ SOFTWARE.
<xsl:value-of select="eo:eol(1)"/>
<xsl:text>@Test</xsl:text>
<xsl:value-of select="eo:eol(1)"/>
<xsl:text>@WritesStdIo</xsl:text>
<xsl:value-of select="eo:eol(1)"/>
<xsl:text>public void works() throws java.lang.Exception {</xsl:text>
<xsl:value-of select="eo:eol(2)"/>
<xsl:choose>
@ -551,8 +549,6 @@ SOFTWARE.
<xsl:value-of select="eo:eol(0)"/>
<xsl:text>import org.junit.jupiter.api.Test;</xsl:text>
<xsl:value-of select="eo:eol(0)"/>
<xsl:text>import org.junitpioneer.jupiter.WritesStdIo;</xsl:text>
<xsl:value-of select="eo:eol(0)"/>
</xsl:template>
<!-- License with disclaimer -->
<xsl:template match="/program" mode="license">

View File

@ -66,64 +66,144 @@
# It dataizes given argument, converts it to string,
# prints it to operation system console and returns next output block
# ready to `write` again.
#
# Console is platform dependent, which means the internal algorithm is different for
# Posix and Windows. Defining the console is happen every time you "touch" `console` object.
# If you use it as input/output:
# ```
# console.read 2 > i1
# i1.read 2 > i2
# i2.read 2 > i3
# ...
# ```
#
# it works faster than:
# ```
# console.read 2 > i1
# console.read 2 > i2
# console.read 2 > i3
# ...
# ```
# That why it's better to use it sequentially.
[] > console
# Read `size` amount of bytes from operation system console.
# Returns new instance of `input-block` with `buffer` read from console.
[size] > read
((input-block --).read size).self > @
platform. > @
if.
os.is-windows
windows-console
posix-console
# Console input block.
#
# Attention! The object is for internal usage only, please don't use the object
# programmatically outside of `console` object.
[buffer] > input-block
$ > self
buffer > @
# Posix console.
# It uses posix system calls to read/write standard inputs/outputs.
[] > posix-console
$ > platform
# Read `size` amount of bytes from operation system console.
# Returns new instance of `input-block` with `buffer` read from console.
[size] > read
^.^.read-bytes size > read-bytes!
self. > @
seq
*
read-bytes
^.^.input-block read-bytes
# Read `size` amount of bytes from operation system console.
# Returns new instance of `input-block` with `buffer` read from console.
[size] > read
((input-block --).read size).self > @
# Bytes read from operation system console.
#
# Attention! The object is for internal usage only, please don't use the object
# programmatically outside of `console` object.
[size] > read-bytes /bytes
# Posix console input block.
#
# Attention! The object is for internal usage only, please don't use the object
# programmatically outside of `console` object.
[buffer] > input-block
$ > self
buffer > @
# Write given `buffer` to console.
# Here `buffer` is either sequence of bytes or and object that can be
# dataized via `as-bytes` object.
# Returns new instance of `output-block` ready to write again.
[buffer] > write
(output-block.write buffer).self > @
# Read `size` amount of bytes from operation system console.
# Returns new instance of `input-block` with `buffer` read from console.
[size] > read
output. > read-bytes!
posix
"read"
* posix.stdin-fileno size
self. > @
seq
*
read-bytes
^.^.input-block read-bytes
# Console output block.
#
# Attention! The object is for internal usage only, please don't use the object
# programmatically outside of `console` object.
[] > output-block
$ > self
true > @
# Write given `buffer` to console.
# Here `buffer` is either sequence of bytes or and object that can be
# dataized via `as-bytes` object.
# Returns new instance of `output-block` ready to write again.
[buffer] > write
(output-block.write buffer).self > @
# Writes bytes contained in `buffer` to operation system console.
# Returns new instance of `output-block` ready to write again.
[buffer] > write
self. > @
seq
*
code.
if.
os.is-windows
win32
"WriteFile"
* win32.std-output-handle buffer buffer.size
# Posix console output block.
#
# Attention! The object is for internal usage only, please don't use the object
# programmatically outside of `console` object.
[] > output-block
$ > self
true > @
# Writes bytes contained in `buffer` to operation system console.
# Returns new instance of `output-block` ready to write again.
[buffer] > write
self. > @
seq
*
code.
posix
"write"
* posix.stdout-fileno buffer buffer.size
^.^.output-block
^.^.output-block
# Windows console.
# It uses kernel32.dll system function calls to read/write standard inputs/outputs.
[] > windows-console
$ > platform
# Read `size` amount of bytes from operation system console.
# Returns new instance of `input-block` with `buffer` read from console.
[size] > read
((input-block --).read size).self > @
# Windows console input block.
#
# Attention! The object is for internal usage only, please don't use the object
# programmatically outside of `console` object.
[buffer] > input-block
$ > self
buffer > @
# Read `size` amount of bytes from operation system console.
# Returns new instance of `input-block` with `buffer` read from console.
[size] > read
output. > read-bytes!
win32
"ReadFile"
* win32.std-input-handle size
self. > @
seq
*
read-bytes
^.^.input-block read-bytes
# Write given `buffer` to console.
# Here `buffer` is either sequence of bytes or and object that can be
# dataized via `as-bytes` object.
# Returns new instance of `output-block` ready to write again.
[buffer] > write
(output-block.write buffer).self > @
# Windows console output block.
#
# Attention! The object is for internal usage only, please don't use the object
# programmatically outside of `console` object.
[] > output-block
$ > self
true > @
# Writes bytes contained in `buffer` to operation system console.
# Returns new instance of `output-block` ready to write again.
[buffer] > write
self. > @
seq
*
code.
win32
"WriteFile"
* win32.std-output-handle buffer buffer.size
^.^.output-block

View File

@ -71,9 +71,9 @@
# Consumes only one line from the standard input stream.
# If there's no line to consume - returns an empty string.
[] > next-line
console.read 1 > first!
(console.read 1).self > first
if. > @
first.size.eq 0
first.as-bytes.size.eq 0
""
rec-read first --
@ -81,14 +81,17 @@
#
# Attention. The object is for internal usage only, please
# don't use the object programmatically outside of `stdin` object.
[char buffer] > rec-read
console.read 1 > next!
[input buffer] > rec-read
input.as-bytes > char
(input.read 1).self > next
if. > @
or.
and.
char.eq "\r"
next.eq "\n"
char.eq "\n"
char.eq --
or.
and.
char.eq "\r"
next.as-bytes.eq "\n"
char.eq "\n"
string buffer
^.rec-read
next

View File

@ -44,6 +44,14 @@
# - Returns:
# * code - written bytes count (number)
# * output - []
# 2. ReadFile
# - Documentation: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
# - Arguments:
# * descriptor (number)
# * buffer size to read (number)
# - Returns:
# * code - read bytes count (number)
# * output - read bytes (bytes)
[name args] > win32
-10 > std-input-handle
-11 > std-output-handle

View File

@ -1,88 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2024 Objectionary.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* @checkstyle PackageNameCheck (4 lines)
* @checkstyle TrailingCommentCheck (3 lines)
*/
package EOorg.EOeolang.EOio; // NOPMD
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.eolang.AtVoid;
import org.eolang.Atom;
import org.eolang.Data;
import org.eolang.Dataized;
import org.eolang.PhDefault;
import org.eolang.Phi;
import org.eolang.Versionized;
import org.eolang.XmirObject;
/**
* Console.read.read-bytes.
*
* @since 0.39
* @checkstyle TypeNameCheck (5 lines)
*/
@Versionized
@XmirObject(oname = "console.read.read-bytes")
@SuppressWarnings("PMD.AvoidDollarSigns")
public final class EOconsole$EOread$EOread_bytes extends PhDefault implements Atom {
/**
* Input stream to read bytes from.
*/
private final InputStream input;
/**
* Ctor.
*/
public EOconsole$EOread$EOread_bytes() {
this(System.in);
}
/**
* Ctor for the tests.
* @param input Stream to read from
*/
@SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
EOconsole$EOread$EOread_bytes(final InputStream input) {
this.input = input;
this.add("size", new AtVoid("size"));
}
@Override
@SuppressWarnings("PMD.AssignmentInOperand")
public Phi lambda() throws IOException {
final int size = new Dataized(this.take("size")).asNumber().intValue();
final byte[] read = new byte[size];
int character;
int processed = 0;
while (processed < size && (character = this.input.read()) != -1) {
read[processed] = (byte) character;
++processed;
}
return new Data.ToPhi(Arrays.copyOf(read, processed));
}
}

View File

@ -1,32 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2024 Objectionary.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* EO runtime, IO.
*
* @since 0.1
* @checkstyle PackageNameCheck (4 lines)
* @checkstyle TrailingCommentCheck (3 lines)
*/
package EOorg.EOeolang.EOio; // NOPMD

View File

@ -27,6 +27,7 @@
*/
package EOorg.EOeolang.EOsys; // NOPMD
import EOorg.EOeolang.EOsys.Win32.ReadFileFuncCall;
import EOorg.EOeolang.EOsys.Win32.WriteFileFuncCall;
import java.util.HashMap;
import java.util.Map;
@ -55,6 +56,7 @@ public final class EOwin32$EOφ extends PhDefault implements Atom {
static {
EOwin32$EOφ.FUNCTIONS.put("WriteFile", WriteFileFuncCall::new);
EOwin32$EOφ.FUNCTIONS.put("ReadFile", ReadFileFuncCall::new);
}
@Override

View File

@ -41,11 +41,21 @@ public interface CStdLib extends Library {
*/
CStdLib INSTANCE = Native.load("c", CStdLib.class);
/**
* Standard input file descriptor.
*/
int STDIN_FILENO = 0;
/**
* Standard output file descriptor.
*/
int STDOUT_FILENO = 1;
/**
* Open flag for reading only.
*/
int O_RDONLY = 0;
/**
* Open flag for reading and writing.
*/
@ -105,4 +115,19 @@ public interface CStdLib extends Library {
* @return Number of bytes was read.
*/
int read(int descriptor, byte[] buf, int size);
/**
* Get global errno variable.
* On Posix systems the global variable errno is set to an error code that provides more
* details about the last failure.
* @return The errno value
*/
int errno();
/**
* Get human-readable string that describes the error.
* @param errno The errno value
* @return Error string
*/
String strerror(int errno);
}

View File

@ -28,6 +28,7 @@
package EOorg.EOeolang.EOsys.Posix; // NOPMD
import EOorg.EOeolang.EOsys.Syscall;
import java.util.Arrays;
import org.eolang.Data;
import org.eolang.Dataized;
import org.eolang.Phi;
@ -55,13 +56,11 @@ public final class ReadSyscall implements Syscall {
final int size = new Dataized(params[1]).asNumber().intValue();
final Phi result = this.posix.take("return").copy();
final byte[] buf = new byte[(int) size];
result.put(
0,
new Data.ToPhi(
CStdLib.INSTANCE.read(new Dataized(params[0]).asNumber().intValue(), buf, size)
)
final int count = CStdLib.INSTANCE.read(
new Dataized(params[0]).asNumber().intValue(), buf, size
);
result.put(1, new Data.ToPhi(buf));
result.put(0, new Data.ToPhi(count));
result.put(1, new Data.ToPhi(Arrays.copyOf(buf, count)));
return result;
}
}

View File

@ -101,6 +101,34 @@ public interface Kernel32 extends StdCallLibrary, WinNT, Wincon {
OVERLAPPED overlapped
);
/**
* Reads data from the specified file or input/output (I/O) device. Reads
* occur at the position specified by the file pointer if supported by the
* device.
* @param handle A handle to the device (for example, a file, file stream,
* physical disk, volume, console buffer, tape drive, socket,
* communications resource, mailslot, or pipe).
* @param buffer A pointer to the buffer that receives the data read from a
* file or device.
* @param count The maximum number of bytes to be read.
* @param read A pointer to the variable that receives the number of bytes
* read when using a synchronous hFile parameter
* @param overlapped A pointer to an OVERLAPPED structure is required if the handle
* parameter was opened with FILE_FLAG_OVERLAPPED, otherwise it can be NULL.
* @return If the function succeeds, the return value is nonzero (TRUE). If
* the function fails, or is completing asynchronously, the return
* value is zero (FALSE).
* @checkstyle MethodNameCheck (5 lines)
* @checkstyle ParameterNumberCheck (20 lines)
*/
boolean ReadFile(
HANDLE handle,
byte[] buffer,
int count,
IntByReference read,
WinBase.OVERLAPPED overlapped
);
/**
* The CreateFile function creates or opens a file, file stream, directory, physical disk,
* volume, console buffer, tape drive, communications resource, mailslot, or named pipe. The

View File

@ -0,0 +1,78 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2024 Objectionary.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* @checkstyle PackageNameCheck (4 lines)
* @checkstyle TrailingCommentCheck (3 lines)
*/
package EOorg.EOeolang.EOsys.Win32; // NOPMD
import EOorg.EOeolang.EOsys.Syscall;
import com.sun.jna.ptr.IntByReference;
import java.util.Arrays;
import org.eolang.Data;
import org.eolang.Dataized;
import org.eolang.Phi;
/**
* ReadFile kernel32 function call.
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile">here for details</a>
* @since 0.40.0
*/
public final class ReadFileFuncCall implements Syscall {
/**
* Win32 object.
*/
private final Phi win;
/**
* Ctor.
* @param win Win32 object
*/
public ReadFileFuncCall(final Phi win) {
this.win = win;
}
@Override
public Phi make(final Phi... params) {
final int size = new Dataized(params[1]).asNumber().intValue();
final byte[] buf = new byte[(int) size];
final IntByReference read = new IntByReference();
final Phi result = this.win.take("return").copy();
result.put(
0,
new Data.ToPhi(
Kernel32.INSTANCE.ReadFile(
Kernel32.INSTANCE.GetStdHandle(new Dataized(params[0]).asNumber().intValue()),
buf,
size,
read,
null
)
)
);
result.put(1, new Data.ToPhi(Arrays.copyOf(buf, read.getValue())));
return result;
}
}

View File

@ -59,6 +59,13 @@ public interface WinNT extends WinDef, WinBase, BaseTSD {
*/
int CREATE_ALWAYS = 2;
/**
* The OPEN_EXISTING flag tells the CreateFile function to open the file only if
* it already exists. If the file doesn't exist, the function fails, and an
* error is returned (typically ERROR_FILE_NOT_FOUND).
*/
int OPEN_EXISTING = 3;
/**
* This flag specifies the desired access to the file. GENERIC_WRITE allows for writing
* data to the file.
@ -68,6 +75,14 @@ public interface WinNT extends WinDef, WinBase, BaseTSD {
*/
int GENERIC_WRITE = 0x40000000;
/**
* This flag grants read access to the file or resource.
* When you specify GENERIC_READ, the handle you obtain will allow you to read the
* contents of the file or resource.
* Value: 0x80000000
*/
int GENERIC_READ = 0x80000000;
/**
* Handle to an object.
* @since 0.40

View File

@ -35,6 +35,11 @@ package EOorg.EOeolang.EOsys.Win32; // NOPMD
*/
@SuppressWarnings("PMD.ConstantsInInterface")
public interface Wincon {
/**
* Standard input handle.
*/
int STD_INPUT_HANDLE = -10;
/**
* Standard output handle.
*/