Skip to content

Commit b8d9b93

Browse files
committed
internal: add sync_io package
1 parent 5a17272 commit b8d9b93

File tree

10 files changed

+450
-0
lines changed

10 files changed

+450
-0
lines changed

sync_io/moon.pkg.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"import": [
3+
"moonbitlang/core/string",
4+
"moonbitlang/core/builtin",
5+
"moonbitlang/core/array",
6+
"moonbitlang/core/bytes"
7+
],
8+
"targets": {
9+
"sync_io_wasm.mbt": ["wasm", "wasm-gc"],
10+
"sync_io_js.mbt": ["js"],
11+
"sync_io_native.mbt": ["native"]
12+
},
13+
"link": {
14+
"native": {
15+
"cc-flags": "./sync_io/native_stub.c"
16+
}
17+
}
18+
}

sync_io/native_stub.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <sys/stat.h>
5+
#include "moonbit.h"
6+
7+
#ifdef _WIN32
8+
#include <windows.h>
9+
#include <direct.h>
10+
#else
11+
#include <dirent.h>
12+
#include <unistd.h>
13+
#endif
14+
15+
int path_exists(struct moonbit_bytes *path) {
16+
struct stat buffer;
17+
int status = stat((const char *)(path->data), &buffer);
18+
if (status == 0) {
19+
return 0;
20+
}
21+
return -1;
22+
}
23+
24+
struct moonbit_bytes* read_file_to_bytes(struct moonbit_bytes *filename) {
25+
FILE *file = fopen((const char*)(filename->data), "rb");
26+
if (file == NULL) {
27+
perror("fopen");
28+
return NULL;
29+
}
30+
31+
// move file pointer to the end of the file
32+
if (fseek(file, 0, SEEK_END) != 0) {
33+
perror("fseek");
34+
fclose(file);
35+
return NULL;
36+
}
37+
38+
// get the current position of the file pointer, which is the file size
39+
long size = ftell(file);
40+
if (size == -1L) {
41+
perror("ftell");
42+
fclose(file);
43+
return NULL;
44+
}
45+
46+
if (fseek(file, 0, SEEK_SET) != 0) {
47+
perror("fseek");
48+
fclose(file);
49+
return NULL;
50+
}
51+
52+
struct moonbit_bytes* bytes = moonbit_make_bytes(size, 0);
53+
54+
// read the file content into the bytes->data
55+
size_t bytes_read = fread(bytes->data, 1, (size_t)size, file);
56+
if (bytes_read != (size_t)size) {
57+
perror("fread");
58+
fclose(file);
59+
return NULL;
60+
}
61+
62+
// close the file
63+
if (fclose(file) != 0) {
64+
perror("fclose");
65+
return NULL;
66+
}
67+
68+
return bytes;
69+
}
70+
71+
void write_bytes_to_file(struct moonbit_bytes* path, struct moonbit_bytes* content) {
72+
FILE *file = fopen((const char *)(path->data), "wb");
73+
size_t content_size = Moonbit_array_length(content);
74+
fwrite(content->data, 1, content_size, file);
75+
fflush(file);
76+
fclose(file);
77+
}

sync_io/sync_io.mbt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2025 International Digital Economy Academy
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
///|
16+
type! IOError {
17+
NotFound(String)
18+
}
19+
20+
///|
21+
impl Show for IOError with output(self, logger) {
22+
logger.write_string(self.to_string())
23+
}
24+
25+
///|
26+
fn IOError::to_string(self : IOError) -> String {
27+
match self {
28+
IOError::NotFound(path) => "`\{path}` does not exist"
29+
}
30+
}
31+
32+
///| Writes an array of bytes to a file at the specified path.
33+
///
34+
/// # Parameters
35+
///
36+
/// - `path` : The path to the file where the bytes will be written.
37+
/// - `content` : An array of bytes to be written to the file.
38+
pub fn write_bytes_to_file(path~ : String, content~ : Bytes) -> Unit {
39+
write_bytes_to_file_internal(path, content)
40+
}
41+
42+
///| Reads the content of a file specified by the given path and returns its
43+
/// content as an array of bytes. If the file does not exist, an error is raised.
44+
///
45+
/// # Parameters
46+
///
47+
/// - `path` : The path to the file to be read.
48+
///
49+
/// # Returns
50+
///
51+
/// - An array of bytes representing the content of the file.
52+
pub fn read_file_to_bytes(path~ : String) -> Bytes! {
53+
read_file_to_bytes_internal!(path)
54+
}

sync_io/sync_io.mbti

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package moonbitlang/core/sync_io
2+
3+
// Values
4+
fn read_file_to_bytes(path~ : String) -> Bytes!
5+
6+
fn write_bytes_to_file(path~ : String, content~ : Bytes) -> Unit
7+
8+
// Types and methods
9+
type IOError
10+
11+
// Type aliases
12+
13+
// Traits
14+

