std: 프로그램이 바깥 세상과 이야기한다 -- 파일과 명령줄
런타임에 open/read/close 와 명령줄을 넣었다. std.io 가 그 위에 파일 열기, 읽기, 쓰기, 그리고 명령줄을 조각으로 나누는 것을 얹는다. 인용부호 처리는 런타임이 알 일이 아니라 라이브러리가 할 일이다. 길에서 고친 것들: - *T 가 타입 시스템에 실체가 없어서 덩어리로 취급됐다. 이제 진짜 종류다 -- 주소일 뿐이고 추적할 대여도 실행할 drop 도 없는 Copy 타입. 그 결과 &u8 이 *u8 에 자동으로 맞지 않게 됐는데, 그게 맞다: R9 는 그 변환을 unsafe 안의 @ptr_cast 로만 허용한다. - raw 포인터에 정수를 더하면 더 뒤의 주소다. 소유자나 대여에는 허용하지 않는다 -- 자기 자리가 있는 것에서 걸어나가는 것이 *T 의 용도다. - @volatile_load / @volatile_store / @ptr_cast 를 내린다. - undefined 가 선언된 타입을 따른다. 없으면 손으로 타이핑할 수 있는 것보다 큰 버퍼를 선언할 방법이 아예 없었다. R8 이 정확히 동작하는 것도 확인했다: 참조성 파라미터가 둘인 함수는 슬라이스를 반환할 수 없다. 어디서 파생됐는지 시그니처가 말하지 않기 때문이다. exec.py 24/24.
This commit is contained in:
@@ -131,6 +131,10 @@ fe_trap endp
|
||||
extern _GetProcessHeap@0 : near
|
||||
extern _HeapAlloc@12 : near
|
||||
extern _HeapFree@12 : near
|
||||
extern _CreateFileA@28 : near
|
||||
extern _ReadFile@20 : near
|
||||
extern _CloseHandle@4 : near
|
||||
extern _GetCommandLineA@0 : near
|
||||
|
||||
; fe_rt_write(handle, ptr, len) -> bytes written
|
||||
public fe_rt_write
|
||||
@@ -298,6 +302,78 @@ hex_digit:
|
||||
ret
|
||||
fe_rt_write_hex endp
|
||||
|
||||
; fe_rt_open(path, write) -> handle, or -1
|
||||
; `path` is a NUL-terminated byte string. Reading opens what is there; writing
|
||||
; creates or truncates.
|
||||
public fe_rt_open
|
||||
fe_rt_open proc near
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
push 0 ; hTemplateFile
|
||||
push 128 ; FILE_ATTRIBUTE_NORMAL
|
||||
cmp dword ptr [ebp+12], 0
|
||||
jne open_write
|
||||
push 3 ; OPEN_EXISTING
|
||||
push 0
|
||||
push 1 ; FILE_SHARE_READ
|
||||
push 80000000h ; GENERIC_READ
|
||||
jmp open_call
|
||||
open_write:
|
||||
push 2 ; CREATE_ALWAYS
|
||||
push 0
|
||||
push 0
|
||||
push 40000000h ; GENERIC_WRITE
|
||||
open_call:
|
||||
push dword ptr [ebp+8]
|
||||
call _CreateFileA@28
|
||||
mov esp, ebp
|
||||
pop ebp
|
||||
ret
|
||||
fe_rt_open endp
|
||||
|
||||
; fe_rt_read(handle, buf, len) -> bytes read, or -1
|
||||
public fe_rt_read
|
||||
fe_rt_read proc near
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
push 0
|
||||
push offset written
|
||||
push dword ptr [ebp+16]
|
||||
push dword ptr [ebp+12]
|
||||
push dword ptr [ebp+8]
|
||||
call _ReadFile@20
|
||||
test eax, eax
|
||||
jne read_ok
|
||||
mov eax, -1
|
||||
jmp read_done
|
||||
read_ok:
|
||||
mov eax, [written]
|
||||
read_done:
|
||||
mov esp, ebp
|
||||
pop ebp
|
||||
ret
|
||||
fe_rt_read endp
|
||||
|
||||
; fe_rt_close(handle)
|
||||
public fe_rt_close
|
||||
fe_rt_close proc near
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
push dword ptr [ebp+8]
|
||||
call _CloseHandle@4
|
||||
mov esp, ebp
|
||||
pop ebp
|
||||
ret
|
||||
fe_rt_close endp
|
||||
|
||||
; fe_rt_cmdline() -> pointer to the whole command line, NUL terminated.
|
||||
; Splitting it is the standard library's job, not the runtime's.
|
||||
public fe_rt_cmdline
|
||||
fe_rt_cmdline proc near
|
||||
call _GetCommandLineA@0
|
||||
ret
|
||||
fe_rt_cmdline endp
|
||||
|
||||
; fe_rt_exit(code) -- never returns
|
||||
public fe_rt_exit
|
||||
fe_rt_exit proc near
|
||||
|
||||
@@ -444,6 +444,14 @@ FeType *check_expr(FeCheckerState *s, FeNode *n)
|
||||
n->sem_type=fe_type_intern(&s->c->types,"bool");
|
||||
return n->sem_type;
|
||||
}
|
||||
/* A raw pointer plus a number is an address further along. Only raw
|
||||
pointers: an owner or a borrow has a place it belongs to, and
|
||||
walking away from it is what `*T` is for. */
|
||||
if (known(a) && a->kind==FE_TYPE_RAW && known(b) &&
|
||||
fe_type_is_integer(b) && op[0] && (op[0]=='+' || op[0]=='-')) {
|
||||
n->sem_type=a;
|
||||
return n->sem_type;
|
||||
}
|
||||
if ((known(a) && !fe_type_is_integer(a)) ||
|
||||
(known(b) && !fe_type_is_integer(b)) ||
|
||||
(known(a) && known(b) && !fe_type_equal(a,b) &&
|
||||
|
||||
@@ -495,6 +495,19 @@ FeType *check_expr_core(FeCheckerState *s, FeNode *n)
|
||||
if(!target || !known(target)) err(c,n->loc,"size/align requires a known type");
|
||||
n->sem_type=fe_type_intern(&c->types,"usize"); return n->sem_type;
|
||||
}
|
||||
if (!n->a && n->text && strcmp(n->text,"@ptr_cast")==0) {
|
||||
/* `@ptr_cast(T, p)`: the first argument names the type the result
|
||||
points at, the second is the address. R9 keeps it in `unsafe`. */
|
||||
FeNode *type_arg=n->children;
|
||||
FeNode *value=type_arg ? type_arg->next : 0;
|
||||
FeType *target=type_arg && type_arg->kind==FE_N_IDENT ?
|
||||
fe_type_intern(&c->types,type_arg->text) : unknown(c);
|
||||
if (!type_arg || !value || value->next)
|
||||
err(c,n->loc,"@ptr_cast requires a type and a pointer");
|
||||
if (value) check_expr(s,value);
|
||||
n->sem_type=fe_type_raw(&c->types,target);
|
||||
return n->sem_type;
|
||||
}
|
||||
if (n->a && n->a->kind == FE_N_MEMBER) {
|
||||
FeNode *method;
|
||||
FeNode *self_param;
|
||||
|
||||
@@ -535,6 +535,14 @@ FeType *m7_check_expected(FeCheckerState *s, FeNode *value,
|
||||
FeType *actual;
|
||||
FeM7ContextKind context;
|
||||
if (!value) return unknown(s->c);
|
||||
/* `undefined` is not a value, it is the absence of one: it takes whatever
|
||||
type was asked for, and says the storage starts out unset. Without this
|
||||
there is no way to declare a buffer larger than you care to type out. */
|
||||
if (value->kind==FE_N_LITERAL && value->text &&
|
||||
!strcmp(value->text,"undefined") && expected) {
|
||||
value->sem_type=expected;
|
||||
return expected;
|
||||
}
|
||||
if (fe_m7_is_null(value)) {
|
||||
if (!fe_m7_can_contextual_null(expected)) {
|
||||
err(s->c,value->loc,"null requires a contextual optional type");
|
||||
|
||||
+32
-1
@@ -25,7 +25,8 @@ FeIrType ir_type_of(const FeType *t)
|
||||
if (t->bits <= 8U) return FE_IR_I8;
|
||||
if (t->bits <= 16U) return FE_IR_I16;
|
||||
return FE_IR_I32;
|
||||
case FE_TYPE_REF: return FE_IR_PTR;
|
||||
case FE_TYPE_REF:
|
||||
case FE_TYPE_RAW: return FE_IR_PTR;
|
||||
case FE_TYPE_OWNED:
|
||||
/* An owned slice carries a length beside the pointer. */
|
||||
return t->elem && t->elem->kind == FE_TYPE_SLICE ? FE_IR_MEM : FE_IR_PTR;
|
||||
@@ -391,6 +392,36 @@ int lower_builtin(Lower *L, FeNode *n, Slot *out)
|
||||
*out = slot_value(fe_ir_const(L->m, L->b, FE_IR_I32, v), FE_IR_I32);
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp(name, "@volatile_load")) {
|
||||
/* Reading through a raw pointer. Nothing here reorders loads yet, so
|
||||
volatile and ordinary read the same; the keyword is what marks the
|
||||
access as deliberate, and the checker already required `unsafe`. */
|
||||
FeNode *arg = n->children;
|
||||
unsigned p = as_value(L, lower_expr(L, arg), arg);
|
||||
FeIrType t = ir_type(n->sem_type);
|
||||
if (t == FE_IR_VOID || t == FE_IR_MEM) t = FE_IR_I8;
|
||||
*out = slot_place(fe_ir_at_temp(p, 0), t, ir_size(n->sem_type));
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp(name, "@volatile_store")) {
|
||||
FeNode *arg = n->children;
|
||||
FeNode *value = arg ? arg->next : 0;
|
||||
unsigned p = as_value(L, lower_expr(L, arg), arg);
|
||||
Slot v = lower_expr(L, value);
|
||||
FeIrType t = value && value->sem_type ? ir_type(value->sem_type)
|
||||
: FE_IR_I8;
|
||||
fe_ir_store(L->m, L->b, fe_ir_at_temp(p, 0), as_value(L, v, value), t);
|
||||
*out = slot_void();
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp(name, "@ptr_cast")) {
|
||||
/* A pointer is a pointer; the type it is said to point at is the
|
||||
checker's business and leaves no trace here. */
|
||||
FeNode *arg = n->children;
|
||||
FeNode *value = arg ? arg->next : 0;
|
||||
*out = slot_value(as_value(L, lower_expr(L, value), value), FE_IR_PTR);
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp(name, "@line")) {
|
||||
*out = slot_value(fe_ir_const(L->m, L->b, FE_IR_I32,
|
||||
(long)n->loc.line), FE_IR_I32);
|
||||
|
||||
@@ -333,6 +333,10 @@ void lower_stmt(Lower *L, FeNode *n)
|
||||
case FE_N_VAR:
|
||||
case FE_N_CONST: {
|
||||
unsigned local = declare_var(L, n->cname, n->sem_type, n->text);
|
||||
/* `undefined` says the storage starts out unset, so there is nothing
|
||||
to write into it. */
|
||||
if (n->b && n->b->kind == FE_N_LITERAL && n->b->text &&
|
||||
!strcmp(n->b->text, "undefined")) return;
|
||||
if (n->b) {
|
||||
Slot v = lower_expr(L, n->b);
|
||||
unsigned flag;
|
||||
|
||||
+17
-2
@@ -234,6 +234,21 @@ FeType *fe_type_ref(FeTypeCtx *ctx, FeType *elem, int mutable)
|
||||
return t;
|
||||
}
|
||||
|
||||
FeType *fe_type_raw(FeTypeCtx *ctx, FeType *elem)
|
||||
{
|
||||
char key[320];
|
||||
FeType *t;
|
||||
sprintf(key, "*%s", elem ? elem->name : "?");
|
||||
t = fe_type_intern(ctx, key);
|
||||
if (t->kind == FE_TYPE_UNKNOWN) {
|
||||
t->kind = FE_TYPE_RAW;
|
||||
t->elem = elem;
|
||||
t->size = FE_PTR_SIZE;
|
||||
t->align = FE_PTR_ALIGN;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
FeType *fe_type_owned(FeTypeCtx *ctx, FeType *elem)
|
||||
{
|
||||
char key[320];
|
||||
@@ -485,7 +500,7 @@ static void layout_type(FeTypeCtx *ctx, FeType *t)
|
||||
if (t->size > 4UL) t->size = 4UL;
|
||||
t->cycle_state = 2; return;
|
||||
}
|
||||
if (t->kind == FE_TYPE_REF) {
|
||||
if (t->kind == FE_TYPE_REF || t->kind == FE_TYPE_RAW) {
|
||||
t->size = FE_PTR_SIZE;
|
||||
t->align = FE_PTR_ALIGN;
|
||||
t->cycle_state = 2; return;
|
||||
@@ -613,7 +628,7 @@ FeType *fe_type_from_ast(FeTypeCtx *ctx, const FeNode *node)
|
||||
return fe_type_error_union(ctx,fe_type_from_ast(ctx,node->a));
|
||||
}
|
||||
if (node->text && strcmp(node->text, "*") == 0)
|
||||
return fe_type_intern(ctx, "<unknown>");
|
||||
return fe_type_raw(ctx, fe_type_from_ast(ctx, node->a));
|
||||
if (node->text && strcmp(node->text, "fn") == 0)
|
||||
return fe_type_intern(ctx, "<unknown>");
|
||||
/* A plain named type may be a generic declaration -- with arguments it is
|
||||
|
||||
+6
-1
@@ -7,7 +7,11 @@ typedef enum FeTypeKind {
|
||||
FE_TYPE_ERROR, FE_TYPE_ERROR_UNION, FE_TYPE_OPTIONAL,
|
||||
FE_TYPE_VOID, FE_TYPE_BOOL, FE_TYPE_CHAR, FE_TYPE_INT,
|
||||
FE_TYPE_STRUCT, FE_TYPE_ENUM, FE_TYPE_ARRAY, FE_TYPE_SLICE, FE_TYPE_STR,
|
||||
FE_TYPE_REF, FE_TYPE_OWNED, FE_TYPE_UNKNOWN
|
||||
FE_TYPE_REF, FE_TYPE_OWNED,
|
||||
/* `*T`. A machine address and nothing else: no borrow to track, no drop
|
||||
to run, Copy. Everything it is good for is behind `unsafe`. */
|
||||
FE_TYPE_RAW,
|
||||
FE_TYPE_UNKNOWN
|
||||
} FeTypeKind;
|
||||
|
||||
/* One target, one pointer width (SPEC 2). usize and isize are that width and
|
||||
@@ -124,6 +128,7 @@ FeType *fe_type_slice(FeTypeCtx *ctx, FeType *elem);
|
||||
FeType *fe_type_mut_slice(FeTypeCtx *ctx, FeType *elem);
|
||||
FeType *fe_type_ref(FeTypeCtx *ctx, FeType *elem, int mutable);
|
||||
FeType *fe_type_owned(FeTypeCtx *ctx, FeType *elem);
|
||||
FeType *fe_type_raw(FeTypeCtx *ctx, FeType *elem);
|
||||
FeType *fe_type_error_union(FeTypeCtx *ctx, FeType *value);
|
||||
void fe_type_require_replace(FeTypeCtx *ctx, FeType *type);
|
||||
FeType *fe_type_declare_struct(FeTypeCtx *ctx, const FeNode *node, int packed);
|
||||
|
||||
+96
-1
@@ -10,7 +10,8 @@ pub fn write(w: Writer, bytes: []u8) -> usize {
|
||||
if w == Writer.Null { return bytes.n; }
|
||||
var handle: i32 = 1;
|
||||
if w == Writer.Stderr { handle = 2; }
|
||||
let done: i32 = sys.raw_write(handle, &bytes[0], bytes.n);
|
||||
var done: i32 = 0;
|
||||
unsafe { done = sys.raw_write(handle, @ptr_cast(u8, &bytes[0]), bytes.n); }
|
||||
if done < 0 { return 0; }
|
||||
return done as usize;
|
||||
}
|
||||
@@ -23,3 +24,97 @@ pub fn println(bytes: []u8) -> usize {
|
||||
let n: usize = write(Writer.Stdout, bytes);
|
||||
return n + write(Writer.Stdout, "\n");
|
||||
}
|
||||
|
||||
// Files. A handle is what the operating system gave back; -1 means it did not
|
||||
// give one. The path has to be NUL terminated because that is what the system
|
||||
// call wants, and `to_cstr` is how a Ferro string becomes one.
|
||||
|
||||
pub fn open_read(path: []mut u8) -> !i32 {
|
||||
var handle: i32 = 0;
|
||||
unsafe { handle = sys.raw_open(@ptr_cast(u8, &path[0]), 0); }
|
||||
if handle == 0 - 1 { return error.NoSuchFile; }
|
||||
return handle;
|
||||
}
|
||||
|
||||
pub fn open_write(path: []mut u8) -> !i32 {
|
||||
var handle: i32 = 0;
|
||||
unsafe { handle = sys.raw_open(@ptr_cast(u8, &path[0]), 1); }
|
||||
if handle == 0 - 1 { return error.CannotWrite; }
|
||||
return handle;
|
||||
}
|
||||
|
||||
pub fn read(handle: i32, into: []mut u8) -> !usize {
|
||||
var got: i32 = 0;
|
||||
unsafe { got = sys.raw_read(handle, @ptr_cast(u8, &into[0]), into.n); }
|
||||
if got < 0 { return error.ReadFailed; }
|
||||
return got as usize;
|
||||
}
|
||||
|
||||
pub fn close(handle: i32) -> void {
|
||||
sys.raw_close(handle);
|
||||
}
|
||||
|
||||
/// Put `text` into `buf` with a NUL after it and say how many bytes that took,
|
||||
/// the NUL included. A system call cannot be told a length, so it needs this.
|
||||
///
|
||||
/// The length comes back rather than a slice of `buf`: with two reference-like
|
||||
/// parameters the signature cannot say which one a returned slice came from,
|
||||
/// and R8 will not guess.
|
||||
pub fn to_cstr(buf: []mut u8, text: []u8) -> usize {
|
||||
var i: usize = 0;
|
||||
while i < text.n {
|
||||
if i + 1 >= buf.n { break; }
|
||||
buf[i] = text[i];
|
||||
i = i + 1;
|
||||
}
|
||||
if i < buf.n { buf[i] = 0; }
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
pub fn write_file(handle: i32, bytes: []u8) -> !usize {
|
||||
var done: i32 = 0;
|
||||
unsafe { done = sys.raw_write(handle, @ptr_cast(u8, &bytes[0]), bytes.n); }
|
||||
if done < 0 { return error.WriteFailed; }
|
||||
return done as usize;
|
||||
}
|
||||
|
||||
/// Copy the command line into `buf` and say how long it is. It arrives as one
|
||||
/// string with the program's own name first; `arg` picks a piece out of it.
|
||||
pub fn cmdline(buf: []mut u8) -> usize {
|
||||
let raw: *u8 = sys.raw_cmdline();
|
||||
var i: usize = 0;
|
||||
unsafe {
|
||||
while i + 1 < buf.n {
|
||||
let c: u8 = @volatile_load(raw + i);
|
||||
if c == 0 { break; }
|
||||
buf[i] = c;
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/// The `n`th whitespace-separated piece of `line`, or an empty slice when
|
||||
/// there is no such piece. Quoting is not handled; nothing here needs it yet.
|
||||
pub fn arg(line: []u8, n: usize) -> []u8 {
|
||||
var at: usize = 0;
|
||||
var seen: usize = 0;
|
||||
while at < line.n {
|
||||
while at < line.n {
|
||||
if line[at] != 32 { break; }
|
||||
at = at + 1;
|
||||
}
|
||||
var stop: usize = at;
|
||||
while stop < line.n {
|
||||
if line[stop] == 32 { break; }
|
||||
stop = stop + 1;
|
||||
}
|
||||
if stop > at {
|
||||
if seen == n { return line[at..stop]; }
|
||||
seen = seen + 1;
|
||||
}
|
||||
at = stop;
|
||||
}
|
||||
return line[0..0];
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ extern "c" fn fe_rt_free(p: *u8);
|
||||
extern "c" fn fe_rt_exit(code: i32);
|
||||
extern "c" fn fe_rt_allocs() -> i32;
|
||||
extern "c" fn fe_rt_frees() -> i32;
|
||||
extern "c" fn fe_rt_open(path: *u8, write: i32) -> i32;
|
||||
extern "c" fn fe_rt_read(handle: i32, buf: *u8, len: usize) -> i32;
|
||||
extern "c" fn fe_rt_close(handle: i32);
|
||||
extern "c" fn fe_rt_cmdline() -> *u8;
|
||||
|
||||
pub fn exit(code: i32) -> void {
|
||||
unsafe { fe_rt_exit(code); }
|
||||
@@ -29,3 +33,21 @@ pub fn raw_free(p: *u8) -> void {
|
||||
// back. A test can insist the two agree; nothing else should care.
|
||||
pub fn allocs() -> i32 { unsafe { return fe_rt_allocs(); } }
|
||||
pub fn frees() -> i32 { unsafe { return fe_rt_frees(); } }
|
||||
|
||||
pub fn raw_open(path: *u8, write: i32) -> i32 {
|
||||
unsafe { return fe_rt_open(path, write); }
|
||||
}
|
||||
|
||||
pub fn raw_read(handle: i32, buf: *u8, len: usize) -> i32 {
|
||||
unsafe { return fe_rt_read(handle, buf, len); }
|
||||
}
|
||||
|
||||
pub fn raw_close(handle: i32) -> void {
|
||||
unsafe { fe_rt_close(handle); }
|
||||
}
|
||||
|
||||
/// The whole command line as one NUL-terminated string. Splitting it into
|
||||
/// arguments is `std.io`'s job: the runtime should not know about quoting.
|
||||
pub fn raw_cmdline() -> *u8 {
|
||||
unsafe { return fe_rt_cmdline(); }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// EXIT:0
|
||||
// OUTPUT:args ok
|
||||
unit cmdargs;
|
||||
import std.io;
|
||||
import std.str;
|
||||
|
||||
// The command line reaches the program. A compiler is told which file to read
|
||||
// this way and no other.
|
||||
|
||||
fn main() -> i32 {
|
||||
var line: [512]u8 = undefined;
|
||||
let n: usize = io.cmdline(line[..]);
|
||||
let program: []u8 = io.arg(line[0..n], 0);
|
||||
if program.n == 0 { @print("no program name\n"); return 1; }
|
||||
if str.find(program, "cmdargs") == program.n {
|
||||
@print("unexpected program name: {}\n", program);
|
||||
return 2;
|
||||
}
|
||||
@print("args ok\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// EXIT:0
|
||||
// OUTPUT:read 64 bytes
|
||||
// OUTPUT:first line: // EXIT:0
|
||||
unit readfile;
|
||||
import std.io;
|
||||
|
||||
// Reads its own source and reports the first line. A program that can open a
|
||||
// file is a program that can be a compiler.
|
||||
|
||||
fn main() -> i32 {
|
||||
var path: [64]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
let n: usize = io.to_cstr(path[..], "fec/tests/exec/readfile.fe");
|
||||
let handle: i32 = io.open_read(path[0..n]) catch |e| {
|
||||
@print("cannot open\n");
|
||||
return 1;
|
||||
};
|
||||
var buf: [64]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
|
||||
let got: usize = io.read(handle, buf[..]) catch |e| {
|
||||
io.close(handle);
|
||||
return 2;
|
||||
};
|
||||
io.close(handle);
|
||||
@print("read {} bytes\n", n);
|
||||
var stop: usize = 0;
|
||||
while stop < got {
|
||||
if buf[stop] == 10 { break; }
|
||||
stop = stop + 1;
|
||||
}
|
||||
@print("first line: {}\n", buf[0..stop]);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user