Skip to content

Commit

Permalink
internal: add sync_io package
Browse files Browse the repository at this point in the history
  • Loading branch information
Young-Flash committed Jan 21, 2025
1 parent 5a17272 commit b8d9b93
Show file tree
Hide file tree
Showing 10 changed files with 450 additions and 0 deletions.
18 changes: 18 additions & 0 deletions sync_io/moon.pkg.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
77 changes: 77 additions & 0 deletions sync_io/native_stub.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "moonbit.h"

#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#else
#include <dirent.h>
#include <unistd.h>
#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);
}
54 changes: 54 additions & 0 deletions sync_io/sync_io.mbt
Original file line number Diff line number Diff line change
@@ -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)
}
14 changes: 14 additions & 0 deletions sync_io/sync_io.mbti
Original file line number Diff line number Diff line change
@@ -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

54 changes: 54 additions & 0 deletions sync_io/sync_io_js.mbt
Original file line number Diff line number Diff line change
@@ -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);
#| }
78 changes: 78 additions & 0 deletions sync_io/sync_io_native.mbt
Original file line number Diff line number Diff line change
@@ -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)
}
21 changes: 21 additions & 0 deletions sync_io/sync_io_test.mbt
Original file line number Diff line number Diff line change
@@ -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!")
}
43 changes: 43 additions & 0 deletions sync_io/sync_io_wasm.mbt
Original file line number Diff line number Diff line change
@@ -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"
Binary file added sync_io/test.txt
Binary file not shown.
Loading

0 comments on commit b8d9b93

Please sign in to comment.