Commit b4e92cee authored by Russ Cox's avatar Russ Cox

cmd/gc: support x[i:j:k]

Design doc at golang.org/s/go12slice.
This is an experimental feature and may not be included in the release.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/10743046
parent 493538ad
......@@ -34,6 +34,8 @@ cgen(Node *n, Node *res)
case OSLICE:
case OSLICEARR:
case OSLICESTR:
case OSLICE3:
case OSLICE3ARR:
if (res->op != ONAME || !res->addable) {
tempname(&n1, n->type);
cgen_slice(n, &n1);
......@@ -629,6 +631,8 @@ agen(Node *n, Node *res)
case OSLICE:
case OSLICEARR:
case OSLICESTR:
case OSLICE3:
case OSLICE3ARR:
tempname(&n1, n->type);
cgen_slice(n, &n1);
agen(&n1, res);
......
......@@ -37,6 +37,8 @@ cgen(Node *n, Node *res)
case OSLICE:
case OSLICEARR:
case OSLICESTR:
case OSLICE3:
case OSLICE3ARR:
if (res->op != ONAME || !res->addable) {
tempname(&n1, n->type);
cgen_slice(n, &n1);
......@@ -841,6 +843,8 @@ agen(Node *n, Node *res)
case OSLICE:
case OSLICEARR:
case OSLICESTR:
case OSLICE3:
case OSLICE3ARR:
tempname(&n1, n->type);
cgen_slice(n, &n1);
agen(&n1, res);
......
......@@ -67,6 +67,8 @@ cgen(Node *n, Node *res)
case OSLICE:
case OSLICEARR:
case OSLICESTR:
case OSLICE3:
case OSLICE3ARR:
if (res->op != ONAME || !res->addable) {
tempname(&n1, n->type);
cgen_slice(n, &n1);
......@@ -547,6 +549,8 @@ agen(Node *n, Node *res)
case OSLICE:
case OSLICEARR:
case OSLICESTR:
case OSLICE3:
case OSLICE3ARR:
tempname(&n1, n->type);
cgen_slice(n, &n1);
agen(&n1, res);
......
......@@ -749,6 +749,8 @@ escassign(EscState *e, Node *dst, Node *src)
case ODOTTYPE2:
case OSLICE:
case OSLICEARR:
case OSLICE3:
case OSLICE3ARR:
// Conversions, field access, slice all preserve the input value.
escassign(e, dst, src->left);
break;
......
......@@ -1022,6 +1022,8 @@ static int opprec[] = {
[OSLICE] = 8,
[OSLICESTR] = 8,
[OSLICEARR] = 8,
[OSLICE3] = 8,
[OSLICE3ARR] = 8,
[ODOTINTER] = 8,
[ODOTMETH] = 8,
[ODOTPTR] = 8,
......@@ -1295,6 +1297,8 @@ exprfmt(Fmt *f, Node *n, int prec)
case OSLICE:
case OSLICESTR:
case OSLICEARR:
case OSLICE3:
case OSLICE3ARR:
exprfmt(f, n->left, nprec);
return fmtprint(f, "[%N]", n->right);
......
......@@ -520,6 +520,8 @@ enum
OSLICE, // v[1:2], typechecking may convert to a more specfic OSLICEXXX.
OSLICEARR, // a[1:2]
OSLICESTR, // s[1:2]
OSLICE3, // v[1:2:3], typechecking may convert to OSLICE3ARR.
OSLICE3ARR, // a[1:2:3]
ORECOVER, // recover
ORECV, // <-c
ORUNESTR, // string(i)
......
......@@ -23,6 +23,8 @@
#include <libc.h>
#include "go.h"
static int isrelease = -1;
static void fixlbrace(int);
%}
%union {
......@@ -950,6 +952,21 @@ pexpr_no_paren:
{
$$ = nod(OSLICE, $1, nod(OKEY, $3, $5));
}
| pexpr '[' oexpr ':' oexpr ':' oexpr ']'
{
// Make sure we don't accidentally release this experimental feature.
// http://golang.org/s/go12slice.
if(isrelease < 0)
isrelease = strstr(getgoversion(), "release") != nil;
if(isrelease)
yyerror("3-index slice not available in release");
if($5 == N)
yyerror("middle index required in 3-index slice");
if($7 == N)
yyerror("final index required in 3-index slice");
$$ = nod(OSLICE3, $1, nod(OKEY, $3, nod(OKEY, $5, $7)));
}
| pseudocall
| convtype '(' expr ocomma ')'
{
......
......@@ -304,6 +304,8 @@ racewalknode(Node **np, NodeList **init, int wr, int skip)
case OSLICE:
case OSLICEARR:
case OSLICE3:
case OSLICE3ARR:
// Seems to only lead to double instrumentation.
//racewalknode(&n->left, init, 0, 0);
goto ret;
......
......@@ -33,6 +33,8 @@ static void stringtoarraylit(Node**);
static Node* resolve(Node*);
static void checkdefergo(Node*);
static int checkmake(Type*, char*, Node*);
static int checksliceindex(Node*, Type*);
static int checksliceconst(Node*, Node*);
static NodeList* typecheckdefstack;
......@@ -303,7 +305,7 @@ static void
typecheck1(Node **np, int top)
{
int et, aop, op, ptr;
Node *n, *l, *r;
Node *n, *l, *r, *lo, *mid, *hi;
NodeList *args;
int ok, ntop;
Type *t, *tp, *missing, *have, *badtype;
......@@ -993,54 +995,63 @@ reswitch:
yyerror("cannot slice %N (type %T)", l, t);
goto error;
}
if(n->right->left != N) {
if((t = n->right->left->type) == T)
goto error;
if(!isint[t->etype]) {
yyerror("invalid slice index %N (type %T)", n->right->left, t);
if((lo = n->right->left) != N && checksliceindex(lo, tp) < 0)
goto error;
if((hi = n->right->right) != N && checksliceindex(hi, tp) < 0)
goto error;
if(checksliceconst(lo, hi) < 0)
goto error;
goto ret;
case OSLICE3:
ok |= Erv;
typecheck(&n->left, top);
typecheck(&n->right->left, Erv);
typecheck(&n->right->right->left, Erv);
typecheck(&n->right->right->right, Erv);
defaultlit(&n->left, T);
indexlit(&n->right->left);
indexlit(&n->right->right->left);
indexlit(&n->right->right->right);
l = n->left;
if(isfixedarray(l->type)) {
if(!islvalue(n->left)) {
yyerror("invalid operation %N (slice of unaddressable value)", n);
goto error;
}
if(n->right->left->op == OLITERAL) {
if(mpgetfix(n->right->left->val.u.xval) < 0) {
yyerror("invalid slice index %N (index must be non-negative)", n->right->left);
goto error;
} else if(tp != nil && tp->bound > 0 && mpgetfix(n->right->left->val.u.xval) > tp->bound) {
yyerror("invalid slice index %N (out of bounds for %d-element array)", n->right->left, tp->bound);
goto error;
} else if(mpcmpfixfix(n->right->left->val.u.xval, maxintval[TINT]) > 0) {
yyerror("invalid slice index %N (index too large)", n->right->left);
goto error;
}
}
n->left = nod(OADDR, n->left, N);
n->left->implicit = 1;
typecheck(&n->left, Erv);
l = n->left;
}
if(n->right->right != N) {
if((t = n->right->right->type) == T)
goto error;
if(!isint[t->etype]) {
yyerror("invalid slice index %N (type %T)", n->right->right, t);
goto error;
}
if(n->right->right->op == OLITERAL) {
if(mpgetfix(n->right->right->val.u.xval) < 0) {
yyerror("invalid slice index %N (index must be non-negative)", n->right->right);
goto error;
} else if(tp != nil && tp->bound > 0 && mpgetfix(n->right->right->val.u.xval) > tp->bound) {
yyerror("invalid slice index %N (out of bounds for %d-element array)", n->right->right, tp->bound);
goto error;
} else if(mpcmpfixfix(n->right->right->val.u.xval, maxintval[TINT]) > 0) {
yyerror("invalid slice index %N (index too large)", n->right->right);
goto error;
}
}
if((t = l->type) == T)
goto error;
tp = nil;
if(istype(t, TSTRING)) {
yyerror("invalid operation %N (3-index slice of string)", n);
goto error;
}
if(n->right->left != N
&& n->right->right != N
&& n->right->left->op == OLITERAL
&& n->right->right->op == OLITERAL
&& mpcmpfixfix(n->right->left->val.u.xval, n->right->right->val.u.xval) > 0) {
yyerror("inverted slice index %N > %N", n->right->left, n->right->right);
if(isptr[t->etype] && isfixedarray(t->type)) {
tp = t->type;
n->type = typ(TARRAY);
n->type->type = tp->type;
n->type->bound = -1;
dowidth(n->type);
n->op = OSLICE3ARR;
} else if(isslice(t)) {
n->type = t;
} else {
yyerror("cannot slice %N (type %T)", l, t);
goto error;
}
if((lo = n->right->left) != N && checksliceindex(lo, tp) < 0)
goto error;
if((mid = n->right->right->left) != N && checksliceindex(mid, tp) < 0)
goto error;
if((hi = n->right->right->right) != N && checksliceindex(hi, tp) < 0)
goto error;
if(checksliceconst(lo, hi) < 0 || checksliceconst(lo, mid) < 0 || checksliceconst(mid, hi) < 0)
goto error;
goto ret;
/*
......@@ -1757,6 +1768,43 @@ out:
*np = n;
}
static int
checksliceindex(Node *r, Type *tp)
{
Type *t;
if((t = r->type) == T)
return -1;
if(!isint[t->etype]) {
yyerror("invalid slice index %N (type %T)", r, t);
return -1;
}
if(r->op == OLITERAL) {
if(mpgetfix(r->val.u.xval) < 0) {
yyerror("invalid slice index %N (index must be non-negative)", r);
return -1;
} else if(tp != nil && tp->bound > 0 && mpgetfix(r->val.u.xval) > tp->bound) {
yyerror("invalid slice index %N (out of bounds for %d-element array)", r, tp->bound);
return -1;
} else if(mpcmpfixfix(r->val.u.xval, maxintval[TINT]) > 0) {
yyerror("invalid slice index %N (index too large)", r);
return -1;
}
}
return 0;
}
static int
checksliceconst(Node *lo, Node *hi)
{
if(lo != N && hi != N && lo->op == OLITERAL && hi->op == OLITERAL
&& mpcmpfixfix(lo->val.u.xval, hi->val.u.xval) > 0) {
yyerror("invalid slice index: %N > %N", lo, hi);
return -1;
}
return 0;
}
static void
checkdefergo(Node *n)
{
......
......@@ -1116,6 +1116,27 @@ walkexpr(Node **np, NodeList **init)
n->right->right = safeexpr(n->right->right, init);
n = sliceany(n, init); // chops n->right, sets n->list
goto ret;
case OSLICE3:
case OSLICE3ARR:
if(n->right == N) // already processed
goto ret;
walkexpr(&n->left, init);
// TODO the OINDEX case is a bug elsewhere that needs to be traced. it causes a crash on ([2][]int{ ... })[1][lo:hi]
// TODO the comment on the previous line was copied from case OSLICE. it might not even be true.
if(n->left->op == OINDEX)
n->left = copyexpr(n->left, n->left->type, init);
else
n->left = safeexpr(n->left, init);
walkexpr(&n->right->left, init);
n->right->left = safeexpr(n->right->left, init);
walkexpr(&n->right->right->left, init);
n->right->right->left = safeexpr(n->right->right->left, init);
walkexpr(&n->right->right->right, init);
n->right->right->right = safeexpr(n->right->right->right, init);
n = sliceany(n, init); // chops n->right, sets n->list
goto ret;
case OADDR:
walkexpr(&n->left, init);
......@@ -2575,21 +2596,28 @@ append(Node *n, NodeList **init)
}
// Generate frontend part for OSLICE[ARR|STR]
// Generate frontend part for OSLICE[3][ARR|STR]
//
static Node*
sliceany(Node* n, NodeList **init)
{
int bounded;
Node *src, *lb, *hb, *bound, *chk, *chk1, *chk2;
int64 lbv, hbv, bv, w;
int bounded, slice3;
Node *src, *lb, *hb, *cb, *bound, *chk, *chk0, *chk1, *chk2;
int64 lbv, hbv, cbv, bv, w;
Type *bt;
// print("before sliceany: %+N\n", n);
src = n->left;
lb = n->right->left;
hb = n->right->right;
slice3 = n->op == OSLICE3 || n->op == OSLICE3ARR;
if(slice3) {
hb = n->right->right->left;
cb = n->right->right->right;
} else {
hb = n->right->right;
cb = hb;
}
bounded = n->etype;
......@@ -2610,6 +2638,13 @@ sliceany(Node* n, NodeList **init)
bv = mpgetfix(bound->val.u.xval);
}
if(isconst(cb, CTINT)) {
cbv = mpgetfix(cb->val.u.xval);
if(cbv < 0 || cbv > bv) {
yyerror("slice index out of bounds");
cbv = -1;
}
}
if(isconst(hb, CTINT)) {
hbv = mpgetfix(hb->val.u.xval);
if(hbv < 0 || hbv > bv) {
......@@ -2631,10 +2666,13 @@ sliceany(Node* n, NodeList **init)
// generate
// if hb > bound || lb > hb { panicslice() }
chk = N;
chk0 = N;
chk1 = N;
chk2 = N;
bt = types[simtype[TUINT]];
if(cb != N && cb->type->width > 4)
bt = types[TUINT64];
if(hb != N && hb->type->width > 4)
bt = types[TUINT64];
if(lb != N && lb->type->width > 4)
......@@ -2642,10 +2680,26 @@ sliceany(Node* n, NodeList **init)
bound = cheapexpr(conv(bound, bt), init);
if(cb != N) {
cb = cheapexpr(conv(cb, bt), init);
if(!bounded)
chk0 = nod(OLT, bound, cb);
} else if(slice3) {
// When we figure out what this means, implement it.
fatal("slice3 with cb == N"); // rejected by parser
}
if(hb != N) {
hb = cheapexpr(conv(hb, bt), init);
if(!bounded)
chk1 = nod(OLT, bound, hb);
if(!bounded) {
if(cb != N)
chk1 = nod(OLT, cb, hb);
else
chk1 = nod(OLT, bound, hb);
}
} else if(slice3) {
// When we figure out what this means, implement it.
fatal("slice3 with hb == N"); // rejected by parser
} else if(n->op == OSLICEARR) {
hb = bound;
} else {
......@@ -2661,11 +2715,17 @@ sliceany(Node* n, NodeList **init)
chk2 = nod(OLT, hb, lb);
}
if(chk1 != N || chk2 != N) {
if(chk0 != N || chk1 != N || chk2 != N) {
chk = nod(OIF, N, N);
chk->nbody = list1(mkcall("panicslice", T, init));
if(chk1 != N)
chk->ntest = chk1;
if(chk0 != N)
chk->ntest = chk0;
if(chk1 != N) {
if(chk->ntest == N)
chk->ntest = chk1;
else
chk->ntest = nod(OOROR, chk->ntest, chk1);
}
if(chk2 != N) {
if(chk->ntest == N)
chk->ntest = chk2;
......@@ -2683,10 +2743,12 @@ sliceany(Node* n, NodeList **init)
// cap = bound [ - lo ]
n->right = N;
n->list = nil;
if(!slice3)
cb = bound;
if(lb == N)
bound = conv(bound, types[simtype[TUINT]]);
bound = conv(cb, types[simtype[TUINT]]);
else
bound = nod(OSUB, conv(bound, types[simtype[TUINT]]), conv(lb, types[simtype[TUINT]]));
bound = nod(OSUB, conv(cb, types[simtype[TUINT]]), conv(lb, types[simtype[TUINT]]));
typecheck(&bound, Erv);
walkexpr(&bound, init);
n->list = list(n->list, bound);
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -74,6 +74,6 @@ static struct {
112, LNAME,
"nested func not allowed",
642, ';',
644, ';',
"else must be followed by if or statement block"
};
......@@ -9,13 +9,13 @@
package p
func F1(s []byte) []byte {
return s[2:1] // ERROR "inverted"
return s[2:1] // ERROR "invalid slice index"
}
func F2(a [10]byte) []byte {
return a[2:1] // ERROR "inverted"
return a[2:1] // ERROR "invalid slice index"
}
func F3(s string) string {
return s[2:1] // ERROR "inverted"
return s[2:1] // ERROR "invalid slice index"
}
// runoutput
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test run-time behavior of 3-index slice expressions.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
var bout *bufio.Writer
func main() {
bout = bufio.NewWriter(os.Stdout)
fmt.Fprintf(bout, "%s", programTop)
fmt.Fprintf(bout, "func main() {\n")
index := []string{
"0",
"1",
"2",
"3",
"10",
"20",
"vminus1",
"v0",
"v1",
"v2",
"v3",
"v10",
"v20",
}
parse := func(s string) (n int, isconst bool) {
if s == "vminus1" {
return -1, false
}
isconst = true
if s[0] == 'v' {
isconst = false
s = s[1:]
}
n, _ = strconv.Atoi(s)
return n, isconst
}
const Cap = 10 // cap of slice, array
for _, base := range []string{"array", "slice"} {
for _, i := range index {
iv, iconst := parse(i)
for _, j := range index {
jv, jconst := parse(j)
for _, k := range index {
kv, kconst := parse(k)
// Avoid errors that would make the program not compile.
// Those are tested by slice3err.go.
switch {
case iconst && jconst && iv > jv,
jconst && kconst && jv > kv,
iconst && kconst && iv > kv,
iconst && base == "array" && iv > Cap,
jconst && base == "array" && jv > Cap,
kconst && base == "array" && kv > Cap:
continue
}
expr := base + "[" + i + ":" + j + ":" + k + "]"
var xbase, xlen, xcap int
if iv > jv || jv > kv || kv > Cap || iv < 0 || jv < 0 || kv < 0 {
xbase, xlen, xcap = -1, -1, -1
} else {
xbase = iv
xlen = jv - iv
xcap = kv - iv
}
fmt.Fprintf(bout, "\tcheckSlice(%q, func() []byte { return %s }, %d, %d, %d)\n", expr, expr, xbase, xlen, xcap)
}
}
}
}
fmt.Fprintf(bout, "\tif !ok { os.Exit(1) }\n")
fmt.Fprintf(bout, "}\n")
bout.Flush()
}
var programTop = `
package main
import (
"fmt"
"os"
"unsafe"
)
var ok = true
var (
array = new([10]byte)
slice = array[:]
vminus1 = -1
v0 = 0
v1 = 1
v2 = 2
v3 = 3
v4 = 4
v5 = 5
v10 = 10
v20 = 20
)
func notOK() {
if ok {
println("BUG:")
ok = false
}
}
func checkSlice(desc string, f func() []byte, xbase, xlen, xcap int) {
defer func() {
if err := recover(); err != nil {
if xbase >= 0 {
notOK()
println(desc, " unexpected panic: ", fmt.Sprint(err))
}
}
// "no panic" is checked below
}()
x := f()
arrayBase := uintptr(unsafe.Pointer(array))
raw := *(*[3]uintptr)(unsafe.Pointer(&x))
base, len, cap := raw[0] - arrayBase, raw[1], raw[2]
if xbase < 0 {
notOK()
println(desc, "=", base, len, cap, "want panic")
return
}
if base != uintptr(xbase) || len != uintptr(xlen) || cap != uintptr(xcap) {
notOK()
println(desc, "=", base, len, cap, "want", xbase, xlen, xcap)
}
}
`
// errorcheck
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package p
var array *[10]int
var slice []int
var str string
var i, j, k int
func f() {
// check what missing arguments are allowed
_ = array[:]
_ = array[i:]
_ = array[:j]
_ = array[i:j]
_ = array[::] // ERROR "middle index required in 3-index slice" "final index required in 3-index slice"
_ = array[i::] // ERROR "middle index required in 3-index slice" "final index required in 3-index slice"
_ = array[:j:] // ERROR "final index required in 3-index slice"
_ = array[i:j:] // ERROR "final index required in 3-index slice"
_ = array[::k] // ERROR "middle index required in 3-index slice"
_ = array[i::k] // ERROR "middle index required in 3-index slice"
_ = array[:j:k]
_ = array[i:j:k]
_ = slice[:]
_ = slice[i:]
_ = slice[:j]
_ = slice[i:j]
_ = slice[::] // ERROR "middle index required in 3-index slice" "final index required in 3-index slice"
_ = slice[i::] // ERROR "middle index required in 3-index slice" "final index required in 3-index slice"
_ = slice[:j:] // ERROR "final index required in 3-index slice"
_ = slice[i:j:] // ERROR "final index required in 3-index slice"
_ = slice[::k] // ERROR "middle index required in 3-index slice"
_ = slice[i::k] // ERROR "middle index required in 3-index slice"
_ = slice[:j:k]
_ = slice[i:j:k]
_ = str[:]
_ = str[i:]
_ = str[:j]
_ = str[i:j]
_ = str[::] // ERROR "3-index slice of string" "middle index required in 3-index slice" "final index required in 3-index slice"
_ = str[i::] // ERROR "3-index slice of string" "middle index required in 3-index slice" "final index required in 3-index slice"
_ = str[:j:] // ERROR "3-index slice of string" "final index required in 3-index slice"
_ = str[i:j:] // ERROR "3-index slice of string" "final index required in 3-index slice"
_ = str[::k] // ERROR "3-index slice of string" "middle index required in 3-index slice"
_ = str[i::k] // ERROR "3-index slice of string" "middle index required in 3-index slice"
_ = str[:j:k] // ERROR "3-index slice of string"
_ = str[i:j:k] // ERROR "3-index slice of string"
// check invalid indices
_ = array[1:2]
_ = array[2:1] // ERROR "invalid slice index"
_ = array[2:2]
_ = array[i:1]
_ = array[1:j]
_ = array[1:2:3]
_ = array[1:3:2] // ERROR "invalid slice index"
_ = array[2:1:3] // ERROR "invalid slice index"
_ = array[2:3:1] // ERROR "invalid slice index"
_ = array[3:1:2] // ERROR "invalid slice index"
_ = array[3:2:1] // ERROR "invalid slice index"
_ = array[i:1:2]
_ = array[i:2:1] // ERROR "invalid slice index"
_ = array[1:j:2]
_ = array[2:j:1] // ERROR "invalid slice index"
_ = array[1:2:k]
_ = array[2:1:k] // ERROR "invalid slice index"
_ = slice[1:2]
_ = slice[2:1] // ERROR "invalid slice index"
_ = slice[2:2]
_ = slice[i:1]
_ = slice[1:j]
_ = slice[1:2:3]
_ = slice[1:3:2] // ERROR "invalid slice index"
_ = slice[2:1:3] // ERROR "invalid slice index"
_ = slice[2:3:1] // ERROR "invalid slice index"
_ = slice[3:1:2] // ERROR "invalid slice index"
_ = slice[3:2:1] // ERROR "invalid slice index"
_ = slice[i:1:2]
_ = slice[i:2:1] // ERROR "invalid slice index"
_ = slice[1:j:2]
_ = slice[2:j:1] // ERROR "invalid slice index"
_ = slice[1:2:k]
_ = slice[2:1:k] // ERROR "invalid slice index"
_ = str[1:2]
_ = str[2:1] // ERROR "invalid slice index"
_ = str[2:2]
_ = str[i:1]
_ = str[1:j]
// check out of bounds indices on array
_ = array[11:11] // ERROR "out of bounds for 10-element array"
_ = array[11:12] // ERROR "out of bounds for 10-element array"
_ = array[11:] // ERROR "out of bounds for 10-element array"
_ = array[:11] // ERROR "out of bounds for 10-element array"
_ = array[1:11] // ERROR "out of bounds for 10-element array"
_ = array[1:11:12] // ERROR "out of bounds for 10-element array"
_ = array[1:2:11] // ERROR "out of bounds for 10-element array"
_ = array[1:11:3] // ERROR "out of bounds for 10-element array"
_ = array[11:2:3] // ERROR "out of bounds for 10-element array"
_ = array[11:12:13] // ERROR "out of bounds for 10-element array"
// slice bounds not checked
_ = slice[11:11]
_ = slice[11:12]
_ = slice[11:]
_ = slice[:11]
_ = slice[1:11]
_ = slice[1:11:12]
_ = slice[1:2:11]
_ = slice[1:11:3] // ERROR "invalid slice index"
_ = slice[11:2:3] // ERROR "invalid slice index"
_ = slice[11:12:13]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment