From b8d9b93a289cfe22e25636b8016ae3fadad91e81 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Tue, 21 Jan 2025 14:48:48 +0800 Subject: [PATCH] internal: add sync_io package --- sync_io/moon.pkg.json | 18 ++++++++ sync_io/native_stub.c | 77 +++++++++++++++++++++++++++++++ sync_io/sync_io.mbt | 54 ++++++++++++++++++++++ sync_io/sync_io.mbti | 14 ++++++ sync_io/sync_io_js.mbt | 54 ++++++++++++++++++++++ sync_io/sync_io_native.mbt | 78 +++++++++++++++++++++++++++++++ sync_io/sync_io_test.mbt | 21 +++++++++ sync_io/sync_io_wasm.mbt | 43 ++++++++++++++++++ sync_io/test.txt | Bin 0 -> 26 bytes sync_io/util.mbt | 91 +++++++++++++++++++++++++++++++++++++ 10 files changed, 450 insertions(+) create mode 100644 sync_io/moon.pkg.json create mode 100644 sync_io/native_stub.c create mode 100644 sync_io/sync_io.mbt create mode 100644 sync_io/sync_io.mbti create mode 100644 sync_io/sync_io_js.mbt create mode 100644 sync_io/sync_io_native.mbt create mode 100644 sync_io/sync_io_test.mbt create mode 100644 sync_io/sync_io_wasm.mbt create mode 100644 sync_io/test.txt create mode 100644 sync_io/util.mbt diff --git a/sync_io/moon.pkg.json b/sync_io/moon.pkg.json new file mode 100644 index 0000000000..3cbc9dfd35 --- /dev/null +++ b/sync_io/moon.pkg.json @@ -0,0 +1,18 @@ +{ + "import": [ + "moonbitlang/core/string", + "moonbitlang/core/builtin", + "moonbitlang/core/array", + "moonbitlang/core/bytes" + ], + "targets": { + "sync_io_wasm.mbt": ["wasm", "wasm-gc"], + "sync_io_js.mbt": ["js"], + "sync_io_native.mbt": ["native"] + }, + "link": { + "native": { + "cc-flags": "./sync_io/native_stub.c" + } + } +} diff --git a/sync_io/native_stub.c b/sync_io/native_stub.c new file mode 100644 index 0000000000..74ca9b4942 --- /dev/null +++ b/sync_io/native_stub.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include "moonbit.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif + +int path_exists(struct moonbit_bytes *path) { + struct stat buffer; + int status = stat((const char *)(path->data), &buffer); + if (status == 0) { + return 0; + } + return -1; +} + +struct moonbit_bytes* read_file_to_bytes(struct moonbit_bytes *filename) { + FILE *file = fopen((const char*)(filename->data), "rb"); + if (file == NULL) { + perror("fopen"); + return NULL; + } + + // move file pointer to the end of the file + if (fseek(file, 0, SEEK_END) != 0) { + perror("fseek"); + fclose(file); + return NULL; + } + + // get the current position of the file pointer, which is the file size + long size = ftell(file); + if (size == -1L) { + perror("ftell"); + fclose(file); + return NULL; + } + + if (fseek(file, 0, SEEK_SET) != 0) { + perror("fseek"); + fclose(file); + return NULL; + } + + struct moonbit_bytes* bytes = moonbit_make_bytes(size, 0); + + // read the file content into the bytes->data + size_t bytes_read = fread(bytes->data, 1, (size_t)size, file); + if (bytes_read != (size_t)size) { + perror("fread"); + fclose(file); + return NULL; + } + + // close the file + if (fclose(file) != 0) { + perror("fclose"); + return NULL; + } + + return bytes; +} + +void write_bytes_to_file(struct moonbit_bytes* path, struct moonbit_bytes* content) { + FILE *file = fopen((const char *)(path->data), "wb"); + size_t content_size = Moonbit_array_length(content); + fwrite(content->data, 1, content_size, file); + fflush(file); + fclose(file); +} diff --git a/sync_io/sync_io.mbt b/sync_io/sync_io.mbt new file mode 100644 index 0000000000..87808dd78a --- /dev/null +++ b/sync_io/sync_io.mbt @@ -0,0 +1,54 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +type! IOError { + NotFound(String) +} + +///| +impl Show for IOError with output(self, logger) { + logger.write_string(self.to_string()) +} + +///| +fn IOError::to_string(self : IOError) -> String { + match self { + IOError::NotFound(path) => "`\{path}` does not exist" + } +} + +///| Writes an array of bytes to a file at the specified path. +/// +/// # Parameters +/// +/// - `path` : The path to the file where the bytes will be written. +/// - `content` : An array of bytes to be written to the file. +pub fn write_bytes_to_file(path~ : String, content~ : Bytes) -> Unit { + write_bytes_to_file_internal(path, content) +} + +///| Reads the content of a file specified by the given path and returns its +/// content as an array of bytes. If the file does not exist, an error is raised. +/// +/// # Parameters +/// +/// - `path` : The path to the file to be read. +/// +/// # Returns +/// +/// - An array of bytes representing the content of the file. +pub fn read_file_to_bytes(path~ : String) -> Bytes! { + read_file_to_bytes_internal!(path) +} diff --git a/sync_io/sync_io.mbti b/sync_io/sync_io.mbti new file mode 100644 index 0000000000..5ee2b8a6b7 --- /dev/null +++ b/sync_io/sync_io.mbti @@ -0,0 +1,14 @@ +package moonbitlang/core/sync_io + +// Values +fn read_file_to_bytes(path~ : String) -> Bytes! + +fn write_bytes_to_file(path~ : String, content~ : Bytes) -> Unit + +// Types and methods +type IOError + +// Type aliases + +// Traits + diff --git a/sync_io/sync_io_js.mbt b/sync_io/sync_io_js.mbt new file mode 100644 index 0000000000..b9962f9ee8 --- /dev/null +++ b/sync_io/sync_io_js.mbt @@ -0,0 +1,54 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +fn read_file_to_bytes_internal(path : String) -> Bytes! { + guard path_exists_internal(path) else { raise IOError::NotFound(path) } + Bytes::from_iter(read_file_to_bytes_ffi(path).iter()) +} + +///| +extern "js" fn read_file_to_bytes_ffi(path : String) -> FixedArray[Byte] = + #| function(path) { + #| fs = require('fs'); + #| let content = fs.readFileSync(path); + #| return content; + #| } + +///| +fn write_bytes_to_file_internal(path : String, content : Bytes) -> Unit { + write_bytes_to_file_ffi(path, FixedArray::from_iter(content.iter())) +} + +///| +extern "js" fn write_bytes_to_file_ffi( + path : String, + content : FixedArray[Byte] +) = + #| function(path, content) { + #| fs = require('fs'); + #| fs.writeFileSync(path, Buffer.from(content)); + #| } + +///| +fn path_exists_internal(path : String) -> Bool { + path_exists_ffi(path) +} + +///| +extern "js" fn path_exists_ffi(path : String) -> Bool = + #| function(path) { + #| fs = require('fs'); + #| return fs.existsSync(path); + #| } diff --git a/sync_io/sync_io_native.mbt b/sync_io/sync_io_native.mbt new file mode 100644 index 0000000000..865530303f --- /dev/null +++ b/sync_io/sync_io_native.mbt @@ -0,0 +1,78 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +fn read_file_to_bytes_internal(path : String) -> Bytes! { + guard path_exists_internal(path) else { raise IOError::NotFound(path) } + let path_bytes = mbt_string_to_utf8_bytes(path, true) + read_file_to_bytes_ffi(path_bytes) +} + +///| +extern "C" fn read_file_to_bytes_ffi(path : Bytes) -> Bytes = "read_file_to_bytes" + +///| +fn write_bytes_to_file_internal(path : String, content : Bytes) -> Unit { + let path_bytes = mbt_string_to_utf8_bytes(path, true) + write_bytes_to_file_ffi(path_bytes, content) +} + +///| +extern "C" fn write_bytes_to_file_ffi(path : Bytes, content : Bytes) = "write_bytes_to_file" + +///| +fn path_exists_internal(path : String) -> Bool { + path_exists_ffi(mbt_string_to_utf8_bytes(path, true)) == 0 +} + +///| +extern "C" fn path_exists_ffi(path : Bytes) -> Int = "path_exists" + +///| +fn mbt_string_to_utf8_bytes(str : String, is_c_str : Bool) -> Bytes { + let res : Array[Byte] = [] + let len = str.length() + let mut i = 0 + while i < len { + let mut c = str[i].to_int() + if 0xD800 <= c && c <= 0xDBFF { + c -= 0xD800 + i = i + 1 + let l = str[i].to_int() - 0xDC00 + c = (c << 10) + l + 0x10000 + } + + // stdout accepts UTF-8, so convert the stream to UTF-8 first + if c < 0x80 { + res.push(c.to_byte()) + } else if c < 0x800 { + res.push((0xc0 + (c >> 6)).to_byte()) + res.push((0x80 + (c & 0x3f)).to_byte()) + } else if c < 0x10000 { + res.push((0xe0 + (c >> 12)).to_byte()) + res.push((0x80 + ((c >> 6) & 0x3f)).to_byte()) + res.push((0x80 + (c & 0x3f)).to_byte()) + } else { + res.push((0xf0 + (c >> 18)).to_byte()) + res.push((0x80 + ((c >> 12) & 0x3f)).to_byte()) + res.push((0x80 + ((c >> 6) & 0x3f)).to_byte()) + res.push((0x80 + (c & 0x3f)).to_byte()) + } + i = i + 1 + } + if is_c_str { + res.push((0).to_byte()) + } + Bytes::from_array(res) +} diff --git a/sync_io/sync_io_test.mbt b/sync_io/sync_io_test.mbt new file mode 100644 index 0000000000..7d6e1e7a0c --- /dev/null +++ b/sync_io/sync_io_test.mbt @@ -0,0 +1,21 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +test "write_and_read" { + let path = "./sync_io/test.txt" + let content = "Hello, World!".to_bytes() + @sync_io.write_bytes_to_file(path~, content~) + let byte = @sync_io.read_file_to_bytes!(path~) + inspect!(byte.to_unchecked_string(), content="Hello, World!") +} diff --git a/sync_io/sync_io_wasm.mbt b/sync_io/sync_io_wasm.mbt new file mode 100644 index 0000000000..bad51b9b67 --- /dev/null +++ b/sync_io/sync_io_wasm.mbt @@ -0,0 +1,43 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +fn read_file_to_bytes_internal(path : String) -> Bytes! { + guard path_exists_internal(path) else { raise IOError::NotFound(path) } + let path = string_to_extern(path) + let content = read_file_to_bytes_ffi(path) + byte_array_from_extern(content) +} + +///| +fn read_file_to_bytes_ffi(path : XExternString) -> XExternByteArray = "__moonbit_fs_unstable" "read_file_to_bytes" + +///| +fn write_bytes_to_file_internal(path : String, content : Bytes) -> Unit { + let path = string_to_extern(path) + let content = byte_array_to_extern(content) + write_bytes_to_file_ffi(path, content) +} + +///| +fn write_bytes_to_file_ffi(path : XExternString, content : XExternByteArray) = "__moonbit_fs_unstable" "write_bytes_to_file" + +///| +fn path_exists_internal(path : String) -> Bool { + let path = string_to_extern(path) + path_exists_ffi(path) +} + +///| +fn path_exists_ffi(path : XExternString) -> Bool = "__moonbit_fs_unstable" "path_exists" diff --git a/sync_io/test.txt b/sync_io/test.txt new file mode 100644 index 0000000000000000000000000000000000000000..ba4d029576828de0e17bc0ef33eb65e05f395376 GIT binary patch literal 26 dcmeZZNM* XStringCreateHandle = "__moonbit_fs_unstable" "begin_create_string" + +///| +fn string_append_char(handle : XStringCreateHandle, ch : Char) = "__moonbit_fs_unstable" "string_append_char" + +///| +fn finish_create_string(handle : XStringCreateHandle) -> XExternString = "__moonbit_fs_unstable" "finish_create_string" + +///| +fn string_to_extern(s : String) -> XExternString { + let handle = begin_create_string() + for i = 0; i < s.length(); i = i + 1 { + string_append_char(handle, s[i]) + } + finish_create_string(handle) +} + +///| +fn begin_read_byte_array(s : XExternByteArray) -> XByteArrayReadHandle = "__moonbit_fs_unstable" "begin_read_byte_array" + +///| +fn byte_array_read_byte(handle : XByteArrayReadHandle) -> Int = "__moonbit_fs_unstable" "byte_array_read_byte" + +///| +fn finish_read_byte_array(handle : XByteArrayReadHandle) = "__moonbit_fs_unstable" "finish_read_byte_array" + +///| +fn begin_create_byte_array() -> XByteArrayCreateHandle = "__moonbit_fs_unstable" "begin_create_byte_array" + +///| +fn byte_array_append_byte(handle : XByteArrayCreateHandle, ch : Int) = "__moonbit_fs_unstable" "byte_array_append_byte" + +///| +fn finish_create_byte_array( + handle : XByteArrayCreateHandle +) -> XExternByteArray = "__moonbit_fs_unstable" "finish_create_byte_array" + +///| +fn byte_array_to_extern(s : Bytes) -> XExternByteArray { + let handle = begin_create_byte_array() + for i = 0; i < s.length(); i = i + 1 { + byte_array_append_byte(handle, s[i].to_int()) + } + finish_create_byte_array(handle) +} + +///| +fn byte_array_from_extern(e : XExternByteArray) -> Bytes { + let buf = Array::new() + let handle = begin_read_byte_array(e) + while true { + let ch = byte_array_read_byte(handle) + if ch == -1 { + break + } else { + buf.push(ch.to_byte()) + } + } + finish_read_byte_array(handle) + Bytes::from_array(buf) +}