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

@@ -594,8 +594,12 @@ pub const IndexExpr = struct {
pub const SliceExpr = struct {
object: *Node,
start: ?*Node = null,
end: ?*Node = null,
start: ?*Node = null, // null = 0
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 {

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 {
const obj = self.lowerExpr(se.object);
const 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);
var lo = if (se.start) |s| self.lowerExpr(s) else self.builder.constInt(0, .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
const obj_ty = self.inferExprType(se.object);
// 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;
self.in_for_header = false;
defer self.in_for_header = saved_hdr_idx;
if (self.current.tag == .dot_dot) {
// [..end]
if (rangeTokenInfo(self.current.tag)) |rt| {
// Prefix form: [..end] / [..=end] / [..] — implicit 0 start
// (a start marker applies to it: [<..end] begins at 1).
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);
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 {
const first = try self.parseExpr();
if (self.current.tag == .dot_dot) {
// [start..end] or [start..]
if (rangeTokenInfo(self.current.tag)) |rt| {
// [start..end] or [start..] — same bound-marker matrix
// as the for-header ranges.
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)
try self.parseExpr()
else
null;
try self.expect(.r_bracket);
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 {
// [index] — normal index access