@shd101wyy/yo 0.0.29 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/out/cjs/index.cjs +7558 -7830
- package/out/cjs/yo-cli.cjs +7557 -7829
- package/out/esm/index.mjs +7457 -7729
- package/out/types/src/codegen/async/runtime-core.d.ts +2 -1
- package/out/types/src/codegen/async/runtime-io-common.d.ts +3 -1
- package/out/types/src/codegen/async/runtime-io-linux.d.ts +1 -0
- package/out/types/src/codegen/async/runtime-io-macos.d.ts +1 -0
- package/out/types/src/codegen/async/runtime-io-windows.d.ts +1 -0
- package/out/types/src/codegen/async/runtime.d.ts +2 -1
- package/out/types/src/codegen/async/state-machine.d.ts +18 -2
- package/out/types/src/codegen/functions/context.d.ts +5 -0
- package/out/types/src/codegen/parallelism/runtime.d.ts +2 -1
- package/out/types/src/codegen/utils/index.d.ts +2 -0
- package/out/types/src/expr.d.ts +1 -0
- package/out/types/src/target.d.ts +1 -0
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/std/cli/arg_parser.yo +365 -0
- package/std/collections/array_list.yo +108 -0
- package/std/collections/hash_map.yo +1 -1
- package/std/collections/hash_set.yo +7 -7
- package/std/collections/linked_list.yo +1 -1
- package/std/encoding/base64.yo +73 -0
- package/std/fs/file.yo +113 -6
- package/std/fs/types.yo +21 -0
- package/std/glob/glob.yo +206 -0
- package/std/http/http.yo +196 -0
- package/std/io/reader.yo +17 -0
- package/std/io/writer.yo +19 -0
- package/std/net/tcp.yo +1 -1
- package/std/prelude.yo +69 -0
- package/std/string/string.yo +398 -4
- package/std/sync/cond.yo +19 -19
- package/std/sync/mutex.yo +16 -16
- package/std/sys/bufio/buf_reader.yo +2 -2
- package/std/sys/future.yo +3 -3
- package/std/toml/toml.yo +179 -0
- package/std/testing/assert.yo +0 -173
package/std/fs/file.yo
CHANGED
|
@@ -34,7 +34,9 @@ open import "../fmt";
|
|
|
34
34
|
{ Error, AnyError, Exception } :: import "../error";
|
|
35
35
|
{ Metadata } :: import "./metadata";
|
|
36
36
|
_metadata_mod :: import "./metadata";
|
|
37
|
-
{
|
|
37
|
+
{ Statx } :: import "../sys/statx";
|
|
38
|
+
IO_path :: import "../sys/path";
|
|
39
|
+
{ OpenMode, FilePermission, SeekFrom, _open_mode_to_flags, _open_mode_needs_perm, _seek_from_to_whence } :: import "./types";
|
|
38
40
|
IO_file :: import "../sys/file";
|
|
39
41
|
IO_seek :: import "../sys/seek";
|
|
40
42
|
{
|
|
@@ -193,9 +195,8 @@ impl(File,
|
|
|
193
195
|
}),
|
|
194
196
|
|
|
195
197
|
// Seek to a position. Returns the new absolute position.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
result := IO_seek.lseek(self._fd, offset, whence);
|
|
198
|
+
seek : (fn(self: Self, offset: i64, from: SeekFrom, using(exn : Exception)) -> i64)({
|
|
199
|
+
result := IO_seek.lseek(self._fd, offset, _seek_from_to_whence(from));
|
|
199
200
|
cond(
|
|
200
201
|
(result < i64(0)) => exn.throw(dyn IOError.from_errno(i32((i64(0) - result)))),
|
|
201
202
|
true => result
|
|
@@ -258,7 +259,7 @@ impl(File, Dispose(
|
|
|
258
259
|
})
|
|
259
260
|
));
|
|
260
261
|
|
|
261
|
-
export File, OpenMode, FilePermission;
|
|
262
|
+
export File, OpenMode, FilePermission, SeekFrom;
|
|
262
263
|
|
|
263
264
|
// ============================================================================
|
|
264
265
|
// Convenience functions
|
|
@@ -374,11 +375,117 @@ exists_cstr :: (fn(path: *(u8), using(io : IO)) -> Impl(Future(bool, IO)))(
|
|
|
374
375
|
exists(Path.from_cstr(path), using(io))
|
|
375
376
|
);
|
|
376
377
|
|
|
378
|
+
// Check if a path is a regular file.
|
|
379
|
+
// Returns false for any error (including file not found).
|
|
380
|
+
is_file :: (fn(path: Path, using(io : IO)) -> Impl(Future(bool, IO)))(
|
|
381
|
+
io.async((using(io : IO)) => {
|
|
382
|
+
path_str := path.to_string();
|
|
383
|
+
cstr_bytes := path_str.to_cstr();
|
|
384
|
+
cstr := cstr_bytes.ptr().unwrap();
|
|
385
|
+
buf_size := __yo_statx_buf_size();
|
|
386
|
+
buf := *(u8)(malloc(buf_size).unwrap());
|
|
387
|
+
result := io.await(IO_file.statx(AT_FDCWD, cstr, AT_STATX_SYNC_AS_STAT, STATX_BASIC_STATS, buf));
|
|
388
|
+
r := cond(
|
|
389
|
+
(result < i32(0)) => false,
|
|
390
|
+
true => {
|
|
391
|
+
sx := Statx(_buf_ptr: buf, _buf_size: buf_size);
|
|
392
|
+
sx.is_file()
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
free(.Some(*(void)(buf)));
|
|
396
|
+
r
|
|
397
|
+
})
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// Check if a path is a regular file (str version).
|
|
401
|
+
is_file_str :: (fn(path: str, using(io : IO)) -> Impl(Future(bool, IO)))(
|
|
402
|
+
is_file(Path.new(String.from(path)), using(io))
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
// Check if a path is a regular file (C string version).
|
|
406
|
+
is_file_cstr :: (fn(path: *(u8), using(io : IO)) -> Impl(Future(bool, IO)))(
|
|
407
|
+
is_file(Path.from_cstr(path), using(io))
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// Check if a path is a directory.
|
|
411
|
+
// Returns false for any error (including file not found).
|
|
412
|
+
is_dir :: (fn(path: Path, using(io : IO)) -> Impl(Future(bool, IO)))(
|
|
413
|
+
io.async((using(io : IO)) => {
|
|
414
|
+
path_str := path.to_string();
|
|
415
|
+
cstr_bytes := path_str.to_cstr();
|
|
416
|
+
cstr := cstr_bytes.ptr().unwrap();
|
|
417
|
+
buf_size := __yo_statx_buf_size();
|
|
418
|
+
buf := *(u8)(malloc(buf_size).unwrap());
|
|
419
|
+
result := io.await(IO_file.statx(AT_FDCWD, cstr, AT_STATX_SYNC_AS_STAT, STATX_BASIC_STATS, buf));
|
|
420
|
+
r := cond(
|
|
421
|
+
(result < i32(0)) => false,
|
|
422
|
+
true => {
|
|
423
|
+
sx := Statx(_buf_ptr: buf, _buf_size: buf_size);
|
|
424
|
+
sx.is_directory()
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
free(.Some(*(void)(buf)));
|
|
428
|
+
r
|
|
429
|
+
})
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// Check if a path is a directory (str version).
|
|
433
|
+
is_dir_str :: (fn(path: str, using(io : IO)) -> Impl(Future(bool, IO)))(
|
|
434
|
+
is_dir(Path.new(String.from(path)), using(io))
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// Check if a path is a directory (C string version).
|
|
438
|
+
is_dir_cstr :: (fn(path: *(u8), using(io : IO)) -> Impl(Future(bool, IO)))(
|
|
439
|
+
is_dir(Path.from_cstr(path), using(io))
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
// Resolve a path to its canonical absolute form (resolves symlinks, . and ..).
|
|
443
|
+
// Throws on error (e.g. path does not exist).
|
|
444
|
+
canonical :: (fn(path: Path, using(io : IO)) -> Impl(Future(Path, IO, Exception)))(
|
|
445
|
+
io.async((using(io, exn)) => {
|
|
446
|
+
path_str := path.to_string();
|
|
447
|
+
cstr_bytes := path_str.to_cstr();
|
|
448
|
+
cstr := cstr_bytes.ptr().unwrap();
|
|
449
|
+
// Async stat to verify path exists (provides await point for proper RC cleanup)
|
|
450
|
+
buf_size := __yo_statx_buf_size();
|
|
451
|
+
stat_buf := *(u8)(malloc(buf_size).unwrap());
|
|
452
|
+
stat_result := io.await(IO_file.statx(AT_FDCWD, cstr, AT_STATX_SYNC_AS_STAT, STATX_BASIC_STATS, stat_buf));
|
|
453
|
+
free(.Some(*(void)(stat_buf)));
|
|
454
|
+
IOError.check(stat_result);
|
|
455
|
+
// Resolve the canonical path
|
|
456
|
+
resolved_buf := *(u8)(malloc(usize(4096)).unwrap());
|
|
457
|
+
rp_result := IO_path.realpath(cstr, resolved_buf);
|
|
458
|
+
cond(
|
|
459
|
+
(rp_result < i32(0)) => {
|
|
460
|
+
free(.Some(*(void)(resolved_buf)));
|
|
461
|
+
IOError.check(rp_result);
|
|
462
|
+
},
|
|
463
|
+
true => ()
|
|
464
|
+
);
|
|
465
|
+
resolved := String.from_cstr(resolved_buf).unwrap();
|
|
466
|
+
free(.Some(*(void)(resolved_buf)));
|
|
467
|
+
Path.new(resolved)
|
|
468
|
+
})
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
// Resolve a path to its canonical absolute form (str version).
|
|
472
|
+
canonical_str :: (fn(path: str, using(io : IO)) -> Impl(Future(Path, IO, Exception)))(
|
|
473
|
+
canonical(Path.new(String.from(path)))
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
// Resolve a path to its canonical absolute form (C string version).
|
|
477
|
+
canonical_cstr :: (fn(path: *(u8), using(io : IO)) -> Impl(Future(Path, IO, Exception)))(
|
|
478
|
+
canonical(Path.from_cstr(path))
|
|
479
|
+
);
|
|
480
|
+
|
|
377
481
|
export
|
|
378
482
|
read_string, read_string_str, read_string_cstr,
|
|
379
483
|
read_file, read_file_str, read_file_cstr,
|
|
380
484
|
write_file, write_file_str, write_file_cstr,
|
|
381
485
|
write_bytes,
|
|
382
486
|
append_file, append_file_str,
|
|
383
|
-
exists, exists_str, exists_cstr
|
|
487
|
+
exists, exists_str, exists_cstr,
|
|
488
|
+
is_file, is_file_str, is_file_cstr,
|
|
489
|
+
is_dir, is_dir_str, is_dir_cstr,
|
|
490
|
+
canonical, canonical_str, canonical_cstr
|
|
384
491
|
;
|
package/std/fs/types.yo
CHANGED
|
@@ -76,3 +76,24 @@ impl(FilePermission,
|
|
|
76
76
|
);
|
|
77
77
|
|
|
78
78
|
export FilePermission;
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// SeekFrom — file seek position reference
|
|
82
|
+
// ============================================================================
|
|
83
|
+
|
|
84
|
+
SeekFrom :: enum(
|
|
85
|
+
Start, // Seek from the beginning of the file
|
|
86
|
+
Current, // Seek relative to the current position
|
|
87
|
+
End // Seek relative to the end of the file
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Convert SeekFrom to POSIX whence constant (i32).
|
|
91
|
+
_seek_from_to_whence :: (fn(from: SeekFrom) -> i32)(
|
|
92
|
+
match(from,
|
|
93
|
+
.Start => i32(0),
|
|
94
|
+
.Current => i32(1),
|
|
95
|
+
.End => i32(2)
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
export SeekFrom, _seek_from_to_whence;
|
package/std/glob/glob.yo
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
open import "../string";
|
|
2
|
+
{ ArrayList } :: import "../collections/array_list";
|
|
3
|
+
|
|
4
|
+
// Recursive byte-level glob pattern matching
|
|
5
|
+
_glob_match_impl :: (fn(pb: ArrayList(u8), pi: usize, tb: ArrayList(u8), ti: usize) -> bool)({
|
|
6
|
+
// Base: both pattern and text consumed
|
|
7
|
+
cond(
|
|
8
|
+
((pi == pb.len()) && (ti == tb.len())) => {
|
|
9
|
+
return true;
|
|
10
|
+
},
|
|
11
|
+
true => ()
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// Pattern consumed but text remains
|
|
15
|
+
cond(
|
|
16
|
+
(pi == pb.len()) => {
|
|
17
|
+
return false;
|
|
18
|
+
},
|
|
19
|
+
true => ()
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
p := pb.get(pi).unwrap();
|
|
23
|
+
|
|
24
|
+
// Handle * and **
|
|
25
|
+
cond(
|
|
26
|
+
(p == u8(42)) => {
|
|
27
|
+
is_dbl := cond(
|
|
28
|
+
((pi + usize(1)) < pb.len()) => (pb.get((pi + usize(1))).unwrap() == u8(42)),
|
|
29
|
+
true => false
|
|
30
|
+
);
|
|
31
|
+
cond(
|
|
32
|
+
is_dbl => {
|
|
33
|
+
// ** matches any characters including /
|
|
34
|
+
npi := (pi + usize(2));
|
|
35
|
+
// Skip optional trailing /
|
|
36
|
+
cond(
|
|
37
|
+
(npi < pb.len()) => {
|
|
38
|
+
cond(
|
|
39
|
+
(pb.get(npi).unwrap() == u8(47)) => {
|
|
40
|
+
npi = (npi + usize(1));
|
|
41
|
+
},
|
|
42
|
+
true => ()
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
true => ()
|
|
46
|
+
);
|
|
47
|
+
// Try ** matching nothing
|
|
48
|
+
cond(
|
|
49
|
+
recur(pb, npi, tb, ti) => {
|
|
50
|
+
return true;
|
|
51
|
+
},
|
|
52
|
+
true => ()
|
|
53
|
+
);
|
|
54
|
+
// Try ** consuming one char
|
|
55
|
+
cond(
|
|
56
|
+
(ti < tb.len()) => {
|
|
57
|
+
return recur(pb, pi, tb, (ti + usize(1)));
|
|
58
|
+
},
|
|
59
|
+
true => {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
true => {
|
|
65
|
+
// Single * matches any except /
|
|
66
|
+
// Try * matching nothing
|
|
67
|
+
cond(
|
|
68
|
+
recur(pb, (pi + usize(1)), tb, ti) => {
|
|
69
|
+
return true;
|
|
70
|
+
},
|
|
71
|
+
true => ()
|
|
72
|
+
);
|
|
73
|
+
// Try * consuming one non-/ char
|
|
74
|
+
cond(
|
|
75
|
+
(ti < tb.len()) => {
|
|
76
|
+
tc := tb.get(ti).unwrap();
|
|
77
|
+
cond(
|
|
78
|
+
(tc != u8(47)) => {
|
|
79
|
+
return recur(pb, pi, tb, (ti + usize(1)));
|
|
80
|
+
},
|
|
81
|
+
true => {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
true => {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
true => ()
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Text consumed but non-star pattern remains
|
|
97
|
+
cond(
|
|
98
|
+
(ti == tb.len()) => {
|
|
99
|
+
return false;
|
|
100
|
+
},
|
|
101
|
+
true => ()
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
t := tb.get(ti).unwrap();
|
|
105
|
+
|
|
106
|
+
// Handle ?
|
|
107
|
+
cond(
|
|
108
|
+
(p == u8(63)) => {
|
|
109
|
+
cond(
|
|
110
|
+
(t != u8(47)) => {
|
|
111
|
+
return recur(pb, (pi + usize(1)), tb, (ti + usize(1)));
|
|
112
|
+
},
|
|
113
|
+
true => {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
true => ()
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Handle [...]
|
|
122
|
+
cond(
|
|
123
|
+
(p == u8(91)) => {
|
|
124
|
+
ci := (pi + usize(1));
|
|
125
|
+
neg := false;
|
|
126
|
+
// Check for negation: ! or ^
|
|
127
|
+
cond(
|
|
128
|
+
(ci < pb.len()) => {
|
|
129
|
+
nc := pb.get(ci).unwrap();
|
|
130
|
+
cond(
|
|
131
|
+
((nc == u8(33)) || (nc == u8(94))) => {
|
|
132
|
+
neg = true;
|
|
133
|
+
ci = (ci + usize(1));
|
|
134
|
+
},
|
|
135
|
+
true => ()
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
true => ()
|
|
139
|
+
);
|
|
140
|
+
// Scan characters in class until ]
|
|
141
|
+
found := false;
|
|
142
|
+
cdone := false;
|
|
143
|
+
while (((ci < pb.len()) && (!(cdone)))), (ci = (ci + usize(1))), {
|
|
144
|
+
ch := pb.get(ci).unwrap();
|
|
145
|
+
cond(
|
|
146
|
+
(ch == u8(93)) => {
|
|
147
|
+
cdone = true;
|
|
148
|
+
},
|
|
149
|
+
true => {
|
|
150
|
+
cond(
|
|
151
|
+
(ch == t) => {
|
|
152
|
+
found = true;
|
|
153
|
+
},
|
|
154
|
+
true => ()
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
// Malformed pattern (no closing ])
|
|
160
|
+
cond(
|
|
161
|
+
(!(cdone)) => {
|
|
162
|
+
return false;
|
|
163
|
+
},
|
|
164
|
+
true => ()
|
|
165
|
+
);
|
|
166
|
+
matched := cond(
|
|
167
|
+
neg => !(found),
|
|
168
|
+
true => found
|
|
169
|
+
);
|
|
170
|
+
cond(
|
|
171
|
+
matched => {
|
|
172
|
+
return recur(pb, ci, tb, (ti + usize(1)));
|
|
173
|
+
},
|
|
174
|
+
true => {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
true => ()
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Literal byte match
|
|
183
|
+
cond(
|
|
184
|
+
(p == t) => recur(pb, (pi + usize(1)), tb, (ti + usize(1))),
|
|
185
|
+
true => false
|
|
186
|
+
)
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
glob_match :: (fn(pattern: String, text: String) -> bool)(
|
|
190
|
+
_glob_match_impl(pattern._bytes, usize(0), text._bytes, usize(0))
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
GlobPattern :: object(
|
|
194
|
+
_pattern : String
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
impl(GlobPattern,
|
|
198
|
+
new : (fn(pattern: String) -> Self)(
|
|
199
|
+
Self(_pattern: pattern)
|
|
200
|
+
),
|
|
201
|
+
matches : (fn(self: Self, text: String) -> bool)(
|
|
202
|
+
_glob_match_impl(self._pattern._bytes, usize(0), text._bytes, usize(0))
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
export glob_match, GlobPattern;
|
package/std/http/http.yo
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// std/http/http.yo - HTTP types and request/response builders
|
|
2
|
+
|
|
3
|
+
open import "../string";
|
|
4
|
+
{ ArrayList } :: import "../collections/array_list";
|
|
5
|
+
{ ToString } :: import "../fmt";
|
|
6
|
+
|
|
7
|
+
HttpMethod :: enum(GET, POST, PUT, DELETE, HEAD, PATCH);
|
|
8
|
+
|
|
9
|
+
impl(HttpMethod, ToString(
|
|
10
|
+
to_string : (self ->
|
|
11
|
+
match(self,
|
|
12
|
+
.GET => `GET`,
|
|
13
|
+
.POST => `POST`,
|
|
14
|
+
.PUT => `PUT`,
|
|
15
|
+
.DELETE => `DELETE`,
|
|
16
|
+
.HEAD => `HEAD`,
|
|
17
|
+
.PATCH => `PATCH`
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
));
|
|
21
|
+
|
|
22
|
+
HttpHeader :: object(name: String, value: String);
|
|
23
|
+
|
|
24
|
+
impl(HttpHeader,
|
|
25
|
+
new : (fn(name: String, value: String) -> Self)(
|
|
26
|
+
Self(name: name, value: value)
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
HttpRequest :: object(
|
|
31
|
+
method: HttpMethod,
|
|
32
|
+
path: String,
|
|
33
|
+
headers: ArrayList(HttpHeader),
|
|
34
|
+
body: String
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
impl(HttpRequest,
|
|
38
|
+
new : (fn(method: HttpMethod, path: String) -> Self)(
|
|
39
|
+
Self(method: method, path: path, headers: ArrayList(HttpHeader).new(), body: ``)
|
|
40
|
+
),
|
|
41
|
+
|
|
42
|
+
header : (fn(self: Self, name: String, value: String) -> Self)({
|
|
43
|
+
self.headers.push(HttpHeader.new(name, value));
|
|
44
|
+
self
|
|
45
|
+
}),
|
|
46
|
+
|
|
47
|
+
with_body : (fn(self: Self, body: String) -> Self)({
|
|
48
|
+
self.body = body;
|
|
49
|
+
self
|
|
50
|
+
}),
|
|
51
|
+
|
|
52
|
+
set_host : (fn(self: Self, host: String) -> unit)({
|
|
53
|
+
self.headers.push(HttpHeader.new(`Host`, host));
|
|
54
|
+
}),
|
|
55
|
+
|
|
56
|
+
set_header : (fn(self: Self, name: String, value: String) -> unit)({
|
|
57
|
+
self.headers.push(HttpHeader.new(name, value));
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
set_body : (fn(self: Self, body: String) -> unit)({
|
|
61
|
+
self.body = body;
|
|
62
|
+
}),
|
|
63
|
+
|
|
64
|
+
to_string : (fn(self: Self) -> String)({
|
|
65
|
+
result := `${self.method.to_string()} ${self.path} HTTP/1.1\r\n`;
|
|
66
|
+
i := usize(0);
|
|
67
|
+
while (i < self.headers.len()), (i = (i + usize(1))), {
|
|
68
|
+
h := self.headers.get(i).unwrap();
|
|
69
|
+
result = `${result}${h.name}: ${h.value}\r\n`;
|
|
70
|
+
};
|
|
71
|
+
result = `${result}\r\n`;
|
|
72
|
+
cond(
|
|
73
|
+
(!(self.body.is_empty())) => { result = `${result}${self.body}`; },
|
|
74
|
+
true => ()
|
|
75
|
+
);
|
|
76
|
+
result
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
HttpResponse :: object(
|
|
81
|
+
status_code: i32,
|
|
82
|
+
status_text: String,
|
|
83
|
+
headers: ArrayList(HttpHeader),
|
|
84
|
+
body: String
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
impl(HttpResponse,
|
|
88
|
+
new : (fn(status_code: i32, status_text: String) -> Self)(
|
|
89
|
+
Self(status_code: status_code, status_text: status_text, headers: ArrayList(HttpHeader).new(), body: ``)
|
|
90
|
+
),
|
|
91
|
+
|
|
92
|
+
get_header : (fn(self: Self, name: String) -> Option(String))({
|
|
93
|
+
lower_name := name.to_lowercase();
|
|
94
|
+
i := usize(0);
|
|
95
|
+
while (i < self.headers.len()), (i = (i + usize(1))), {
|
|
96
|
+
h := self.headers.get(i).unwrap();
|
|
97
|
+
cond((h.name.to_lowercase() == lower_name) => { return .Some(h.value); }, true => ());
|
|
98
|
+
};
|
|
99
|
+
.None
|
|
100
|
+
}),
|
|
101
|
+
|
|
102
|
+
is_ok : (fn(self: Self) -> bool)(
|
|
103
|
+
((self.status_code >= i32(200)) && (self.status_code < i32(300)))
|
|
104
|
+
),
|
|
105
|
+
|
|
106
|
+
is_redirect : (fn(self: Self) -> bool)(
|
|
107
|
+
((self.status_code >= i32(300)) && (self.status_code < i32(400)))
|
|
108
|
+
),
|
|
109
|
+
|
|
110
|
+
is_error : (fn(self: Self) -> bool)(
|
|
111
|
+
(self.status_code >= i32(400))
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
parse_response :: (fn(raw: String) -> Result(HttpResponse, String))({
|
|
116
|
+
lines := raw.split(`\r\n`);
|
|
117
|
+
cond((lines.len() == usize(0)) => { return .Err(`Empty response`); }, true => ());
|
|
118
|
+
|
|
119
|
+
status_line := lines.get(usize(0)).unwrap();
|
|
120
|
+
cond((!(status_line.starts_with(`HTTP/`))) => { return .Err(`Invalid status line`); }, true => ());
|
|
121
|
+
|
|
122
|
+
sp1_opt := status_line.index_of(` `);
|
|
123
|
+
cond(sp1_opt.is_none() => { return .Err(`Invalid status line format`); }, true => ());
|
|
124
|
+
sp1 := sp1_opt.unwrap();
|
|
125
|
+
rest := status_line.substring((sp1 + usize(1)), status_line.len());
|
|
126
|
+
|
|
127
|
+
sp2_opt := rest.index_of(` `);
|
|
128
|
+
cond(sp2_opt.is_none() => { return .Err(`Invalid status line format`); }, true => ());
|
|
129
|
+
sp2 := sp2_opt.unwrap();
|
|
130
|
+
code_str := rest.substring(usize(0), sp2);
|
|
131
|
+
status_text := rest.substring((sp2 + usize(1)), rest.len());
|
|
132
|
+
|
|
133
|
+
code_opt := code_str.parse_i32();
|
|
134
|
+
cond(code_opt.is_none() => { return .Err(`Invalid status code`); }, true => ());
|
|
135
|
+
code := code_opt.unwrap();
|
|
136
|
+
|
|
137
|
+
resp := HttpResponse.new(code, status_text);
|
|
138
|
+
|
|
139
|
+
j := usize(1);
|
|
140
|
+
body_start := lines.len();
|
|
141
|
+
while (j < lines.len()), (j = (j + usize(1))), {
|
|
142
|
+
hline := lines.get(j).unwrap();
|
|
143
|
+
cond(
|
|
144
|
+
hline.is_empty() => {
|
|
145
|
+
body_start = (j + usize(1));
|
|
146
|
+
break;
|
|
147
|
+
},
|
|
148
|
+
true => {
|
|
149
|
+
colon_opt := hline.index_of(`:`);
|
|
150
|
+
cond(
|
|
151
|
+
colon_opt.is_some() => {
|
|
152
|
+
colon := colon_opt.unwrap();
|
|
153
|
+
hname := hline.substring(usize(0), colon).trim();
|
|
154
|
+
hval := hline.substring((colon + usize(1)), hline.len()).trim();
|
|
155
|
+
resp.headers.push(HttpHeader.new(hname, hval));
|
|
156
|
+
},
|
|
157
|
+
true => ()
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
cond(
|
|
164
|
+
(body_start < lines.len()) => {
|
|
165
|
+
resp.body = lines.get(body_start).unwrap();
|
|
166
|
+
m := (body_start + usize(1));
|
|
167
|
+
while (m < lines.len()), (m = (m + usize(1))), {
|
|
168
|
+
resp.body = `${resp.body}\r\n${lines.get(m).unwrap()}`;
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
true => ()
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
.Ok(resp)
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
http_status_text :: (fn(code: i32) -> String)(
|
|
178
|
+
cond(
|
|
179
|
+
(code == i32(200)) => `OK`,
|
|
180
|
+
(code == i32(201)) => `Created`,
|
|
181
|
+
(code == i32(204)) => `No Content`,
|
|
182
|
+
(code == i32(301)) => `Moved Permanently`,
|
|
183
|
+
(code == i32(302)) => `Found`,
|
|
184
|
+
(code == i32(304)) => `Not Modified`,
|
|
185
|
+
(code == i32(400)) => `Bad Request`,
|
|
186
|
+
(code == i32(404)) => `Not Found`,
|
|
187
|
+
(code == i32(405)) => `Method Not Allowed`,
|
|
188
|
+
(code == i32(500)) => `Internal Server Error`,
|
|
189
|
+
(code == i32(502)) => `Bad Gateway`,
|
|
190
|
+
(code == i32(503)) => `Service Unavailable`,
|
|
191
|
+
true => `Unknown`
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
export HttpMethod, HttpHeader, HttpRequest, HttpResponse;
|
|
196
|
+
export parse_response, http_status_text;
|
package/std/io/reader.yo
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// std/io/reader.yo - Reader trait for byte-level input
|
|
2
|
+
//
|
|
3
|
+
// Provides a common interface for reading bytes from various sources
|
|
4
|
+
// (files, network streams, buffers, etc.)
|
|
5
|
+
|
|
6
|
+
{ Exception } :: import "../error";
|
|
7
|
+
|
|
8
|
+
// Reader trait — synchronous byte reading interface.
|
|
9
|
+
// Types implementing Reader provide `read` for reading into a buffer.
|
|
10
|
+
Reader :: trait(
|
|
11
|
+
// Read up to `size` bytes into buffer `buf`.
|
|
12
|
+
// Returns the number of bytes actually read (0 at end-of-stream).
|
|
13
|
+
read :
|
|
14
|
+
fn(self: *(Self), buf: *(u8), size: usize, using(exn : Exception)) -> usize
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export Reader;
|
package/std/io/writer.yo
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// std/io/writer.yo - Writer trait for byte-level output
|
|
2
|
+
//
|
|
3
|
+
// Provides a common interface for writing bytes to various destinations
|
|
4
|
+
// (files, network streams, buffers, etc.)
|
|
5
|
+
|
|
6
|
+
{ Exception } :: import "../error";
|
|
7
|
+
|
|
8
|
+
// Writer trait — synchronous byte writing interface.
|
|
9
|
+
Writer :: trait(
|
|
10
|
+
// Write bytes from buffer. Returns number of bytes written.
|
|
11
|
+
write :
|
|
12
|
+
fn(self: *(Self), buf: *(u8), size: usize, using(exn : Exception)) -> usize,
|
|
13
|
+
|
|
14
|
+
// Flush any buffered data to the underlying destination.
|
|
15
|
+
flush :
|
|
16
|
+
fn(self: *(Self), using(exn : Exception)) -> unit
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export Writer;
|
package/std/net/tcp.yo
CHANGED
|
@@ -268,7 +268,7 @@ impl(TcpStream,
|
|
|
268
268
|
}),
|
|
269
269
|
|
|
270
270
|
// Read all available data into a byte list.
|
|
271
|
-
|
|
271
|
+
read_bytes : (fn(self: Self, using(io : IO)) -> Impl(Future(ArrayList(u8), IO, Exception)))({
|
|
272
272
|
fd := self._fd;
|
|
273
273
|
io.async((using(io : IO, exn : Exception)) => {
|
|
274
274
|
buf_size := usize(4096);
|
package/std/prelude.yo
CHANGED
|
@@ -3410,6 +3410,40 @@ impl(forall(T : Type), Option(T),
|
|
|
3410
3410
|
)
|
|
3411
3411
|
);
|
|
3412
3412
|
|
|
3413
|
+
// Option(T) implements Comptime when T implements Comptime
|
|
3414
|
+
impl(forall(T : Type), where(T <: Comptime), Option(T), Comptime());
|
|
3415
|
+
|
|
3416
|
+
// Comptime methods for Option(T)
|
|
3417
|
+
impl(forall(T : Type), where(T <: Comptime), Option(T),
|
|
3418
|
+
comptime_unwrap : (fn(comptime(self) : Self) -> comptime(T))(
|
|
3419
|
+
match(self,
|
|
3420
|
+
.Some(value) => value,
|
|
3421
|
+
.None => panic("Called comptime_unwrap on a None value")
|
|
3422
|
+
)
|
|
3423
|
+
),
|
|
3424
|
+
|
|
3425
|
+
comptime_unwrap_or : (fn(comptime(self) : Self, comptime(or_value) : T) -> comptime(T))(
|
|
3426
|
+
match(self,
|
|
3427
|
+
.Some(value) => value,
|
|
3428
|
+
.None => or_value
|
|
3429
|
+
)
|
|
3430
|
+
),
|
|
3431
|
+
|
|
3432
|
+
comptime_is_some : (fn(comptime(self) : Self) -> comptime(bool))(
|
|
3433
|
+
match(self,
|
|
3434
|
+
.Some(_) => true,
|
|
3435
|
+
.None => false
|
|
3436
|
+
)
|
|
3437
|
+
),
|
|
3438
|
+
|
|
3439
|
+
comptime_is_none : (fn(comptime(self) : Self) -> comptime(bool))(
|
|
3440
|
+
match(self,
|
|
3441
|
+
.None => true,
|
|
3442
|
+
.Some(_) => false
|
|
3443
|
+
)
|
|
3444
|
+
)
|
|
3445
|
+
);
|
|
3446
|
+
|
|
3413
3447
|
// Alias of the Option type
|
|
3414
3448
|
? :: Option;
|
|
3415
3449
|
// Nullable pointer
|
|
@@ -3459,6 +3493,41 @@ impl(forall(OkType : Type, ErrorType : Type), Result(OkType, ErrorType),
|
|
|
3459
3493
|
)
|
|
3460
3494
|
)
|
|
3461
3495
|
);
|
|
3496
|
+
|
|
3497
|
+
// Result(OkType, ErrorType) implements Comptime when both types implement Comptime
|
|
3498
|
+
impl(forall(OkType : Type, ErrorType : Type), where(OkType <: Comptime, ErrorType <: Comptime), Result(OkType, ErrorType), Comptime());
|
|
3499
|
+
|
|
3500
|
+
// Comptime methods for Result(OkType, ErrorType)
|
|
3501
|
+
impl(forall(OkType : Type, ErrorType : Type), where(OkType <: Comptime, ErrorType <: Comptime), Result(OkType, ErrorType),
|
|
3502
|
+
comptime_unwrap : (fn(comptime(self) : Self) -> comptime(OkType))(
|
|
3503
|
+
match(self,
|
|
3504
|
+
.Ok(value) => value,
|
|
3505
|
+
.Err(_) => panic("Called comptime_unwrap on an Err value")
|
|
3506
|
+
)
|
|
3507
|
+
),
|
|
3508
|
+
|
|
3509
|
+
comptime_unwrap_err : (fn(comptime(self) : Self) -> comptime(ErrorType))(
|
|
3510
|
+
match(self,
|
|
3511
|
+
.Err(error) => error,
|
|
3512
|
+
.Ok(_) => panic("Called comptime_unwrap_err on an Ok value")
|
|
3513
|
+
)
|
|
3514
|
+
),
|
|
3515
|
+
|
|
3516
|
+
comptime_is_ok : (fn(comptime(self) : Self) -> comptime(bool))(
|
|
3517
|
+
match(self,
|
|
3518
|
+
.Ok(_) => true,
|
|
3519
|
+
.Err(_) => false
|
|
3520
|
+
)
|
|
3521
|
+
),
|
|
3522
|
+
|
|
3523
|
+
comptime_is_err : (fn(comptime(self) : Self) -> comptime(bool))(
|
|
3524
|
+
match(self,
|
|
3525
|
+
.Err(_) => true,
|
|
3526
|
+
.Ok(_) => false
|
|
3527
|
+
)
|
|
3528
|
+
)
|
|
3529
|
+
);
|
|
3530
|
+
|
|
3462
3531
|
export
|
|
3463
3532
|
Result
|
|
3464
3533
|
;
|