diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index e07b019855..0504d828b3 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -818,6 +818,32 @@ pub fn (s string) substr(start int, end int) string { return res } +// version of `substr()` that is used in `a[start..end] or {` +// return an error when the index is out of range +[direct_array_access] +pub fn (s string) substr_with_check(start int, end int) ?string { + if start > end || start > s.len || end > s.len || start < 0 || end < 0 { + return error('substr($start, $end) out of bounds (len=$s.len)') + } + len := end - start + if len == s.len { + return s.clone() + } + mut res := string{ + str: unsafe { malloc_noscan(len + 1) } + len: len + } + for i in 0 .. len { + unsafe { + res.str[i] = s.str[start + i] + } + } + unsafe { + res.str[len] = 0 + } + return res +} + // substr_ni returns the string between index positions `start` and `end` allowing negative indexes // This function always return a valid string. [direct_array_access] diff --git a/vlib/builtin/string_test.v b/vlib/builtin/string_test.v index 88af1bc2a1..6c7283838f 100644 --- a/vlib/builtin/string_test.v +++ b/vlib/builtin/string_test.v @@ -120,6 +120,34 @@ fn test_sort_reverse() { assert vals[3] == 'arr' } +fn test_ranges() { + s := 'test' + s1 := s[0..20] or { 'both' } + s2 := s[..20] or { 'last' } + s3 := s[10..] or { 'first' } + s4 := ranges_propagate_both(s) or { 'both' } + s5 := ranges_propagate_last(s) or { 'last' } + s6 := ranges_propagate_first(s) or { 'first' } + assert s1 == 'both' + assert s2 == 'last' + assert s3 == 'first' + assert s4 == 'both' + assert s5 == 'last' + assert s6 == 'first' +} + +fn ranges_propagate_first(s string) ?string { + return s[10..] ? +} + +fn ranges_propagate_last(s string) ?string { + return s[..20] ? +} + +fn ranges_propagate_both(s string) ?string { + return s[1..20] ? +} + fn test_split_nth() { a := '1,2,3' assert a.split(',').len == 3 diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 26705ecdc1..6214ea0c9f 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3569,6 +3569,10 @@ pub fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { } .array { node.is_array = true + if node.or_expr.kind != .absent && node.index is ast.RangeExpr { + c.error('custom error handling on range expressions for arrays is not supported yet.', + node.or_expr.pos) + } break } .array_fixed { diff --git a/vlib/v/gen/c/index.v b/vlib/v/gen/c/index.v index 20642b84a8..08a041d9d5 100644 --- a/vlib/v/gen/c/index.v +++ b/vlib/v/gen/c/index.v @@ -59,11 +59,23 @@ fn (mut g Gen) index_expr(node ast.IndexExpr) { fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { sym := g.table.final_sym(node.left_type) + mut tmp_opt := '' + mut cur_line := '' + mut gen_or := node.or_expr.kind != .absent || node.is_option + if sym.kind == .string { if node.is_gated { g.write('string_substr_ni(') } else { - g.write('string_substr(') + if gen_or { + tmp_opt = g.new_tmp_var() + cur_line = g.go_before_stmt(0) + g.out.write_string(util.tabs(g.indent)) + opt_elem_type := g.typ(ast.string_type.set_flag(.optional)) + g.write('$opt_elem_type $tmp_opt = string_substr_with_check(') + } else { + g.write('string_substr(') + } } if node.left_type.is_ptr() { g.write('*') @@ -131,6 +143,14 @@ fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { g.write('.len') } g.write(')') + + if gen_or { + if !node.is_option { + g.or_block(tmp_opt, node.or_expr, ast.string_type) + } + + g.write('\n$cur_line*(string*)&${tmp_opt}.data') + } } fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 8f7aa9d55b..cc09e34394 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2378,44 +2378,141 @@ fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { high = p.expr(0) has_high = true } - pos := start_pos.extend(p.tok.position()) + + pos_high := start_pos.extend(p.tok.position()) p.check(.rsbr) + mut or_kind_high := ast.OrKind.absent + mut or_stmts_high := []ast.Stmt{} + mut or_pos_high := token.Position{} + + if !p.or_is_handled { + // a[..end] or {...} + if p.tok.kind == .key_orelse { + was_inside_or_expr := p.inside_or_expr + p.inside_or_expr = true + or_pos_high = p.tok.position() + p.next() + p.open_scope() + or_stmts_high = p.parse_block_no_scope(false) + or_pos_high = or_pos_high.extend(p.prev_tok.position()) + p.close_scope() + p.inside_or_expr = was_inside_or_expr + return ast.IndexExpr{ + left: left + pos: pos_high + index: ast.RangeExpr{ + low: ast.empty_expr() + high: high + has_high: has_high + pos: pos_high + is_gated: is_gated + } + or_expr: ast.OrExpr{ + kind: .block + stmts: or_stmts_high + pos: or_pos_high + } + is_gated: is_gated + } + } + // `a[start..end] ?` + if p.tok.kind == .question { + or_pos_high = p.tok.position() + or_kind_high = .propagate + p.next() + } + } + return ast.IndexExpr{ left: left - pos: pos + pos: pos_high index: ast.RangeExpr{ low: ast.empty_expr() high: high has_high: has_high - pos: pos + pos: pos_high is_gated: is_gated } + or_expr: ast.OrExpr{ + kind: or_kind_high + stmts: or_stmts_high + pos: or_pos_high + } is_gated: is_gated } } expr := p.expr(0) // `[expr]` or `[expr..` mut has_high := false + if p.tok.kind == .dotdot { - // [start..end] or [start..] + // either [start..end] or [start..] p.next() mut high := ast.empty_expr() if p.tok.kind != .rsbr { has_high = true high = p.expr(0) } - pos := start_pos.extend(p.tok.position()) + pos_low := start_pos.extend(p.tok.position()) p.check(.rsbr) + mut or_kind_low := ast.OrKind.absent + mut or_stmts_low := []ast.Stmt{} + mut or_pos_low := token.Position{} + + if !p.or_is_handled { + // a[start..end] or {...} + if p.tok.kind == .key_orelse { + was_inside_or_expr := p.inside_or_expr + p.inside_or_expr = true + or_pos_low = p.tok.position() + p.next() + p.open_scope() + or_stmts_low = p.parse_block_no_scope(false) + or_pos_low = or_pos_low.extend(p.prev_tok.position()) + p.close_scope() + p.inside_or_expr = was_inside_or_expr + return ast.IndexExpr{ + left: left + pos: pos_low + index: ast.RangeExpr{ + low: expr + high: high + has_high: has_high + has_low: has_low + pos: pos_low + is_gated: is_gated + } + or_expr: ast.OrExpr{ + kind: .block + stmts: or_stmts_low + pos: or_pos_low + } + is_gated: is_gated + } + } + // `a[start..end] ?` + if p.tok.kind == .question { + or_pos_low = p.tok.position() + or_kind_low = .propagate + p.next() + } + } + return ast.IndexExpr{ left: left - pos: pos + pos: pos_low index: ast.RangeExpr{ low: expr high: high has_high: has_high has_low: has_low - pos: pos + pos: pos_low is_gated: is_gated } + or_expr: ast.OrExpr{ + kind: or_kind_low + stmts: or_stmts_low + pos: or_pos_low + } is_gated: is_gated } }