sync_io/sync_io_js.mbt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2025 International Digital Economy Academy
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
///|
16+
fn read_file_to_bytes_internal(path : String) -> Bytes! {
17+
guard path_exists_internal(path) else { raise IOError::NotFound(path) }
18+
Bytes::from_iter(read_file_to_bytes_ffi(path).iter())
19+
}
20+
21+
///|
22+
extern "js" fn read_file_to_bytes_ffi(path : String) -> FixedArray[Byte] =
23+
#| function(path) {
24+
#| fs = require('fs');
25+
#| let content = fs.readFileSync(path);
26+
#| return content;
27+
#| }
28+
29+
///|
30+
fn write_bytes_to_file_internal(path : String, content : Bytes) -> Unit {
31+
write_bytes_to_file_ffi(path, FixedArray::from_iter(content.iter()))
32+
}
33+
34+
///|
35+
extern "js" fn write_bytes_to_file_ffi(
36+
path : String,
37+
content : FixedArray[Byte]
38+
) =
39+
#| function(path, content) {
40+
#| fs = require('fs');
41+
#| fs.writeFileSync(path, Buffer.from(content));
42+
#| }
43+
44+
///|
45+
fn path_exists_internal(path : String) -> Bool {
46+
path_exists_ffi(path)
47+
}
48+
49+
///|
50+
extern "js" fn path_exists_ffi(path : String) -> Bool =
51+
#| function(path) {
52+
#| fs = require('fs');
53+
#| return fs.existsSync(path);
54+
#| }

sync_io/sync_io_native.mbt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2025 International Digital Economy Academy
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
///|
16+
fn read_file_to_bytes_internal(path : String) -> Bytes! {
17+
guard path_exists_internal(path) else { raise IOError::NotFound(path) }
18+
let path_bytes = mbt_string_to_utf8_bytes(path, true)
19+
read_file_to_bytes_ffi(path_bytes)
20+
}
21+
22+
///|
23+
extern "C" fn read_file_to_bytes_ffi(path : Bytes) -> Bytes = "read_file_to_bytes"
24+
25+
///|
26+
fn write_bytes_to_file_internal(path : String, content : Bytes) -> Unit {
27+
let path_bytes = mbt_string_to_utf8_bytes(path, true)
28+
write_bytes_to_file_ffi(path_bytes, content)
29+
}
30+
31+
///|
32+
extern "C" fn write_bytes_to_file_ffi(path : Bytes, content : Bytes) = "write_bytes_to_file"
33+
34+
///|
35+
fn path_exists_internal(path : String) -> Bool {
36+
path_exists_ffi(mbt_string_to_utf8_bytes(path, true)) == 0
37+
}
38+
39+
///|
40+
extern "C" fn path_exists_ffi(path : Bytes) -> Int = "path_exists"
41+
42+
///|
43+
fn mbt_string_to_utf8_bytes(str : String, is_c_str : Bool) -> Bytes {
44+
let res : Array[Byte] = []
45+
let len = str.length()
46+
let mut i = 0
47+
while i < len {
48+
let mut c = str[i].to_int()
49+
if 0xD800 <= c && c <= 0xDBFF {
50+
c -= 0xD800
51+
i = i + 1
52+
let l = str[i].to_int() - 0xDC00
53+
c = (c << 10) + l + 0x10000
54+
}
55+
56+
// stdout accepts UTF-8, so convert the stream to UTF-8 first
57+
if c < 0x80 {
58+
res.push(c.to_byte())
59+
} else if c < 0x800 {
60+
res.push((0xc0 + (c >> 6)).to_byte())
61+
res.push((0x80 + (c & 0x3f)).to_byte())
62+
} else if c < 0x10000 {
63+
res.push((0xe0 + (c >> 12)).to_byte())
64+
res.push((0x80 + ((c >> 6) & 0x3f)).to_byte())
65+
res.push((0x80 + (c & 0x3f)).to_byte())
66+
} else {
67+
res.push((0xf0 + (c >> 18)).to_byte())
68+
res.push((0x80 + ((c >> 12) & 0x3f)).to_byte())
69+
res.push((0x80 + ((c >> 6) & 0x3f)).to_byte())
70+
res.push((0x80 + (c & 0x3f)).to_byte())
71+
}
72+
i = i + 1
73+
}
74+
if is_c_str {
75+
res.push((0).to_byte())
76+
}
77+
Bytes::from_array(res)
78+
}

sync_io/sync_io_test.mbt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2025 International Digital Economy Academy
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
test "write_and_read" {
16+
let path = "./sync_io/test.txt"
17+
let content = "Hello, World!".to_bytes()
18+
@sync_io.write_bytes_to_file(path~, content~)
19+
let byte = @sync_io.read_file_to_bytes!(path~)
20+
inspect!(byte.to_unchecked_string(), content="Hello, World!")
21+
}

sync_io/sync_io_wasm.mbt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2025 International Digital Economy Academy
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
///|
16+
fn read_file_to_bytes_internal(path : String) -> Bytes! {
17+
guard path_exists_internal(path) else { raise IOError::NotFound(path) }
18+
let path = string_to_extern(path)
19+
let content = read_file_to_bytes_ffi(path)
20+
byte_array_from_extern(content)
21+
}
22+
23+
///|
24+
fn read_file_to_bytes_ffi(path : XExternString) -> XExternByteArray = "__moonbit_fs_unstable" "read_file_to_bytes"
25+
26+
///|
27+
fn write_bytes_to_file_internal(path : String, content : Bytes) -> Unit {
28+
let path = string_to_extern(path)
29+
let content = byte_array_to_extern(content)
30+
write_bytes_to_file_ffi(path, content)
31+
}
32+
33+
///|
34+
fn write_bytes_to_file_ffi(path : XExternString, content : XExternByteArray) = "__moonbit_fs_unstable" "write_bytes_to_file"
35+
36+
///|
37+
fn path_exists_internal(path : String) -> Bool {
38+
let path = string_to_extern(path)
39+
path_exists_ffi(path)
40+
}
41+
42+
///|
43+
fn path_exists_ffi(path : XExternString) -> Bool = "__moonbit_fs_unstable" "path_exists"

sync_io/test.txt

26 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)