Skip to content

Commit 13fafe7

Browse files
authored
Merge pull request #246 from NeowayLabs/addIndexingToStrings
Add indexing to strings
2 parents b80e1ab + 99aed2a commit 13fafe7

File tree

13 files changed

+503
-128
lines changed

13 files changed

+503
-128
lines changed

internal/sh/builtin/len.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
type (
1212
lenFn struct {
13-
arg sh.Obj
13+
arg sh.Collection
1414
}
1515
)
1616

@@ -29,12 +29,7 @@ func lenresult(res int) []sh.Obj {
2929
}
3030

3131
func (l *lenFn) Run(in io.Reader, out io.Writer, err io.Writer) ([]sh.Obj, error) {
32-
if l.arg.Type() == sh.ListType {
33-
arglist := l.arg.(*sh.ListObj)
34-
return lenresult(len(arglist.List())), nil
35-
}
36-
argstr := l.arg.(*sh.StrObj)
37-
return lenresult(len(argstr.Str())), nil
32+
return lenresult(l.arg.Len()), nil
3833
}
3934

4035
func (l *lenFn) SetArgs(args []sh.Obj) error {
@@ -43,11 +38,11 @@ func (l *lenFn) SetArgs(args []sh.Obj) error {
4338
}
4439

4540
obj := args[0]
46-
47-
if obj.Type() != sh.ListType && obj.Type() != sh.StringType {
48-
return errors.NewError("lenfn expects a list or a string, but a %s was provided", obj.Type())
41+
col, err := sh.NewCollection(obj)
42+
if err != nil {
43+
return errors.NewError("len:error[%s]", err)
4944
}
5045

51-
l.arg = obj
46+
l.arg = col
5247
return nil
5348
}

internal/sh/shell.go

Lines changed: 48 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -529,41 +529,37 @@ func (shell *Shell) ExecFile(path string) error {
529529
}
530530

531531
func (shell *Shell) setvar(name *ast.NameNode, value sh.Obj) error {
532-
finalObj := value
533532

534-
if name.Index != nil {
535-
if list, ok := shell.Getvar(name.Ident); ok {
536-
index, err := shell.evalIndex(name.Index)
537-
538-
if err != nil {
539-
return err
540-
}
541-
542-
if list.Type() != sh.ListType {
543-
return errors.NewEvalError(shell.filename,
544-
name, "Indexed assigment on non-list type: Variable %s is %s",
545-
name.Ident,
546-
list.Type())
547-
}
533+
if name.Index == nil {
534+
shell.Setvar(name.Ident, value)
535+
return nil
536+
}
548537

549-
lobj := list.(*sh.ListObj)
550-
lvalues := lobj.List()
538+
obj, ok := shell.Getvar(name.Ident)
539+
if !ok {
540+
return errors.NewEvalError(shell.filename,
541+
name, "Variable %s not found", name.Ident)
542+
}
551543

552-
if index >= len(lvalues) {
553-
return errors.NewEvalError(shell.filename,
554-
name, "List out of bounds error. List has %d elements, but trying to access index %d",
555-
len(lvalues), index)
556-
}
544+
index, err := shell.evalIndex(name.Index)
545+
if err != nil {
546+
return err
547+
}
557548

558-
lvalues[index] = value
559-
finalObj = sh.NewListObj(lvalues)
560-
} else {
561-
return errors.NewEvalError(shell.filename,
562-
name, "Variable %s not found", name.Ident)
563-
}
549+
col, err := sh.NewWriteableCollection(obj)
550+
if err != nil {
551+
return errors.NewEvalError(shell.filename, name, err.Error())
564552
}
565553

566-
shell.Setvar(name.Ident, finalObj)
554+
err = col.Set(index, value)
555+
if err != nil {
556+
return errors.NewEvalError(
557+
shell.filename,
558+
name,
559+
"error[%s] setting var",
560+
err,
561+
)
562+
}
567563
return nil
568564
}
569565

@@ -1486,27 +1482,21 @@ func (shell *Shell) evalIndexedVar(indexVar *ast.IndexExpr) (sh.Obj, error) {
14861482
return nil, err
14871483
}
14881484

1489-
if v.Type() != sh.ListType {
1490-
return nil, errors.NewEvalError(shell.filename, indexVar.Var,
1491-
"Invalid indexing of non-list variable: %s (%+v)", v.Type(), v)
1485+
col, err := sh.NewCollection(v)
1486+
if err != nil {
1487+
return nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error())
14921488
}
14931489

14941490
indexNum, err := shell.evalIndex(indexVar.Index)
14951491
if err != nil {
14961492
return nil, err
14971493
}
14981494

1499-
vlist := v.(*sh.ListObj)
1500-
values := vlist.List()
1501-
1502-
if indexNum < 0 || indexNum >= len(values) {
1503-
return nil, errors.NewEvalError(shell.filename,
1504-
indexVar.Var,
1505-
"Index out of bounds. len(%s) == %d, but given %d", indexVar.Var.Name,
1506-
len(values), indexNum)
1495+
val, err := col.Get(indexNum)
1496+
if err != nil {
1497+
return nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error())
15071498
}
1508-
1509-
return values[indexNum], nil
1499+
return val, nil
15101500
}
15111501

15121502
func (shell *Shell) evalArgIndexedVar(indexVar *ast.IndexExpr) ([]sh.Obj, error) {
@@ -1515,28 +1505,21 @@ func (shell *Shell) evalArgIndexedVar(indexVar *ast.IndexExpr) ([]sh.Obj, error)
15151505
return nil, err
15161506
}
15171507

1518-
if v.Type() != sh.ListType {
1519-
return nil, errors.NewEvalError(shell.filename, indexVar.Var,
1520-
"Invalid indexing of non-list variable: %s (%+v)", v.Type(), v)
1508+
col, err := sh.NewCollection(v)
1509+
if err != nil {
1510+
return nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error())
15211511
}
15221512

15231513
indexNum, err := shell.evalIndex(indexVar.Index)
15241514
if err != nil {
15251515
return nil, err
15261516
}
15271517

1528-
vlist := v.(*sh.ListObj)
1529-
values := vlist.List()
1530-
1531-
if indexNum < 0 || indexNum >= len(values) {
1532-
return nil, errors.NewEvalError(shell.filename,
1533-
indexVar.Var,
1534-
"Index out of bounds. len(%s) == %d, but given %d", indexVar.Var.Name,
1535-
len(values), indexNum)
1518+
retval, err := col.Get(indexNum)
1519+
if err != nil {
1520+
return nil, errors.NewEvalError(shell.filename, indexVar.Var, err.Error())
15361521
}
15371522

1538-
retval := values[indexNum]
1539-
15401523
if indexVar.IsVariadic {
15411524
if retval.Type() != sh.ListType {
15421525
return nil, errors.NewEvalError(shell.filename,
@@ -1545,7 +1528,7 @@ func (shell *Shell) evalArgIndexedVar(indexVar *ast.IndexExpr) ([]sh.Obj, error)
15451528
retlist := retval.(*sh.ListObj)
15461529
return retlist.List(), nil
15471530
}
1548-
return []sh.Obj{values[indexNum]}, nil
1531+
return []sh.Obj{retval}, nil
15491532
}
15501533

15511534
func (shell *Shell) evalVariable(a ast.Expr) (sh.Obj, error) {
@@ -2213,14 +2196,18 @@ func (shell *Shell) executeFor(n *ast.ForNode) ([]sh.Obj, error) {
22132196
return nil, err
22142197
}
22152198

2216-
if obj.Type() != sh.ListType {
2199+
col, err := sh.NewCollection(obj)
2200+
if err != nil {
22172201
return nil, errors.NewEvalError(shell.filename,
2218-
inExpr, "Invalid variable type in for range: %s", obj.Type())
2202+
inExpr, "error[%s] trying to iterate", err)
22192203
}
22202204

2221-
objlist := obj.(*sh.ListObj)
2222-
2223-
for _, val := range objlist.List() {
2205+
for i := 0; i < col.Len(); i++ {
2206+
val, err := col.Get(i)
2207+
if err != nil {
2208+
return nil, errors.NewEvalError(shell.filename,
2209+
inExpr, "unexpected error[%s] during iteration", err)
2210+
}
22242211
shell.Setvar(id, val)
22252212

22262213
objs, err := shell.executeTree(n.Tree(), false)

internal/sh/shell_test.go

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -524,59 +524,6 @@ func TestExecuteCmdMultipleAssignment(t *testing.T) {
524524
}
525525
}
526526

527-
// IFS *DO NOT* exists anymore.
528-
// This tests only assure things works as expected (IFS has no power)
529-
func TestExecuteCmdAssignmentIFSDontWork(t *testing.T) {
530-
for _, test := range []execTestCase{
531-
{
532-
"ifs",
533-
`IFS = (" ")
534-
range <= echo 1 2 3 4 5 6 7 8 9 10
535-
536-
for i in $range {
537-
echo "i = " + $i
538-
}`,
539-
"", "",
540-
"<interactive>:4:9: Invalid variable type in for range: StringType",
541-
},
542-
{
543-
"ifs",
544-
`IFS = (";")
545-
range <= echo "1;2;3;4;5;6;7;8;9;10"
546-
547-
for i in $range {
548-
echo "i = " + $i
549-
}`,
550-
"", "",
551-
"<interactive>:4:9: Invalid variable type in for range: StringType",
552-
},
553-
{
554-
"ifs",
555-
`IFS = (" " ";")
556-
range <= echo "1;2;3;4;5;6 7;8;9;10"
557-
558-
for i in $range {
559-
echo "i = " + $i
560-
}`,
561-
"", "",
562-
"<interactive>:4:9: Invalid variable type in for range: StringType",
563-
},
564-
{
565-
"ifs",
566-
`IFS = (" " "-")
567-
range <= echo "1;2;3;4;5;6;7-8;9;10"
568-
569-
for i in $range {
570-
echo "i = " + $i
571-
}`,
572-
"", "",
573-
"<interactive>:4:9: Invalid variable type in for range: StringType",
574-
},
575-
} {
576-
testExec(t, test)
577-
}
578-
}
579-
580527
func TestExecuteRedirection(t *testing.T) {
581528
f, teardown := setup(t)
582529
defer teardown()

sh/obj.go

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,72 @@ type (
2929

3030
StrObj struct {
3131
objType
32-
str string
32+
runes []rune
33+
}
34+
35+
Collection interface {
36+
Len() int
37+
Get(index int) (Obj, error)
38+
}
39+
40+
WriteableCollection interface {
41+
Set(index int, val Obj) error
3342
}
3443
)
3544

45+
func NewCollection(o Obj) (Collection, error) {
46+
sizer, ok := o.(Collection)
47+
if !ok {
48+
return nil, fmt.Errorf(
49+
"SizeError: trying to get size from type %s which is not a collection",
50+
o.Type(),
51+
)
52+
}
53+
return sizer, nil
54+
}
55+
56+
func NewWriteableCollection(o Obj) (WriteableCollection, error) {
57+
indexer, ok := o.(WriteableCollection)
58+
if !ok {
59+
return nil, fmt.Errorf(
60+
"IndexError: trying to use a non write/indexable type %s to write on index: ",
61+
o.Type(),
62+
)
63+
}
64+
return indexer, nil
65+
}
66+
3667
func (o objType) Type() objType {
3768
return o
3869
}
3970

4071
func NewStrObj(val string) *StrObj {
4172
return &StrObj{
42-
str: val,
73+
runes: []rune(val),
4374
objType: StringType,
4475
}
4576
}
4677

47-
func (o *StrObj) Str() string { return o.str }
78+
func (o *StrObj) Str() string { return string(o.runes) }
4879

4980
func (o *StrObj) String() string { return o.Str() }
5081

82+
func (o *StrObj) Get(index int) (Obj, error) {
83+
if index >= o.Len() {
84+
return nil, fmt.Errorf(
85+
"IndexError: Index[%d] out of range, string size[%d]",
86+
index,
87+
o.Len(),
88+
)
89+
}
90+
91+
return NewStrObj(string(o.runes[index])), nil
92+
}
93+
94+
func (o *StrObj) Len() int {
95+
return len(o.runes)
96+
}
97+
5198
func NewFnObj(val FnDef) *FnObj {
5299
return &FnObj{
53100
fn: val,
@@ -66,6 +113,33 @@ func NewListObj(val []Obj) *ListObj {
66113
}
67114
}
68115

116+
func (o *ListObj) Len() int {
117+
return len(o.list)
118+
}
119+
120+
func (o *ListObj) Set(index int, value Obj) error {
121+
if index >= len(o.list) {
122+
return fmt.Errorf(
123+
"IndexError: Index[%d] out of range, list size[%d]",
124+
index,
125+
len(o.list),
126+
)
127+
}
128+
o.list[index] = value
129+
return nil
130+
}
131+
132+
func (o *ListObj) Get(index int) (Obj, error) {
133+
if index >= len(o.list) {
134+
return nil, fmt.Errorf(
135+
"IndexError: Index out of bounds, index[%d] but list size[%d]",
136+
index,
137+
len(o.list),
138+
)
139+
}
140+
return o.list[index], nil
141+
}
142+
69143
func (o *ListObj) List() []Obj { return o.list }
70144

71145
func (o *ListObj) String() string {

tests/cfg.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package tests
2+
3+
const nashcmd = "../cmd/nash/nash"

0 commit comments

Comments
 (0)