diff --git a/SPEC.md b/SPEC.md index b7058c0..6c8e6c8 100644 --- a/SPEC.md +++ b/SPEC.md @@ -110,6 +110,7 @@ and or not orelse - **배열은 포인터로 붕괴하지 않는다.** 함수에 넘기려면 `arr[..]`로 슬라이스를 만들거나 `&arr` / `^[N]T`를 쓴다. - 슬라이싱: `arr[..]`, `arr[a..b]`(반개구간, 경계 검사), `arr[a..]`, `slice[a..b]`. `let` 배열·공유 슬라이스에서는 `[]T`, `var` 배열·배타 슬라이스에서는 `[]mut T`가 생긴다. - `[]mut T`는 `[]T`로, `&mut T`는 `&T`로 **호출 인자 위치에서만** 암묵 재대여할 수 있다. 이것은 호출 동안의 read-only view이며 원래 배타 대여는 원래 마지막 사용까지 유지된다. 일반 `let`/대입에는 이 암묵 약화를 적용하지 않는다. 장기 shared borrow가 필요하면 root/place에서 명시적으로 새 `&` 또는 shared slice를 만들고 R6 검사를 받는다. +- **배타 대여를 호출에 넘기는 것은 이동이 아니라 그 호출 동안의 재대여다.** `&mut T`를 `&mut T` 파라미터에, `[]mut T`를 `[]mut T` 파라미터에 넘기면 호출이 끝날 때 돌려받는다. 호출이 도는 동안 호출자는 그 값에 손댈 수 없으므로 별칭이 생기지 않는다. 이것이 없으면 배타 파라미터를 다시 넘기는 일이 함수당 한 번만 가능해져서 `&mut`가 사실상 쓸 수 없게 된다. - `^[]T`는 "슬라이스를 가리키는 포인터"가 아니라 길이를 함께 소유하는 독립 타입이다. R4의 일반 `^T` 대상 제한의 예외이며 `?^[]T`도 허용한다. `*[]T`/`*[]mut T`는 계속 금지한다. `mem.alloc_slice(T, n)`가 반환하고 drop 시 버퍼를 해제한다. - `str`은 nominal 타입이 아니라 미리 선언된 `const str = []u8;` type alias다. UTF-8 검증을 보장하지 않으며 문자열 리터럴은 정적 읽기 전용 `[]u8`이다. 따라서 별도 변환 규칙이나 별도 C 표현은 없다. diff --git a/fec/src/check.c b/fec/src/check.c index 5da3711..6f238da 100644 --- a/fec/src/check.c +++ b/fec/src/check.c @@ -156,9 +156,27 @@ static int compatible(FeType *want, FeType *got, FeNode *value) value->text[0] != '\'' && value->text[0] != '"'; } +/* Does passing `arg` to a parameter of type `param` lend it rather than give + it away? An exclusive borrow handed to a call comes back when the call + returns, so it is not a move. */ +static int call_reborrows(const FeType *param, const FeType *arg) +{ + if (!param || !arg) return 0; + if (param->kind==FE_TYPE_REF && arg->kind==FE_TYPE_REF && + param->ref_mut && arg->ref_mut) return 1; + if (param->kind==FE_TYPE_SLICE && arg->kind==FE_TYPE_SLICE && + param->ref_mut && arg->ref_mut) return 1; + return 0; +} + static int explicit_castable(FeType *a, FeType *b) { if (!a || !b) return 0; + /* An enum without a payload is a number with names on it, so reading it + as one is a widening or narrowing and nothing more. The other direction + is not allowed: an arbitrary number is not a variant. */ + if (a->kind == FE_TYPE_ENUM && !a->fields && + (fe_type_is_integer(b) || b->kind == FE_TYPE_CHAR)) return 1; return (fe_type_is_integer(a) || a->kind == FE_TYPE_CHAR) && (fe_type_is_integer(b) || b->kind == FE_TYPE_CHAR); } @@ -2820,6 +2838,11 @@ static FeType *check_call_args(FeCheckerState *s, FeNode *n, FeSym *sym, } else if (b && a && b->kind==FE_TYPE_SLICE && !b->ref_mut && a->kind==FE_TYPE_SLICE && a->ref_mut) { /* Call-only []mut -> [] weakening is a temporary view. */ + } else if (call_reborrows(b, a)) { + /* Handing an exclusive borrow to a call lends it for the length of + that call and takes it back after: the caller cannot touch it + meanwhile, so nothing is aliased. Without this an exclusive + parameter could be passed onwards exactly once. */ } else mark_moved(s,arg,a); if (!compatible(b, a, arg) && !(b && a && b->kind==FE_TYPE_SLICE && a->kind==FE_TYPE_SLICE && @@ -2916,7 +2939,8 @@ static FeType *check_call(FeCheckerState *s, FeNode *n) if (root && root->borrow_root) root=root->borrow_root; if (root) fe_own_call_shared_view(c->diags,&root->own,arg->loc); } else if (!(b && a && b->kind==FE_TYPE_SLICE && - a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut)) + a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut) && + !call_reborrows(b, a)) mark_moved(s,arg,arg->sem_type ? arg->sem_type : a); if (!fe_type_equal(b,a) && !m7_actual_compatible(b,a,arg) && !(b && a && b->kind==FE_TYPE_SLICE && a->kind==FE_TYPE_SLICE && diff --git a/fec/tests/exec/calc.fe b/fec/tests/exec/calc.fe new file mode 100644 index 0000000..0f6b95a --- /dev/null +++ b/fec/tests/exec/calc.fe @@ -0,0 +1,120 @@ +// EXIT:0 +// OUTPUT:1+2*3 = 7 +// OUTPUT:(1+2)*3 = 9 +// OUTPUT:2*(3+4)-5 = 9 +// OUTPUT:10/3 = 3 +// OUTPUT:1+ = error +// OUTPUT:(1+2 = error +unit calc; +import std.io; +import std.fmt; + +// A recursive-descent evaluator over a byte slice. +// +// The position travels in a `&mut usize` rather than in a struct beside the +// text: a struct cannot hold a slice, because a slice is a borrowed view and +// R4 keeps borrows out of aggregate storage. Passing both is the honest way +// to say "this text, and how far we have read". + +fn done(src: []u8, at: usize) -> bool { return at >= src.n; } + +fn peek(src: []u8, at: usize) -> u8 { + if done(src, at) { return 0; } + return src[at]; +} + +fn skip_spaces(src: []u8, at: &mut usize) -> void { + while not done(src, at.^) { + if src[at.^] != 32 { break; } + at.^ = at.^ + 1; + } +} + +fn number(src: []u8, at: &mut usize) -> !i32 { + var value: i32 = 0; + var digits: usize = 0; + while not done(src, at.^) { + let c: u8 = src[at.^]; + if c < 48 or c > 57 { break; } + value = value * 10 + ((c - 48) as i32); + digits = digits + 1; + at.^ = at.^ + 1; + } + if digits == 0 { return error.BadNumber; } + return value; +} + +fn factor(src: []u8, at: &mut usize) -> !i32 { + skip_spaces(src, at); + if peek(src, at.^) == 40 { + at.^ = at.^ + 1; + let inner: i32 = try expr(src, at); + skip_spaces(src, at); + if peek(src, at.^) != 41 { return error.Unbalanced; } + at.^ = at.^ + 1; + return inner; + } + return number(src, at); +} + +fn term(src: []u8, at: &mut usize) -> !i32 { + var left: i32 = try factor(src, at); + while true { + skip_spaces(src, at); + let op: u8 = peek(src, at.^); + if op != 42 and op != 47 { break; } + at.^ = at.^ + 1; + let right: i32 = try factor(src, at); + if op == 42 { left = left * right; } + else { + if right == 0 { return error.DivideByZero; } + left = left / right; + } + } + return left; +} + +fn expr(src: []u8, at: &mut usize) -> !i32 { + var left: i32 = try term(src, at); + while true { + skip_spaces(src, at); + let op: u8 = peek(src, at.^); + if op != 43 and op != 45 { break; } + at.^ = at.^ + 1; + let right: i32 = try term(src, at); + if op == 43 { left = left + right; } + else { left = left - right; } + } + return left; +} + +fn evaluate(text: []u8) -> !i32 { + var at: usize = 0; + let value: i32 = try expr(text, &mut at); + skip_spaces(text, &mut at); + if not done(text, at) { return error.Trailing; } + return value; +} + +fn show(text: []u8) -> void { + var buf: [16]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + io.print(text); + io.print(" = "); + let value: i32 = evaluate(text) catch |e| { + io.print("error\n"); + return; + }; + let n: usize = fmt.fmt_i32(buf[..], value); + io.print(buf[0..n]); + io.print("\n"); +} + +fn main() -> i32 { + show("1+2*3"); + show("(1+2)*3"); + show("2*(3+4)-5"); + show("10/3"); + show("1+"); + show("(1+2"); + return 0; +}