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:
agra
2026-06-10 22:12:45 +03:00
parent f513c11ea6
commit fea5617e4e
9 changed files with 93 additions and 11 deletions

View 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
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View 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

View File

@@ -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");

View File

@@ -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]` |

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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