lang: slice ranges take the same bound markers as for-header ranges
xs[1..=3] (end inclusive), xs[0<..<4] (both exclusive), xs[..=2] (prefix form with markers, implicit 0 start), xs[2<..] (open end, exclusive start), and xs[..] (whole collection) — lowered as lo+1 / hi+1 on the existing subslice op. Strings slice through the same path. An explicit end marker requires an end expression, matching the for-header rule. Regression: examples/0052-basic-slice-range-bounds.sx.
This commit is contained in:
32
examples/0052-basic-slice-range-bounds.sx
Normal file
32
examples/0052-basic-slice-range-bounds.sx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Slice range bound markers — same matrix as for-header ranges: each side
|
||||||
|
// of `..` takes `=` (inclusive) or `<` (exclusive), defaults 0-inclusive
|
||||||
|
// start / exclusive end. Prefix form takes markers too ([..=2], [<..3]);
|
||||||
|
// [..] is the whole slice; bounds are arbitrary expressions; strings slice
|
||||||
|
// through the same path.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
dump :: (s: []s64, tag: string) {
|
||||||
|
print("{}: ", tag);
|
||||||
|
for s (v) { print("{} ", v); }
|
||||||
|
print("(len {})\n", s.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
xs : [6]s64 = .[10, 11, 12, 13, 14, 15];
|
||||||
|
full : []s64 = xs[0..6];
|
||||||
|
|
||||||
|
dump(full[1..=3], "1..=3"); // 11 12 13
|
||||||
|
dump(full[0<..<4], "0<..<4"); // 11 12 13
|
||||||
|
dump(full[..=2], "..=2"); // 10 11 12
|
||||||
|
dump(full[<..3], "<..3"); // 11 12
|
||||||
|
dump(full[2<..], "2<.."); // 13 14 15
|
||||||
|
dump(full[..], ".."); // all six
|
||||||
|
x := 3;
|
||||||
|
dump(full[x-1..=x+1], "x-1..=x+1"); // 12 13 14
|
||||||
|
|
||||||
|
s := "abcdef";
|
||||||
|
print("str 1..=3: {}\n", s[1..=3]); // bcd
|
||||||
|
print("str 0<..<4: {}\n", s[0<..<4]); // bcd
|
||||||
|
0
|
||||||
|
}
|
||||||
1
examples/expected/0052-basic-slice-range-bounds.exit
Normal file
1
examples/expected/0052-basic-slice-range-bounds.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
examples/expected/0052-basic-slice-range-bounds.stderr
Normal file
1
examples/expected/0052-basic-slice-range-bounds.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
9
examples/expected/0052-basic-slice-range-bounds.stdout
Normal file
9
examples/expected/0052-basic-slice-range-bounds.stdout
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
1..=3: 11 12 13 (len 3)
|
||||||
|
0<..<4: 11 12 13 (len 3)
|
||||||
|
..=2: 10 11 12 (len 3)
|
||||||
|
<..3: 11 12 (len 2)
|
||||||
|
2<..: 13 14 15 (len 3)
|
||||||
|
..: 10 11 12 13 14 15 (len 6)
|
||||||
|
x-1..=x+1: 12 13 14 (len 3)
|
||||||
|
str 1..=3: bcd
|
||||||
|
str 0<..<4: bcd
|
||||||
@@ -347,6 +347,7 @@ for 1..=5, 0.. (a, b) { print("{}:{}\n", a, b); } // a: 1..5, b follows
|
|||||||
for items (val) => total += val; // arrow body
|
for items (val) => total += val; // arrow body
|
||||||
for 0<..<n (i) { } // bound markers: 1 .. n-1
|
for 0<..<n (i) { } // bound markers: 1 .. n-1
|
||||||
for 0=..=n (i) { } // 0 .. n
|
for 0=..=n (i) { } // 0 .. n
|
||||||
|
sub := items[1..=3]; // slices take them too
|
||||||
|
|
||||||
// Defer
|
// Defer
|
||||||
f := open("file.txt");
|
f := open("file.txt");
|
||||||
|
|||||||
13
specs.md
13
specs.md
@@ -965,9 +965,22 @@ word := msg[6..11]; // string → "world"
|
|||||||
- `expr[start..end]` — elements from `start` (inclusive) to `end` (exclusive)
|
- `expr[start..end]` — elements from `start` (inclusive) to `end` (exclusive)
|
||||||
- `expr[start..]` — elements from `start` to end
|
- `expr[start..]` — elements from `start` to end
|
||||||
- `expr[..end]` — elements from beginning to `end`
|
- `expr[..end]` — elements from beginning to `end`
|
||||||
|
- `expr[..]` — the whole collection as a slice
|
||||||
- Result type: `[]T` for arrays/slices, `string` for strings
|
- Result type: `[]T` for arrays/slices, `string` for strings
|
||||||
- No memory allocation — the result points into the original backing storage
|
- No memory allocation — the result points into the original backing storage
|
||||||
|
|
||||||
|
Slice ranges take the same bound markers as for-header ranges — `=`
|
||||||
|
inclusive / `<` exclusive on either side of `..`, defaulting to
|
||||||
|
start-inclusive, end-exclusive:
|
||||||
|
```sx
|
||||||
|
arr[1..=3] // elements 1, 2, 3
|
||||||
|
arr[0<..<4] // elements 1, 2, 3
|
||||||
|
arr[..=2] // elements 0, 1, 2 (prefix form takes markers too)
|
||||||
|
arr[2<..] // elements 3 .. len-1
|
||||||
|
```
|
||||||
|
An explicit end marker (`..=` / `..<`) requires an end expression. Bounds
|
||||||
|
are arbitrary expressions (`arr[x-1..=x+1]`).
|
||||||
|
|
||||||
### Pointer Types
|
### Pointer Types
|
||||||
|
|
||||||
| Syntax | Meaning | `.len` | `[i]` |
|
| Syntax | Meaning | `.len` | `[i]` |
|
||||||
|
|||||||
@@ -594,8 +594,12 @@ pub const IndexExpr = struct {
|
|||||||
|
|
||||||
pub const SliceExpr = struct {
|
pub const SliceExpr = struct {
|
||||||
object: *Node,
|
object: *Node,
|
||||||
start: ?*Node = null,
|
start: ?*Node = null, // null = 0
|
||||||
end: ?*Node = null,
|
end: ?*Node = null, // null = len
|
||||||
|
/// `<..` family — slice begins one past `start`.
|
||||||
|
start_exclusive: bool = false,
|
||||||
|
/// `..=` family — slice includes `end`.
|
||||||
|
end_inclusive: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PointerTypeExpr = struct {
|
pub const PointerTypeExpr = struct {
|
||||||
|
|||||||
@@ -1207,8 +1207,10 @@ pub fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref {
|
|||||||
|
|
||||||
pub fn lowerSliceExpr(self: *Lowering, se: *const ast.SliceExpr) Ref {
|
pub fn lowerSliceExpr(self: *Lowering, se: *const ast.SliceExpr) Ref {
|
||||||
const obj = self.lowerExpr(se.object);
|
const obj = self.lowerExpr(se.object);
|
||||||
const lo = if (se.start) |s| self.lowerExpr(s) else self.builder.constInt(0, .s64);
|
var lo = if (se.start) |s| self.lowerExpr(s) else self.builder.constInt(0, .s64);
|
||||||
const hi = if (se.end) |e| self.lowerExpr(e) else self.builder.emit(.{ .length = .{ .operand = obj } }, .s64);
|
if (se.start_exclusive) lo = self.builder.add(lo, self.builder.constInt(1, .s64), .s64);
|
||||||
|
var hi = if (se.end) |e| self.lowerExpr(e) else self.builder.emit(.{ .length = .{ .operand = obj } }, .s64);
|
||||||
|
if (se.end_inclusive) hi = self.builder.add(hi, self.builder.constInt(1, .s64), .s64);
|
||||||
// Infer result slice type from the object
|
// Infer result slice type from the object
|
||||||
const obj_ty = self.inferExprType(se.object);
|
const obj_ty = self.inferExprType(se.object);
|
||||||
// Subslice of string stays string (same {ptr, i64} layout, correct type category)
|
// Subslice of string stays string (same {ptr, i64} layout, correct type category)
|
||||||
|
|||||||
@@ -2575,26 +2575,45 @@ pub const Parser = struct {
|
|||||||
const saved_hdr_idx = self.in_for_header;
|
const saved_hdr_idx = self.in_for_header;
|
||||||
self.in_for_header = false;
|
self.in_for_header = false;
|
||||||
defer self.in_for_header = saved_hdr_idx;
|
defer self.in_for_header = saved_hdr_idx;
|
||||||
if (self.current.tag == .dot_dot) {
|
if (rangeTokenInfo(self.current.tag)) |rt| {
|
||||||
// [..end]
|
// Prefix form: [..end] / [..=end] / [..] — implicit 0 start
|
||||||
|
// (a start marker applies to it: [<..end] begins at 1).
|
||||||
self.advance();
|
self.advance();
|
||||||
const end_expr = try self.parseExpr();
|
if (rt.end_marked and self.current.tag == .r_bracket) {
|
||||||
|
return self.fail("a range with an explicit end marker ('..=' / '..<') requires an end expression — the open form is '..'");
|
||||||
|
}
|
||||||
|
const end_expr: ?*ast.Node = if (self.current.tag != .r_bracket)
|
||||||
|
try self.parseExpr()
|
||||||
|
else
|
||||||
|
null;
|
||||||
try self.expect(.r_bracket);
|
try self.expect(.r_bracket);
|
||||||
expr = try self.createNode(expr.span.start, .{ .slice_expr = .{
|
expr = try self.createNode(expr.span.start, .{ .slice_expr = .{
|
||||||
.object = expr, .start = null, .end = end_expr,
|
.object = expr,
|
||||||
|
.start = null,
|
||||||
|
.end = end_expr,
|
||||||
|
.start_exclusive = rt.start_exclusive,
|
||||||
|
.end_inclusive = rt.end_inclusive,
|
||||||
} });
|
} });
|
||||||
} else {
|
} else {
|
||||||
const first = try self.parseExpr();
|
const first = try self.parseExpr();
|
||||||
if (self.current.tag == .dot_dot) {
|
if (rangeTokenInfo(self.current.tag)) |rt| {
|
||||||
// [start..end] or [start..]
|
// [start..end] or [start..] — same bound-marker matrix
|
||||||
|
// as the for-header ranges.
|
||||||
self.advance();
|
self.advance();
|
||||||
|
if (rt.end_marked and self.current.tag == .r_bracket) {
|
||||||
|
return self.fail("a range with an explicit end marker ('..=' / '..<') requires an end expression — the open form is 'a..'");
|
||||||
|
}
|
||||||
const end_expr: ?*ast.Node = if (self.current.tag != .r_bracket)
|
const end_expr: ?*ast.Node = if (self.current.tag != .r_bracket)
|
||||||
try self.parseExpr()
|
try self.parseExpr()
|
||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
try self.expect(.r_bracket);
|
try self.expect(.r_bracket);
|
||||||
expr = try self.createNode(expr.span.start, .{ .slice_expr = .{
|
expr = try self.createNode(expr.span.start, .{ .slice_expr = .{
|
||||||
.object = expr, .start = first, .end = end_expr,
|
.object = expr,
|
||||||
|
.start = first,
|
||||||
|
.end = end_expr,
|
||||||
|
.start_exclusive = rt.start_exclusive,
|
||||||
|
.end_inclusive = rt.end_inclusive,
|
||||||
} });
|
} });
|
||||||
} else {
|
} else {
|
||||||
// [index] — normal index access
|
// [index] — normal index access
|
||||||
|
|||||||
Reference in New Issue
Block a user