From 170e6a23a183392b1bc9cf8e010e9dd5ca36f656 Mon Sep 17 00:00:00 2001 From: Markus Freitag Date: Fri, 11 Feb 2022 01:18:48 +0100 Subject: [PATCH] fix indexable object types and add index ranges --- ast/assign.go | 2 +- ast/index.go | 1 + evaluator/assign.go | 76 +++++++++- evaluator/assign_test.go | 280 ++++++++++++++++++++++++++++++++++++ evaluator/evaluator.go | 2 +- evaluator/evaluator_test.go | 82 ++++++++++- evaluator/index.go | 45 ++++-- evaluator/index_test.go | 182 +++++++++++++++++++++++ parser/assign.go | 3 + parser/expression.go | 8 +- parser/index.go | 11 ++ tests/indexables.expected | 19 +++ tests/indexables.rl | 32 +++++ 13 files changed, 729 insertions(+), 14 deletions(-) create mode 100644 evaluator/assign_test.go create mode 100644 evaluator/index_test.go create mode 100644 tests/indexables.expected create mode 100644 tests/indexables.rl diff --git a/ast/assign.go b/ast/assign.go index 08044cba..ca9a8b2d 100644 --- a/ast/assign.go +++ b/ast/assign.go @@ -8,7 +8,7 @@ import ( type Assign struct { Token token.Token - Name *Identifier + Name Expression Value Expression } diff --git a/ast/index.go b/ast/index.go index 96b03cbd..a4c3db75 100644 --- a/ast/index.go +++ b/ast/index.go @@ -10,6 +10,7 @@ type Index struct { Token token.Token Left Expression Index Expression + Colon string } func (ie *Index) TokenLiteral() string { return ie.Token.Literal } diff --git a/evaluator/assign.go b/evaluator/assign.go index ea69e24a..fc43a21e 100644 --- a/evaluator/assign.go +++ b/evaluator/assign.go @@ -1,6 +1,9 @@ package evaluator import ( + "fmt" + "math" + "github.com/flipez/rocket-lang/ast" "github.com/flipez/rocket-lang/object" ) @@ -11,6 +14,77 @@ func evalAssign(a *ast.Assign, env *object.Environment) (val object.Object) { return evaluated } - env.Set(a.Name.String(), evaluated) + switch v := a.Name.(type) { + case *ast.Identifier: + env.Set(v.String(), evaluated) + case *ast.Index: + obj, _ := env.Get(v.Left.String()) + switch o := obj.(type) { + case *object.Array: + idx, err := handleIntegerIndex(v, env) + if err != nil { + return object.NewError(err) + } + + if l := int64(len(o.Elements)); int64(math.Abs(float64(idx))) >= l { + return object.NewErrorFormat( + "index out of range, got %d but array has only %d elements", idx, l, + ) + } + + if idx < 0 { + idx = int64(len(o.Elements)) + idx + } + + o.Elements[idx] = evaluated + case *object.Hash: + obj := Eval(v.Index, env) + h, ok := obj.(object.Hashable) + if !ok { + return object.NewErrorFormat("expected index to be hashable") + } + + key := h.HashKey() + o.Pairs[key] = object.HashPair{Key: obj, Value: evaluated} + case *object.String: + idx, err := handleIntegerIndex(v, env) + if err != nil { + return object.NewError(err) + } + + if l := int64(len(o.Value)); int64(math.Abs(float64(idx))) >= l { + return object.NewErrorFormat( + "index out of range, got %d but string is only %d long", idx, l, + ) + } + + if idx < 0 { + idx = int64(len(o.Value)) + idx + } + + strEval, ok := evaluated.(*object.String) + if !ok { + return object.NewErrorFormat("expected STRING object, got %s", evaluated.Type()) + } + if l := len(strEval.Value); l != 1 { + return object.NewErrorFormat( + "expected STRING object to have a length of 1, got %d", l, + ) + } + + o.Value = o.Value[:idx] + strEval.Value + o.Value[idx+1:] + default: + return object.NewErrorFormat("expected object to be indexable") + } + } return evaluated } + +func handleIntegerIndex(ai *ast.Index, env *object.Environment) (int64, error) { + obj := Eval(ai.Index, env) + num, ok := obj.(*object.Integer) + if !ok { + return 0, fmt.Errorf("expected index to be an INTEGER, got %s", obj.Type()) + } + return num.Value, nil +} diff --git a/evaluator/assign_test.go b/evaluator/assign_test.go new file mode 100644 index 00000000..fb059934 --- /dev/null +++ b/evaluator/assign_test.go @@ -0,0 +1,280 @@ +package evaluator + +import ( + "testing" + + "github.com/flipez/rocket-lang/ast" + "github.com/flipez/rocket-lang/object" +) + +func newAstAssign(name, value ast.Expression) *ast.Assign { + return &ast.Assign{ + Name: name, + Value: value, + } +} + +func prefilledEnv(objs map[string]object.Object) *object.Environment { + env := object.NewEnvironment() + for name, obj := range objs { + env.Set(name, obj) + } + return env +} + +func TestEvalAssign(t *testing.T) { + testcases := []struct { + env *object.Environment + a *ast.Assign + expected object.Object + }{ + // valid string assignment + { + a: newAstAssign( + &ast.Identifier{Value: "s"}, + &ast.String{Value: "abc"}, + ), + expected: object.NewString("abc"), + }, + // assignment with an invalid expression + { + a: newAstAssign( + &ast.Identifier{Value: "s"}, + &ast.Infix{ + Left: &ast.String{Value: "abc"}, + Operator: "+", + Right: &ast.Integer{Value: 123}, + }, + ), + expected: object.NewErrorFormat("type mismatch: STRING + INTEGER"), + }, + // valid array assignment with positive index + { + env: prefilledEnv(map[string]object.Object{ + "a": object.NewArrayWithObjects( + object.NewString("a"), + object.NewString("b"), + object.NewString("c"), + ), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "a"}, + Index: &ast.Integer{Value: 0}, + }, + &ast.String{Value: "A"}, + ), + expected: object.NewString("A"), + }, + // valid array assignment with negative index + { + env: prefilledEnv(map[string]object.Object{ + "a": object.NewArrayWithObjects( + object.NewString("a"), + object.NewString("b"), + object.NewString("c"), + ), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "a"}, + Index: &ast.Integer{Value: -1}, + }, + &ast.String{Value: "C"}, + ), + expected: object.NewString("C"), + }, + // array assignment with non integer index + { + env: prefilledEnv(map[string]object.Object{ + "a": object.NewArrayWithObjects( + object.NewString("a"), + object.NewString("b"), + object.NewString("c"), + ), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "a"}, + Index: &ast.String{Value: "1"}, + }, + &ast.String{Value: "C"}, + ), + expected: object.NewErrorFormat("expected index to be an INTEGER, got STRING"), + }, + // array assignment with integer index bigger than array size + { + env: prefilledEnv(map[string]object.Object{ + "a": object.NewArrayWithObjects( + object.NewString("a"), + object.NewString("b"), + object.NewString("c"), + ), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "a"}, + Index: &ast.Integer{Value: 3}, + }, + &ast.String{Value: "C"}, + ), + expected: object.NewErrorFormat("index out of range, got 3 but array has only 3 elements"), + }, + // valid hash assignment + { + env: prefilledEnv(map[string]object.Object{ + "h": object.NewHash(map[object.HashKey]object.HashPair{ + object.NewString("a").HashKey(): object.HashPair{ + Key: object.NewString("a"), + Value: object.NewInteger(1), + }, + }), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "h"}, + Index: &ast.String{Value: "a"}, + }, + &ast.Integer{Value: 2}, + ), + expected: object.NewInteger(2), + }, + // hash assignment with invalid index + { + env: prefilledEnv(map[string]object.Object{ + "h": object.NewHash(map[object.HashKey]object.HashPair{ + object.NewString("a").HashKey(): object.HashPair{ + Key: object.NewString("a"), + Value: object.NewInteger(1), + }, + }), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "h"}, + Index: &ast.Infix{ + Left: &ast.String{Value: "abc"}, + Operator: "+", + Right: &ast.Integer{Value: 123}, + }, + }, + &ast.Integer{Value: 2}, + ), + expected: object.NewErrorFormat("expected index to be hashable"), + }, + + // valid string assignment with positive index + { + env: prefilledEnv(map[string]object.Object{ + "s": object.NewString("abc"), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "s"}, + Index: &ast.Integer{Value: 0}, + }, + &ast.String{Value: "A"}, + ), + expected: object.NewString("A"), + }, + // valid string assignment with negative index + { + env: prefilledEnv(map[string]object.Object{ + "s": object.NewString("abc"), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "s"}, + Index: &ast.Integer{Value: -1}, + }, + &ast.String{Value: "C"}, + ), + expected: object.NewString("C"), + }, + // string assignment with non integer index + { + env: prefilledEnv(map[string]object.Object{ + "s": object.NewString("abc"), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "s"}, + Index: &ast.String{Value: "1"}, + }, + &ast.String{Value: "C"}, + ), + expected: object.NewErrorFormat("expected index to be an INTEGER, got STRING"), + }, + // string assignment with integer index bigger than string size + { + env: prefilledEnv(map[string]object.Object{ + "s": object.NewString("abc"), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "s"}, + Index: &ast.Integer{Value: 3}, + }, + &ast.String{Value: "C"}, + ), + expected: object.NewErrorFormat("index out of range, got 3 but string is only 3 long"), + }, + // string assignment with valid index but invalid value type + { + env: prefilledEnv(map[string]object.Object{ + "s": object.NewString("abc"), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "s"}, + Index: &ast.Integer{Value: 0}, + }, + &ast.Integer{Value: 1}, + ), + expected: object.NewErrorFormat("expected STRING object, got INTEGER"), + }, + // string assignment with valid index but invalid value size + { + env: prefilledEnv(map[string]object.Object{ + "s": object.NewString("abc"), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "s"}, + Index: &ast.Integer{Value: 0}, + }, + &ast.String{Value: "AA"}, + ), + expected: object.NewErrorFormat("expected STRING object to have a length of 1, got 2"), + }, + // index assignment with non indexable object + { + env: prefilledEnv(map[string]object.Object{ + "i": object.NewInteger(123), + }), + a: newAstAssign( + &ast.Index{ + Left: &ast.Identifier{Value: "i"}, + Index: &ast.Integer{Value: 0}, + }, + &ast.Integer{Value: 4}, + ), + expected: object.NewErrorFormat("expected object to be indexable"), + }, + } + + for _, tc := range testcases { + if tc.env == nil { + tc.env = object.NewEnvironment() + } + obj := evalAssign(tc.a, tc.env) + if obj.Type() != tc.expected.Type() { + t.Errorf("expected object to be a %s, got %s", tc.expected.Type(), obj.Type()) + continue + } + if obj.Inspect() != tc.expected.Inspect() { + t.Errorf("unexpected result, got=%s, want=%s", obj.Inspect(), tc.expected.Inspect()) + continue + } + } +} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 23c1cf59..01690b17 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -104,7 +104,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object { if object.IsError(index) { return index } - return evalIndex(left, index) + return evalIndex(left, index, node.Colon) case *ast.ObjectCall: res := evalObjectCall(node, env) diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 198dd7bf..7480330c 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -292,6 +292,60 @@ func TestStringLiteral(t *testing.T) { } } +func TestStringIndexExpressions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + `"abc"[1]`, + "b", + }, + { + `"abc"[-1]`, + "c", + }, + { + `"abc"[4]`, + nil, + }, + { + `"abc"[:2]`, + "ab", + }, + { + `"abc"[:-2]`, + "a", + }, + { + `"abc"[2:]`, + "c", + }, + { + `"abc"[-2:]`, + "bc", + }, + { + `s="abc";s[1]="B";s[1]`, + "B", + }, + { + `s="abc";s[-2]="B";s[-2]`, + "B", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + str, ok := tt.expected.(string) + if ok { + testStringObject(t, evaluated, str) + } else { + testNullObject(t, evaluated) + } + } +} + func TestStringConcatenation(t *testing.T) { input := `"Hello" + " " + "World!"` @@ -404,7 +458,15 @@ func TestArrayIndexExpressions(t *testing.T) { }, { "[1, 2, 3][-1]", - nil, + 3, + }, + { + `a=[1,2,3];a[1]=5;a[1]`, + 5, + }, + { + `a=[1,2,3];a[-1]=5;a[-1]`, + 5, }, } @@ -493,6 +555,10 @@ func TestHashIndexExpressions(t *testing.T) { `{false: 5}[false]`, 5, }, + { + `h={"a": 1};h["a"]=5;h["a"]`, + 5, + }, } for _, tt := range tests { @@ -594,6 +660,20 @@ func testEval(input string) object.Object { return Eval(program, env) } +func testStringObject(t *testing.T, obj object.Object, expected string) bool { + result, ok := obj.(*object.String) + if !ok { + t.Errorf("object is not String. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%s, want=%s", result.Value, expected) + return false + } + + return true +} + func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { result, ok := obj.(*object.Integer) if !ok { diff --git a/evaluator/index.go b/evaluator/index.go index ee3eca7b..9f67a439 100644 --- a/evaluator/index.go +++ b/evaluator/index.go @@ -4,14 +4,14 @@ import ( "github.com/flipez/rocket-lang/object" ) -func evalIndex(left, index object.Object) object.Object { +func evalIndex(left, index object.Object, colon string) object.Object { switch { case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: - return evalArrayIndexExpression(left, index) + return evalArrayIndexExpression(left, index, colon) case left.Type() == object.HASH_OBJ: return evalHashIndexExpression(left, index) case left.Type() == object.STRING_OBJ: - return evalStringIndexExpression(left, index) + return evalStringIndexExpression(left, index, colon) case left.Type() == object.MODULE_OBJ: return evalModuleIndexExpression(left, index) default: @@ -25,16 +25,29 @@ func evalModuleIndexExpression(module, index object.Object) object.Object { return evalHashIndexExpression(moduleObject.Attributes, index) } -func evalStringIndexExpression(left, index object.Object) object.Object { +func evalStringIndexExpression(left, index object.Object, colon string) object.Object { stringObject := left.(*object.String) idx := index.(*object.Integer).Value max := int64(len(stringObject.Value) - 1) - if idx < 0 || idx > max { + if idx < 0 { + idx += max + 1 + } + + if idx > max { return object.NULL } - return object.NewString(string(stringObject.Value[idx])) + var result object.String + switch colon { + case "left": + result.Value = stringObject.Value[:idx] + case "right": + result.Value = stringObject.Value[idx:] + default: + result.Value = string(stringObject.Value[idx]) + } + return &result } func evalHashIndexExpression(hash, index object.Object) object.Object { @@ -52,14 +65,28 @@ func evalHashIndexExpression(hash, index object.Object) object.Object { return pair.Value } -func evalArrayIndexExpression(array, index object.Object) object.Object { +func evalArrayIndexExpression(array, index object.Object, colon string) object.Object { arrayObject := array.(*object.Array) idx := index.(*object.Integer).Value max := int64(len(arrayObject.Elements) - 1) - if idx < 0 || idx > max { + if idx < 0 { + idx += max + 1 + } + + if idx > max { return object.NULL } - return arrayObject.Elements[idx] + if colon == "" { + return arrayObject.Elements[idx] + } + + var sub object.Array + if colon == "left" { + sub.Elements = arrayObject.Elements[:idx] + } else if colon == "right" { + sub.Elements = arrayObject.Elements[idx:] + } + return &sub } diff --git a/evaluator/index_test.go b/evaluator/index_test.go new file mode 100644 index 00000000..7609e282 --- /dev/null +++ b/evaluator/index_test.go @@ -0,0 +1,182 @@ +package evaluator + +import ( + "testing" + + "github.com/flipez/rocket-lang/object" +) + +func TestEvalIndex(t *testing.T) { + testcases := []struct { + left, index object.Object + colon string + expected object.Object + }{ + // "abcde"[2] => c + { + left: object.NewString("abcde"), + index: object.NewInteger(2), + expected: object.NewString("c"), + }, + // "abcde"[-2] => d + { + left: object.NewString("abcde"), + index: object.NewInteger(-2), + expected: object.NewString("d"), + }, + // "abcde"[2:] => cde + { + left: object.NewString("abcde"), + index: object.NewInteger(2), + colon: "right", + expected: object.NewString("cde"), + }, + // "abcde"[-2:] => de + { + left: object.NewString("abcde"), + index: object.NewInteger(-2), + colon: "right", + expected: object.NewString("de"), + }, + // "abcde"[:2] => ab + { + left: object.NewString("abcde"), + index: object.NewInteger(2), + colon: "left", + expected: object.NewString("ab"), + }, + // "abcde"[:-2] => abc + { + left: object.NewString("abcde"), + index: object.NewInteger(-2), + colon: "left", + expected: object.NewString("abc"), + }, + // "abcde"[10] => NULL + { + left: object.NewString("abcde"), + index: object.NewInteger(10), + expected: object.NULL, + }, + + // {"a": 1}["a"] => 1 + { + left: object.NewHash(map[object.HashKey]object.HashPair{ + object.NewString("a").HashKey(): object.HashPair{ + Key: object.NewString("a"), + Value: object.NewInteger(1), + }, + }), + index: object.NewString("a"), + expected: object.NewInteger(1), + }, + // {"a": 1}["b"] => NULL + { + left: object.NewHash(map[object.HashKey]object.HashPair{ + object.NewString("a").HashKey(): object.HashPair{ + Key: object.NewString("a"), + Value: object.NewInteger(1), + }, + }), + index: object.NewString("b"), + expected: object.NULL, + }, + // {"a": 1}[NULL] => ERROR: unusable as hash key: NULL + { + left: object.NewHash(map[object.HashKey]object.HashPair{ + object.NewString("a").HashKey(): object.HashPair{ + Key: object.NewString("a"), + Value: object.NewInteger(1), + }, + }), + index: object.NULL, + expected: object.NewErrorFormat("unusable as hash key: NULL"), + }, + + // ["a","b","c","d","e"][2] => "c" + { + left: object.NewArrayWithObjects( + object.NewString("a"), object.NewString("b"), object.NewString("c"), + object.NewString("d"), object.NewString("e"), + ), + index: object.NewInteger(2), + expected: object.NewString("c"), + }, + // ["a","b","c","d","e"][-2] => "d" + { + left: object.NewArrayWithObjects( + object.NewString("a"), object.NewString("b"), object.NewString("c"), + object.NewString("d"), object.NewString("e"), + ), + index: object.NewInteger(-2), + expected: object.NewString("d"), + }, + // ["a","b","c","d","e"][2:] => ["c", "d", "e"] + { + left: object.NewArrayWithObjects( + object.NewString("a"), object.NewString("b"), object.NewString("c"), + object.NewString("d"), object.NewString("e"), + ), + index: object.NewInteger(2), + colon: "right", + expected: object.NewArrayWithObjects( + object.NewString("c"), object.NewString("d"), object.NewString("e"), + ), + }, + // ["a","b","c","d","e"][-2:] => ["d", "e"] + { + left: object.NewArrayWithObjects( + object.NewString("a"), object.NewString("b"), object.NewString("c"), + object.NewString("d"), object.NewString("e"), + ), + index: object.NewInteger(-2), + colon: "right", + expected: object.NewArrayWithObjects( + object.NewString("d"), object.NewString("e"), + ), + }, + // ["a","b","c","d","e"][:2] => ["a", "b"] + { + left: object.NewArrayWithObjects( + object.NewString("a"), object.NewString("b"), object.NewString("c"), + object.NewString("d"), object.NewString("e"), + ), + index: object.NewInteger(2), + colon: "left", + expected: object.NewArrayWithObjects( + object.NewString("a"), object.NewString("b"), + ), + }, + // ["a","b","c","d","e"][:-2] => ["a", "b", "c"] + { + left: object.NewArrayWithObjects( + object.NewString("a"), object.NewString("b"), object.NewString("c"), + object.NewString("d"), object.NewString("e"), + ), + index: object.NewInteger(-2), + colon: "left", + expected: object.NewArrayWithObjects( + object.NewString("a"), object.NewString("b"), object.NewString("c"), + ), + }, + + // 12345[2] => ERROR: index operator not supported: INTEGER + { + left: object.NewInteger(12345), + index: object.NewInteger(2), + expected: object.NewErrorFormat("index operator not supported: INTEGER"), + }, + } + + for _, tc := range testcases { + obj := evalIndex(tc.left, tc.index, tc.colon) + if obj.Type() != tc.expected.Type() { + t.Errorf("expected object to be a %s, got %s", tc.expected.Type(), obj.Type()) + continue + } + if obj.Inspect() != tc.expected.Inspect() { + t.Errorf("unexpected result, got=%s, want=%s", obj.Inspect(), tc.expected.Inspect()) + continue + } + } +} diff --git a/parser/assign.go b/parser/assign.go index 2ea52590..58c130a3 100644 --- a/parser/assign.go +++ b/parser/assign.go @@ -10,10 +10,13 @@ func (p *Parser) parseAssignExpression(name ast.Expression) ast.Expression { stmt := &ast.Assign{Token: p.curToken} if n, ok := name.(*ast.Identifier); ok { stmt.Name = n + } else if index, ok := name.(*ast.Index); ok { + stmt.Name = index } else { msg := fmt.Sprintf("%d:%d: expected assign token to be IDENT, got %s instead", p.curToken.LineNumber, p.curToken.LinePosition, name.TokenLiteral()) p.errors = append(p.errors, msg) } + p.nextToken() stmt.Value = p.parseExpression(LOWEST) return stmt diff --git a/parser/expression.go b/parser/expression.go index 893bc397..f6c737d6 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -13,7 +13,13 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { } leftExp := prefix() - for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { + if p.peekTokenIs(token.COLON) { + return leftExp + } + + for !p.peekTokenIs(token.SEMICOLON) && + !p.peekTokenIs(token.COLON) && + precedence < p.peekPrecedence() { infix := p.infixParseFns[p.peekToken.Type] if infix == nil { return leftExp diff --git a/parser/index.go b/parser/index.go index c5944db6..24f0424c 100644 --- a/parser/index.go +++ b/parser/index.go @@ -9,8 +9,19 @@ func (p *Parser) parseIndex(left ast.Expression) ast.Expression { exp := &ast.Index{Token: p.curToken, Left: left} p.nextToken() + + if p.curTokenIs(token.COLON) { + exp.Colon = "left" + p.nextToken() + } + exp.Index = p.parseExpression(LOWEST) + if p.peekTokenIs(token.COLON) { + exp.Colon = "right" + p.nextToken() + } + if !p.expectPeek(token.RBRACKET) { return nil } diff --git a/tests/indexables.expected b/tests/indexables.expected new file mode 100644 index 00000000..9deb9869 --- /dev/null +++ b/tests/indexables.expected @@ -0,0 +1,19 @@ +"c" +"e" +"ab" +"abcd" +"cdef" +"ef" +"abCdEf" +3 +4 +[1, 2] +[1, 2, 3] +[3, 4, 5] +[4, 5] +[1, 2, 8, 9, 5] +1 +true +3 +"moo" +true diff --git a/tests/indexables.rl b/tests/indexables.rl new file mode 100644 index 00000000..d195b17a --- /dev/null +++ b/tests/indexables.rl @@ -0,0 +1,32 @@ +s = "abcdef" +puts(s[2]) +puts(s[-2]) +puts(s[:2]) +puts(s[:-2]) +puts(s[2:]) +puts(s[-2:]) + +s[2] = "C" +s[-2] = "E" +puts(s) + +a = [1, 2, 3, 4, 5] +puts(a[2]) +puts(a[-2]) +puts(a[:2]) +puts(a[:-2]) +puts(a[2:]) +puts(a[-2:]) + +a[2] = 8 +a[-2] = 9 +puts(a) + +h = {"a": 1, 2: true} +puts(h["a"]) +puts(h[2]) +h["a"] = 3 +h["b"] = "moo" +puts(h["a"]) +puts(h["b"]) +puts(h[2])