Repository: abs-lang/abs
Pull request #250 (2019-07-27 06:46:21)

Tracking PR for 1.6.x, here we go!
New minor branch for ABS :) this is the PR that tracks everything
going into `1.6`.

/cc @abs-lang/contributors 


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 58fdbef..2b9b32b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ repository.
 
 Since we follow [semver](https://semver.org/),
 when a new feature is released we don't backport it but simply
-create a new version branch, such as `1.5.x`. Bugs, instead,
+create a new version branch, such as `1.6.x`. Bugs, instead,
 might be backported from `1.1.0` to, for example, `1.0.x` and we
 will have a new [release](https://github.com/abs-lang/abs/releases),
 say `1.0.1` for the `1.0.x` version branch.
diff --git a/README.md b/README.md
index fc17ef2..adca001 100644
--- a/README.md
+++ b/README.md
@@ -24,12 +24,12 @@
 
 # The ABS programming language
 
-ABS is a scripting language that works best when you're scripting on
+ABS is a programming language that works best when you're scripting on
 your terminal. It tries to combine the elegance of languages
 such as Python, or Ruby, to the convenience of Bash.
 
 ``` bash
-tz = $(cat /etc/timezone);
+tz = `cat /etc/timezone`
 continent, city = tz.split("/")
 
 echo("Best city in the world?")
@@ -73,7 +73,7 @@ And here's how you could write the same code in ABS:
 
 ``` bash
 # Simple program that fetches your IP and sums it up
-res = $(curl -s 'https://api.ipify.org?format=json');
+res = `curl -s 'https://api.ipify.org?format=json'`
 
 if !res.ok {
   echo("An error occurred: %s", res)
diff --git a/ast/ast.go b/ast/ast.go
index 207fdc4..09023fd 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -492,10 +492,18 @@ func (al *ArrayLiteral) String() string {
 	return out.String()
 }
 
+// IndexExpression allows accessing a single index, or a range,
+// over a string or an array.
+//
+// array[1:10]	-> left[index:end]
+// array[1] 	-> left[index]
+// string[1] 	-> left[index]
 type IndexExpression struct {
-	Token token.Token // The [ token
-	Left  Expression
-	Index Expression
+	Token   token.Token // The [ token
+	Left    Expression  // the argument on which the index is access eg array of array[1]
+	Index   Expression  // the left-most index eg. 1 in array[1] or array[1:10]
+	IsRange bool        // whether the expression is a range (1:10)
+	End     Expression  // the end of the range, if the expression is a range
 }
 
 func (ie *IndexExpression) expressionNode()      {}
@@ -506,7 +514,13 @@ func (ie *IndexExpression) String() string {
 	out.WriteString("(")
 	out.WriteString(ie.Left.String())
 	out.WriteString("[")
-	out.WriteString(ie.Index.String())
+
+	if ie.IsRange {
+		out.WriteString(ie.Index.String() + ":" + ie.End.String())
+	} else {
+		out.WriteString(ie.Index.String())
+	}
+
 	out.WriteString("])")
 
 	return out.String()
diff --git a/docs/README.md b/docs/README.md
index 162b832..515c2f7 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -21,12 +21,12 @@
   <img href="https://github.com/abs-lang/abs" alt="undefined" src="https://img.shields.io/github/stars/abs-lang/abs.svg?style=social">
 </p>
 
-ABS is a scripting language that works best when you're scripting on
+ABS is a programming language that works best when you're scripting on
 your terminal. It tries to combine the elegance of languages
 such as Python, or Ruby, to the convenience of Bash.
 
 ``` bash
-tz = $(cat /etc/timezone);
+tz = `cat /etc/timezone`;
 continent, city = tz.split("/")
 
 echo("Best city in the world?")
@@ -48,14 +48,14 @@ in Bash:
 
 ``` bash
 # Simple program that fetches your IP and sums it up
-RES=$(curl -s 'https://api.ipify.org?format=json' || "ERR")
+RES=`curl -s 'https://api.ipify.org?format=json' || "ERR"`
 
 if [ "$RES" = "ERR" ]; then
     echo "An error occurred"
     exit 1
 fi
 
-IP=$(echo $RES | jq -r ".ip")
+IP=`echo $RES | jq -r ".ip"`
 IFS=. read first second third fourth <<EOF
 ${IP##*-}
 EOF
@@ -70,7 +70,7 @@ And here's how you could write the same code in ABS:
 
 ``` bash
 # Simple program that fetches your IP and sums it up
-res = $(curl -s 'https://api.ipify.org?format=json');
+res = `curl -s 'https://api.ipify.org?format=json'`
 
 if !res.ok {
   echo("An error occurred: %s", res)
diff --git a/docs/installer.sh b/docs/installer.sh
index a02363b..64f793c 100644
--- a/docs/installer.sh
+++ b/docs/installer.sh
@@ -27,7 +27,7 @@ if [ "${MACHINE_TYPE}" = 'x86_64' ]; then
   ARCH="amd64"
 fi
 
-VERSION=1.5.1
+VERSION=1.6.0
 
 echo "Trying to detect the details of your architecture."
 echo ""
diff --git a/docs/introduction/how-to-run-abs-code.md b/docs/introduction/how-to-run-abs-code.md
index 9776ad6..7c97b02 100644
--- a/docs/introduction/how-to-run-abs-code.md
+++ b/docs/introduction/how-to-run-abs-code.md
@@ -61,7 +61,7 @@ the fly:
 $ abs
 Hello there, welcome to the ABS programming language!
 Type 'quit' when you're done, 'help' if you get lost!
-⧐  ip = $(curl icanhazip.com)
+⧐  ip = `curl icanhazip.com`
 ⧐  ip.ok
 true
 ⧐  ip()
diff --git a/docs/introduction/why-another-scripting-language.md b/docs/introduction/why-another-scripting-language.md
index d35f090..7fe2c9e 100644
--- a/docs/introduction/why-another-scripting-language.md
+++ b/docs/introduction/why-another-scripting-language.md
@@ -44,7 +44,7 @@ call the API of nba.com in order to retrieve the stats for
 one of last year's NBA games:
 
 ``` bash
-r = $(curl "http://data.nba.net/prod/v1/20170201/0021600732_boxscore.json" -H 'DNT: 1' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36' -H 'Accept: */*' -H 'Referer: http://stats.nba.com/' -H 'Connection: keep-alive' --compressed);
+r = `curl "http://data.nba.net/prod/v1/20170201/0021600732_boxscore.json" -H 'DNT: 1' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36' -H 'Accept: */*' -H 'Referer: http://stats.nba.com/' -H 'Connection: keep-alive' --compressed`;
 
 if !r.ok {
     echo("Could not fetch game data. Bummer!")
@@ -79,7 +79,7 @@ A sneak-peek at some of the things ABS can elegantly do:
 
 ``` bash
 # Unix pipes work
-ip = $(curl icanhazip.com | tr -d '\n')
+ip = `curl icanhazip.com | tr -d '\n'`
 
 # We now have a string -> "10.10.10.12"
 echo(ip)
diff --git a/docs/misc/error.md b/docs/misc/error.md
index a1e4909..4876136 100644
--- a/docs/misc/error.md
+++ b/docs/misc/error.md
@@ -21,10 +21,10 @@ $ cat examples/error-parse.abs
 m.a = 'abc'
 
 # this is a command terminated with a semi
-d/d = $(command);
+d/d = `command`;
 
 # this is a command terminated with a LF
-c/c = $(command)
+c/c = `command`
 
 # this is a bad infix operator
 b %% c
@@ -34,9 +34,9 @@ $ abs examples/error-parse.abs
 	no prefix parse function for '=' found
 	[4:5]	m.a = 'abc'
 	no prefix parse function for '=' found
-	[7:5]	d/d = $(command);
+	[7:5]	d/d = `command`;
 	no prefix parse function for '=' found
-	[10:5]	c/c = $(command)
+	[10:5]	c/c = `command`
 	no prefix parse function for '%' found
 	[13:4]	b %% c
 
diff --git a/docs/misc/technical-details.md b/docs/misc/technical-details.md
index a3689c4..8e90aad 100644
--- a/docs/misc/technical-details.md
+++ b/docs/misc/technical-details.md
@@ -37,9 +37,9 @@ tests/test-parser.abs
 	no prefix parse function for '=' found
 	[4:5]	m.a = 'abc'
 	no prefix parse function for '=' found
-	[7:5]	d/d = $(command);
+	[7:5]	d/d = `command`;
 	no prefix parse function for '=' found
-	[10:5]	c/c = $(command)
+	[10:5]	c/c = `command`
 	no prefix parse function for '%' found
 	[13:4]	b %% c
 	no prefix parse function for '&&' found
@@ -205,7 +205,7 @@ ERROR: type mismatch: NULL + NUMBER
 
 ## Roadmap
 
-We're currently working on [1.6](https://github.com/abs-lang/abs/milestone/13).
+We're currently working on [1.7](https://github.com/abs-lang/abs/milestone/14).
 
 ## Next
 
diff --git a/docs/syntax/return.md b/docs/syntax/return.md
index e342259..3b9ada5 100644
--- a/docs/syntax/return.md
+++ b/docs/syntax/return.md
@@ -20,6 +20,14 @@ func = f(x) {
 func(9) # 10
 ```
 
+The default value of a `return` is `null`:
+
+```
+if x {
+    return # null
+}
+```
+
 ## Next
 
 That's about it for this section!
diff --git a/docs/syntax/system-commands.md b/docs/syntax/system-commands.md
index 89ee8b5..2ec36a9 100644
--- a/docs/syntax/system-commands.md
+++ b/docs/syntax/system-commands.md
@@ -4,11 +4,10 @@ Executing system commands is one of the most important features
 of ABS, as it allows the mixing of conveniency of the shell with
 the syntax of a modern programming language.
 
-Commands are executed either with `$(command)` or `` `command` ``,
+Commands are executed with the `` `command` `` syntax,
 which resemble Bash's syntax to execute commands in a subshell:
 
 ``` bash
-date = $(date) # "Sun Apr 1 04:30:59 +01 1995"
 date = `date` # "Sun Apr 1 04:30:59 +01 1995"
 ```
 
@@ -18,7 +17,7 @@ encounter an error, the same string would hold the error
 message:
 
 ``` bash
-date = $(dat) # "bash: dat: command not found"
+date = `dat` # "bash: dat: command not found"
 ```
 
 It would be fairly painful to have to parse strings
@@ -27,60 +26,11 @@ in ABS, the returned string has a special property `ok` that
 checks whether the command was successful:
 
 ``` bash
-ls = $(ls -la)
-
-if ls.ok {
-    echo("hello world")
-}
-
-# or
-
 if `ls -la`.ok {
     echo("hello world")
 }
 ```
-It is also possible to execute a shell command without capturing its
-input or output using the `exec(command)` function. This allows long running
-or interactive programs to be run using the terminal's Standard IO
-(stdin, stdout, stderr). For example:
-```bash
-exec("sudo visudo")
-```
-would open the default text editor in super user mode on the /etc/sudoers file.
 
-Unlike the normal backtick command execution syntax above,
-the `exec(command)` function call does not return a result string unless it fails. 
-Therefore, the `exec(command)` may be the last command executed in a script
-file leaving the executed command in charge of the terminal IO until it
-terminates.  
-
-For example, an ABS script might be used to marshall the command line args
-for an interactive program such as the nano editor:
-
-``` bash
-$ cat abs/tests/test-exec.abs
-# marshall the args for the nano editor
-# if the filename is not given in the args, prompt for it
-# if the file is located outside the user's home dir, invoke sudo nano filename
-
-cmd = 'nano'
-filename = arg(2)
-homedir = env("HOME")
-
-while filename == '' {
-    echo("Please enter file name for %s: ", cmd)
-    filename = stdin()
-}
-
-if filename.prefix('~/') || filename.prefix(homedir) {
-    sudo = ''
-} else {
-    sudo = 'sudo'
-}
-
-# execute the command with live stdIO
-exec("$sudo $cmd $filename")
-```
 ## Executing commands in background
 
 Sometimes you might want to execute a command in
@@ -155,13 +105,21 @@ and if you need `$` literals in your command, you
 simply need to escape them with a `\`:
 
 ``` bash
-$(echo $PWD) # "" since the ABS variable PWD doesn't exist
-$(echo \$PWD) # "/go/src/github.com/abs-lang/abs"
+`echo $PWD` # "" since the ABS variable PWD doesn't exist
+`echo \$PWD` # "/go/src/github.com/abs-lang/abs"
 ```
 
-## Limitations
+## Alternative $() syntax
+
+Even though the use of backticks is the standard recommended
+way to run system commands, for the ease of embedding ABS also
+allows you to use the `$(command)` syntax:
+
+```
+$(basename $(dirname "/tmp/make/life/easy")) // "easy"
+```
 
-Currently, commands that use the `$()` syntax need to be
+Commands that use the `$()` syntax need to be
 on their own line, meaning that you will not
 be able to have additional code on the same line.
 This will throw an error:
@@ -170,10 +128,56 @@ This will throw an error:
 $(sleep 10); echo("hello world")
 ```
 
-Note that this is currently a limitation that will likely
-be removed in the future (see [#41](https://github.com/abs-lang/abs/issues/41)).
+## Executing commands without capturing I/O
+
+It is also possible to execute a shell command without capturing its
+input or output using the `exec(command)` function. This allows long running
+or interactive programs to be run using the terminal's Standard IO
+(stdin, stdout, stderr). For example:
+
+```bash
+exec("sudo visudo")
+```
+
+would open the default text editor in super user mode on the /etc/sudoers file.
+
+Unlike the normal backtick command execution syntax above,
+the `exec(command)` function call does not return a result string unless it fails. 
+Therefore, the `exec(command)` may be the last command executed in a script
+file leaving the executed command in charge of the terminal IO until it
+terminates.  
+
+For example, an ABS script might be used to marshall the command line args
+for an interactive program such as the nano editor:
+
+``` bash
+$ cat abs/tests/test-exec.abs
+# marshall the args for the nano editor
+# if the filename is not given in the args, prompt for it
+# if the file is located outside the user's home dir, invoke sudo nano filename
+
+cmd = 'nano'
+filename = arg(2)
+homedir = env("HOME")
+
+while filename == '' {
+    echo("Please enter file name for %s: ", cmd)
+    filename = stdin()
+}
+
+if filename.prefix('~/') || filename.prefix(homedir) {
+    sudo = ''
+} else {
+    sudo = 'sudo'
+}
+
+# execute the command with live stdIO
+exec("$sudo $cmd $filename")
+```
+
+## Limitations
 
-Also note that, currently, the implementation of system commands
+Note that the implementation of system commands
 requires the `bash` executable to [be available on the system](https://github.com/abs-lang/abs/blob/5b5b0abf3115a5dd4dfe8485501f8765985ad0db/evaluator/evaluator.go#L696-L722).
 On Windows, commands are executed through [cmd.exe](https://github.com/abs-lang/abs/blob/ee793641be09ad8572c3e913fef8468f69b0c0a2/evaluator/evaluator.go#L1101-L1103).
 Future work will make it possible to select which shell to use,
diff --git a/docs/types/array.md b/docs/types/array.md
index a653910..88cb9e8 100644
--- a/docs/types/array.md
+++ b/docs/types/array.md
@@ -24,6 +24,30 @@ array[3]
 
 Accessing an index that does not exist returns null.
 
+You can also access a range of indexes with the `[start:end]` notation:
+
+``` bash
+array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+
+array[0:2] // [0, 1, 2]
+```
+
+where `start` is the starting position in the array, and `end` is
+the ending one. If `start` is not specified, it is assumed to be 0,
+and if `end` is omitted it is assumed to be the last index in the
+array:
+
+``` bash
+array[:2] // [0, 1, 2]
+array[7:] // [7, 8, 9]
+```
+
+If `end` is negative, it will be converted to `length of array - end`:
+
+``` bash
+array[:-3] // [0, 1, 2, 3, 4, 5, 6]
+```
+
 To concatenate arrays, "sum" them:
 
 ``` bash
diff --git a/docs/types/builtin-function.md b/docs/types/builtin-function.md
index 56e1148..45c7b0d 100644
--- a/docs/types/builtin-function.md
+++ b/docs/types/builtin-function.md
@@ -243,7 +243,7 @@ $ cat ~/.absrc
 source("~/abs/lib/library.abs")
 
 $ abs
-Hello user, welcome to the ABS (1.5.1) programming language!
+Hello user, welcome to the ABS (1.6.0) programming language!
 Type 'quit' when you are done, 'help' if you get lost!
 ⧐ adder(1, 2)
 3
diff --git a/docs/types/string.md b/docs/types/string.md
index 886bce5..606e90a 100644
--- a/docs/types/string.md
+++ b/docs/types/string.md
@@ -34,6 +34,27 @@ with the index notation:
 
 Accessing an index that does not exist returns null.
 
+You can also access a range of the string with the `[start:end]` notation:
+
+``` bash
+"string"[0:3] // "str"
+```
+
+where `start` is the starting position in the array, and `end` is
+the ending one. If `start` is not specified, it is assumed to be 0,
+and if `end` is omitted it is assumed to be the character in the string:
+
+``` bash
+"string"[0:3] // "str"
+"string"[1:] // "tring"
+```
+
+If `end` is negative, it will be converted to `length of string - end`:
+
+``` bash
+"string"[0:-1] // "strin"
+```
+
 To concatenate strings, "sum" them:
 
 ``` bash
@@ -383,6 +404,10 @@ Returns the last index at which `str` is found:
 
 ### slice(start, end)
 
+> This function is deprecated and might be removed in future versions.
+>
+> Use the index notation instead: `"string"[0, 3]`
+
 Returns a portion of the string, from `start` to `end`:
 
 ``` bash
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index 8a582c3..5eb6821 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -204,15 +204,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
 		return &object.Array{Token: node.Token, Elements: elements}
 
 	case *ast.IndexExpression:
-		left := Eval(node.Left, env)
-		if isError(left) {
-			return left
-		}
-		index := Eval(node.Index, env)
-		if isError(index) {
-			return index
-		}
-		return evalIndexExpression(node.Token, left, index)
+		return evalIndexExpression(node, env)
 
 	case *ast.HashLiteral:
 		return evalHashLiteral(node, env)
@@ -1072,23 +1064,68 @@ func unwrapReturnValue(obj object.Object) object.Object {
 	return obj
 }
 
-func evalIndexExpression(tok token.Token, left, index object.Object) object.Object {
+func evalIndexExpression(node *ast.IndexExpression, env *object.Environment) object.Object {
+	tok := node.Token
+	left := Eval(node.Left, env)
+	if isError(left) {
+		return left
+	}
+	index := Eval(node.Index, env)
+	if isError(index) {
+		return index
+	}
+	end := Eval(node.End, env)
+	if isError(end) {
+		return end
+	}
+
 	switch {
 	case left.Type() == object.ARRAY_OBJ && index.Type() == object.NUMBER_OBJ:
-		return evalArrayIndexExpression(left, index)
+		return evalArrayIndexExpression(tok, left, index, end, node.IsRange)
 	case left.Type() == object.HASH_OBJ && index.Type() == object.STRING_OBJ:
 		return evalHashIndexExpression(tok, left, index)
 	case left.Type() == object.STRING_OBJ && index.Type() == object.NUMBER_OBJ:
-		return evalStringIndexExpression(tok, left, index)
+		return evalStringIndexExpression(tok, left, index, end, node.IsRange)
 	default:
 		return newError(tok, "index operator not supported: %s on %s", index.Inspect(), left.Type())
 	}
 }
 
-func evalStringIndexExpression(tok token.Token, array, index object.Object) object.Object {
+func evalStringIndexExpression(tok token.Token, array, index object.Object, end object.Object, isRange bool) object.Object {
 	stringObject := array.(*object.String)
 	idx := index.(*object.Number).Int()
-	max := len(stringObject.Value) - 1
+	max := len(stringObject.Value)
+
+	if isRange {
+		// A range's minimum value is 0
+		if idx < 0 {
+			idx = 0
+		}
+		endIdx, ok := end.(*object.Number)
+
+		// check if the range end is a number
+		if ok {
+			// if it's lower than zero, then the end is len(x) - end,
+			// else it's the end value itself
+			if endIdx.Int() < 0 {
+				max = int(math.Max(float64(max+endIdx.Int()), 0))
+			} else if endIdx.Int() < max {
+				max = endIdx.Int()
+			}
+		} else if end != NULL {
+			// if the end index is not a number nor null, then we have an error
+			// (null would mean no end, so it's valid)
+			return newError(tok, `index ranges can only be numerical: got "%s" (type %s)`, end.Inspect(), end.Type())
+		}
+
+		// if the start is higher than the end, let's return
+		// a skeleton
+		if idx > max {
+			return &object.String{Token: tok, Value: ""}
+		}
+
+		return &object.String{Token: tok, Value: string(stringObject.Value[idx:max])}
+	}
 
 	if idx < 0 || idx > max {
 		return NULL
@@ -1097,11 +1134,42 @@ func evalStringIndexExpression(tok token.Token, array, index object.Object) obje
 	return &object.String{Token: tok, Value: string(stringObject.Value[idx])}
 }
 
-func evalArrayIndexExpression(array, index object.Object) object.Object {
+func evalArrayIndexExpression(tok token.Token, array, index object.Object, end object.Object, isRange bool) object.Object {
 	arrayObject := array.(*object.Array)
 	idx := index.(*object.Number).Int()
 	max := len(arrayObject.Elements) - 1
 
+	if isRange {
+		// A range's minimum value is 0
+		if idx < 0 {
+			idx = 0
+		}
+		endIdx, ok := end.(*object.Number)
+
+		// check if the range end is a number
+		if ok {
+			// if it's lower than zero, then the end is len(x) - end,
+			// else it's the end value itself
+			if endIdx.Int() < 0 {
+				max = int(math.Max(float64(max+endIdx.Int()), 0))
+			} else if endIdx.Int() < max {
+				max = endIdx.Int() - 1
+			}
+		} else if end != NULL {
+			// if the end index is not a number nor null, then we have an error
+			// (null would mean no end, so it's valid)
+			return newError(tok, `index ranges can only be numerical: got "%s" (type %s)`, end.Inspect(), end.Type())
+		}
+
+		// if the start is higher than the end, let's return
+		// a skeleton
+		if idx > max {
+			return &object.Array{Token: tok, Elements: []object.Object{}}
+		}
+
+		return &object.Array{Token: tok, Elements: arrayObject.Elements[idx : max+1]}
+	}
+
 	if idx < 0 || idx > max {
 		return NULL
 	}
diff --git a/examples/basic.abs b/examples/basic.abs
index 9c12299..9543edf 100644
--- a/examples/basic.abs
+++ b/examples/basic.abs
@@ -1,2 +1,2 @@
-ip = $(curl -s 'https://api.ipify.org?format=json' | jq -rj ".ip" | awk '{print "[" $1 "]"}');
+ip = `curl -s 'https://api.ipify.org?format=json' | jq -rj ".ip"`
 echo "your ip is " + ip
\ No newline at end of file
diff --git a/examples/city_selector.abs b/examples/city_selector.abs
index d07026a..e293474 100644
--- a/examples/city_selector.abs
+++ b/examples/city_selector.abs
@@ -1,4 +1,4 @@
-tz = $(cat /etc/timezone)
+tz = `cat /etc/timezone`
 cont, city = tz.split("/")
 
 echo("Best city in the world?")
diff --git a/examples/domain_checker.abs b/examples/domain_checker.abs
index e885298..afe301c 100644
--- a/examples/domain_checker.abs
+++ b/examples/domain_checker.abs
@@ -2,7 +2,7 @@
 # Check if a domain is in your hostfile
 echo("What domain are we looking for today?")
 domain = stdin()
-matches = $(cat /etc/hosts | grep $domain | wc -l)
+matches = `cat /etc/hosts | grep $domain | wc -l`
 
 if !matches.ok {
     echo("How do you even...")
diff --git a/examples/implicit_retur_value.abs b/examples/implicit_retur_value.abs
new file mode 100644
index 0000000..3f11116
--- /dev/null
+++ b/examples/implicit_retur_value.abs
@@ -0,0 +1,10 @@
+fn = f() {
+    if !flag("value") {
+        return;
+    }
+
+    return flag("value")
+}
+
+echo("Call this script with the flag --value to output it. If the flag is not passed, an implicit return will trigger and 'null' will be printed.")
+echo(fn())
\ No newline at end of file
diff --git a/examples/index_ranges.abs b/examples/index_ranges.abs
new file mode 100644
index 0000000..a2d40ef
--- /dev/null
+++ b/examples/index_ranges.abs
@@ -0,0 +1,4 @@
+str = "Hello world!"
+
+echo(str[:5]) // Hello
+echo(str.split("")[:5]) // ["H", "e", "l", "l", "o"]
\ No newline at end of file
diff --git a/examples/ip-sum.abs b/examples/ip-sum.abs
index 5b04663..5248021 100644
--- a/examples/ip-sum.abs
+++ b/examples/ip-sum.abs
@@ -1,4 +1,4 @@
-res = $(curl -s 'https://api.ipify.org?format=json');
+res = `curl -s 'https://api.ipify.org?format=json'`
 
 if !res.ok {
   echo("An error occurred: %s", res)
diff --git a/examples/ip-sum.sh b/examples/ip-sum.sh
index a5eb8c1..b873cc4 100755
--- a/examples/ip-sum.sh
+++ b/examples/ip-sum.sh
@@ -1,12 +1,12 @@
 # Simple program that fetches your IP and sums it up
-RES=$(curl -s 'https://api.ipify.org?format=json' || "ERR")
+RES=`curl -s 'https://api.ipify.org?format=json' || "ERR"`
 
 if [ "$RES" = "ERR" ]; then
     echo "An error occurred"
     exit 1
 fi
 
-IP=$(echo $RES | jq -r ".ip")
+IP=`echo $RES | jq -r ".ip"`
 IFS=. read first second third fourth <<EOF
 ${IP##*-}
 EOF
diff --git a/examples/nba-game.abs b/examples/nba-game.abs
index 6b75d43..072478a 100644
--- a/examples/nba-game.abs
+++ b/examples/nba-game.abs
@@ -1,4 +1,4 @@
-r = $(curl "http://data.nba.net/prod/v1/20170201/0021600732_boxscore.json" -H 'DNT: 1' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36' -H 'Accept: */*' -H 'Referer: http://stats.nba.com/' -H 'Connection: keep-alive' --compressed);
+r = `curl "http://data.nba.net/prod/v1/20170201/0021600732_boxscore.json" -H 'DNT: 1' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36' -H 'Accept: */*' -H 'Referer: http://stats.nba.com/' -H 'Connection: keep-alive' --compressed`
 
 if !r.ok {
     echo("Could not fetch game data. Bummer!")
diff --git a/examples/shell_interpolation.abs b/examples/shell_interpolation.abs
index 58f19d3..562c1a1 100644
--- a/examples/shell_interpolation.abs
+++ b/examples/shell_interpolation.abs
@@ -1,3 +1,3 @@
 var = "/etc"
-out = $(ls -la $var)
+out = `ls -la $var`
 echo(out)
\ No newline at end of file
diff --git a/examples/shell_stdin.abs b/examples/shell_stdin.abs
index 12d63fb..374aea2 100644
--- a/examples/shell_stdin.abs
+++ b/examples/shell_stdin.abs
@@ -1,3 +1,3 @@
 # echo hello | abs examples/shell_stdin.abs
-out = $(cat)
+out = `cat`
 echo("%s %s", out, "world!")
diff --git a/main.go b/main.go
index 2c62e11..1b2ca25 100644
--- a/main.go
+++ b/main.go
@@ -7,7 +7,7 @@ import (
 	"github.com/abs-lang/abs/repl"
 )
 
-var Version = "1.5.1"
+var Version = "1.6.0"
 
 // The ABS interpreter
 func main() {
diff --git a/object/object.go b/object/object.go
index 7c5a049..d2c2c2a 100644
--- a/object/object.go
+++ b/object/object.go
@@ -166,15 +166,15 @@ func (f *Function) Json() string { return f.Inspect() }
 //
 // So, look at this:
 //
-// cmd = $(ls -la)
+// cmd = `ls -la`
 // type(cmd) // STRING
 // cmd.ok // TRUE
 //
-// cmd = $(curlzzzzz)
+// cmd = `curlzzzzz`
 // type(cmd) // STRING
 // cmd.ok // FALSE
 //
-// cmd = $(sleep 10 &)
+// cmd = `sleep 10 &`
 // type(cmd) // STRING
 // cmd.done // FALSE
 // cmd.wait() // ...
diff --git a/parser/parser.go b/parser/parser.go
index f7017e8..c166a0b 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -210,6 +210,7 @@ func (p *Parser) ParseProgram() *ast.Program {
 		}
 		p.nextToken()
 	}
+
 	return program
 }
 
@@ -327,10 +328,20 @@ func (p *Parser) parseAssignStatement() ast.Statement {
 // return x
 func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
 	stmt := &ast.ReturnStatement{Token: p.curToken}
+	returnToken := p.curToken
 
 	p.nextToken()
 
-	stmt.ReturnValue = p.parseExpression(LOWEST)
+	// return;
+	if p.curTokenIs(token.SEMICOLON) {
+		stmt.ReturnValue = &ast.NullLiteral{Token: p.curToken}
+	} else if p.peekTokenIs(token.RBRACE) || p.peekTokenIs(token.EOF) {
+		// return
+		stmt.ReturnValue = &ast.NullLiteral{Token: returnToken}
+	} else {
+		// return xyz
+		stmt.ReturnValue = p.parseExpression(LOWEST)
+	}
 
 	if p.peekTokenIs(token.SEMICOLON) {
 		p.nextToken()
@@ -804,12 +815,29 @@ func (p *Parser) ParseArrayLiteral() ast.Expression {
 	return array
 }
 
-// some["thing"]
+// some["thing"] or some[1:10]
 func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
 	exp := &ast.IndexExpression{Token: p.curToken, Left: left}
 
-	p.nextToken()
-	exp.Index = p.parseExpression(LOWEST)
+	if p.peekTokenIs(token.COLON) {
+		exp.Index = &ast.NumberLiteral{Value: 0, Token: token.Token{Type: token.NUMBER, Position: 0, Literal: "0"}}
+		exp.IsRange = true
+	} else {
+		p.nextToken()
+		exp.Index = p.parseExpression(LOWEST)
+	}
+
+	if p.peekTokenIs(token.COLON) {
+		exp.IsRange = true
+		p.nextToken()
+
+		if p.peekTokenIs(token.RBRACKET) {
+			exp.End = nil
+		} else {
+			p.nextToken()
+			exp.End = p.parseExpression(LOWEST)
+		}
+	}
 
 	if !p.expectPeek(token.RBRACKET) {
 		return nil
diff --git a/scripts/release.abs b/scripts/release.abs
index 29311e3..90cbfb4 100644
--- a/scripts/release.abs
+++ b/scripts/release.abs
@@ -45,13 +45,13 @@ platforms = [
 ]
 
 echo("Deleting previous builds...")
-rm = $(rm -rf builds/abs-*)
+rm = `rm -rf builds/abs-*`
 
 if !rm.ok {
     return echo("error: " + rm)
 }
 
-version = $(cat ./main.go | grep "var Version")
+version = `cat ./main.go | grep "var Version"`
 version = version.slice(15, len(version) - 1)
 echo("Running builds for version %s, confirm by typing \"y\"".fmt(version))
 selection = stdin()
@@ -69,7 +69,7 @@ for platform in platforms {
     }
 
     echo("Building %s %s", goos, goarch)
-    build = $(GOOS=$goos GOARCH=$goarch go build -ldflags="-s -w" -o builds/$output_name main.go)
+    build = `GOOS=$goos GOARCH=$goarch go build -ldflags="-s -w" -o builds/$output_name main.go`
 
     if !build.ok {
         echo("error: " + build)


Test patch:
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
index 4f58f87..93a68d6 100644
--- a/evaluator/evaluator_test.go
+++ b/evaluator/evaluator_test.go
@@ -455,8 +455,11 @@ func TestWhileExpressions(t *testing.T) {
 func TestReturnStatements(t *testing.T) {
 	tests := []struct {
 		input    string
-		expected float64
+		expected interface{}
 	}{
+		{"return;", nil},
+		{"return", nil},
+		{"fn = f() { return }; fn()", nil},
 		{"return 10;", 10},
 		{"return 10; 9;", 10},
 		{"return 2 * 5; 9;", 10},
@@ -497,7 +500,14 @@ fn(10);`,
 
 	for _, tt := range tests {
 		evaluated := testEval(tt.input)
-		testNumberObject(t, evaluated, tt.expected)
+		switch tt.expected.(type) {
+		case int:
+			testNumberObject(t, evaluated, float64(tt.expected.(int)))
+		case nil:
+			testNullObject(t, evaluated)
+		default:
+			panic("should not reach here")
+		}
 	}
 }
 
@@ -1345,6 +1355,38 @@ func TestArrayIndexExpressions(t *testing.T) {
 			"[1, 2, 3][-1]",
 			nil,
 		},
+		{
+			"a = [1, 2, 3, 4, 5, 6, 7, 8, 9][1:-300]; a[0]",
+			nil,
+		},
+		{
+			"a = [1, 2, 3, 4, 5, 6, 7, 8, 9][1:4]; a[0] + a[1] + a[2]",
+			9,
+		},
+		{
+			"a = [1, 2, 3, 4, 5, 6, 7, 8, 9][1:4]; a.len()",
+			3,
+		},
+		{
+			"a = [1, 2, 3, 4, 5, 6, 7, 8, 9][200:3]; a[0]",
+			nil,
+		},
+		{
+			"a = [1, 2, 3, 4, 5, 6, 7, 8, 9][7:-1]; a[0]",
+			8,
+		},
+		{
+			"a = [1, 2, 3, 4, 5, 6, 7, 8, 9][100:]; a[0]",
+			nil,
+		},
+		{
+			"a = [1, 2, 3, 4, 5, 6, 7, 8, 9][0:100]; a[0]",
+			1,
+		},
+		{
+			"a = [1, 2, 3, 4, 5, 6, 7, 8, 9][-10:]; a[0]",
+			1,
+		},
 	}
 
 	for _, tt := range tests {
@@ -1447,15 +1489,57 @@ func TestStringIndexExpressions(t *testing.T) {
 			`"123"[1]`,
 			"2",
 		},
+		{
+			`"123"[1:]`,
+			"23",
+		},
+		{
+			`"123"[1:1]`,
+			"",
+		},
+		{
+			`"123"[:2]`,
+			"12",
+		},
+		{
+			`"123"[:-1]`,
+			"12",
+		},
+		{
+			`"123"[2:-10]`,
+			"",
+		},
+		{
+			`"123"[2:1]`,
+			"",
+		},
+		{
+			`"123"[200:]`,
+			"",
+		},
+		{
+			`"123"[0:10]`,
+			"123",
+		},
+		{
+			`"123"[-10:]`,
+			"123",
+		},
+		{
+			`"123"[-10:{}]`,
+			`index ranges can only be numerical: got "{}" (type HASH)`,
+		},
 	}
 
 	for _, tt := range tests {
 		evaluated := testEval(tt.input)
-		s, ok := tt.expected.(string)
-		if ok {
-			testStringObject(t, evaluated, s)
-		} else {
+		switch result := evaluated.(type) {
+		case *object.Null:
 			testNullObject(t, evaluated)
+		case *object.String:
+			testStringObject(t, evaluated, tt.expected.(string))
+		case *object.Error:
+			logErrorWithPosition(t, result.Message, tt.expected)
 		}
 	}
 }
diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go
index 4662613..ec6a32b 100644
--- a/lexer/lexer_test.go
+++ b/lexer/lexer_test.go
@@ -96,6 +96,7 @@ for true {
 for true {
 	continue
 }
+a[1:3]
 `
 
 	tests := []struct {
@@ -330,6 +331,12 @@ for true {
 		{token.LBRACE, "{"},
 		{token.CONTINUE, "continue"},
 		{token.RBRACE, "}"},
+		{token.IDENT, "a"},
+		{token.LBRACKET, "["},
+		{token.NUMBER, "1"},
+		{token.COLON, ":"},
+		{token.NUMBER, "3"},
+		{token.RBRACKET, "]"},
 		{token.EOF, ""},
 	}
 
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 51872ef..d53ca3c 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -56,6 +56,8 @@ func TestReturnStatements(t *testing.T) {
 		input         string
 		expectedValue interface{}
 	}{
+		{"return", nil},
+		{"return;", nil},
 		{"return 5;", 5},
 		{"return true;", true},
 		{"return foobar;", "foobar"},
@@ -1043,7 +1045,7 @@ func TestCommandParsing(t *testing.T) {
 	testCommand(t, command, "curl icanhazip.com -X POST")
 }
 
-func TestBacktickParsing(t *testing.T) {
+func TestBacktickCommandParsing(t *testing.T) {
 	input := "`curl icanhazip.com -X POST`"
 
 	l := lexer.New(input)
@@ -1352,6 +1354,87 @@ func TestParsingIndexExpressions(t *testing.T) {
 	}
 }
 
+func TestParsingIndexRangeExpressions(t *testing.T) {
+	input := "myArray[99 : 101]"
+
+	l := lexer.New(input)
+	p := New(l)
+	program := p.ParseProgram()
+	checkParserErrors(t, p)
+
+	stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
+	indexExp, ok := stmt.Expression.(*ast.IndexExpression)
+	if !ok {
+		t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression)
+	}
+
+	if !indexExp.IsRange {
+		t.Fatalf("exp is not range")
+	}
+
+	if !testIdentifier(t, indexExp.Left, "myArray") {
+		return
+	}
+
+	testNumberLiteral(t, indexExp.Index, 99)
+	testNumberLiteral(t, indexExp.End, 101)
+}
+
+func TestParsingIndexRangeWithoutStartExpressions(t *testing.T) {
+	input := "myArray[: 101]"
+
+	l := lexer.New(input)
+	p := New(l)
+	program := p.ParseProgram()
+	checkParserErrors(t, p)
+
+	stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
+	indexExp, ok := stmt.Expression.(*ast.IndexExpression)
+	if !ok {
+		t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression)
+	}
+
+	if !indexExp.IsRange {
+		t.Fatalf("exp is not range")
+	}
+
+	if !testIdentifier(t, indexExp.Left, "myArray") {
+		return
+	}
+
+	testNumberLiteral(t, indexExp.Index, 0)
+	testNumberLiteral(t, indexExp.End, 101)
+}
+
+func TestParsingIndexRangeWithoutEndExpressions(t *testing.T) {
+	input := "myArray[99 : ]"
+
+	l := lexer.New(input)
+	p := New(l)
+	program := p.ParseProgram()
+	checkParserErrors(t, p)
+
+	stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
+	indexExp, ok := stmt.Expression.(*ast.IndexExpression)
+	if !ok {
+		t.Fatalf("exp not *ast.IndexExpression. got=%T", stmt.Expression)
+	}
+
+	if !testIdentifier(t, indexExp.Left, "myArray") {
+		return
+	}
+
+	if !indexExp.IsRange {
+		t.Fatalf("exp is not range")
+	}
+
+	testNumberLiteral(t, indexExp.Index, 99)
+
+	if indexExp.End != nil {
+		t.Fatalf("range end is not nil. got=%T", indexExp.End)
+	}
+}
+
 func TestParsingProperty(t *testing.T) {
 	input := "var.prop"
 
@@ -1615,6 +1698,8 @@ func testLiteralExpression(
 		return testIdentifier(t, exp, v)
 	case bool:
 		return testBooleanLiteral(t, exp, v)
+	case nil:
+		return testNullLiteral(t, exp, v)
 	}
 	t.Errorf("type of exp not handled. got=%T", exp)
 	return false
@@ -1724,6 +1809,21 @@ func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool {
 	return true
 }
 
+func testNullLiteral(t *testing.T, exp ast.Expression, value interface{}) bool {
+	nl, ok := exp.(*ast.NullLiteral)
+	if !ok {
+		t.Errorf("exp not *ast.NullLiteral. got=%T", exp)
+		return false
+	}
+
+	if nl.TokenLiteral() != "null" {
+		t.Errorf("nl.TokenLiteral not %t. got=%s", value, nl.TokenLiteral())
+		return false
+	}
+
+	return true
+}
+
 func checkParserErrors(t *testing.T, p *Parser) {
 	errors := p.Errors()
 	if len(errors) == 0 {
diff --git a/tests/test-abs.sh b/tests/test-abs.sh
index a8513da..cd09bca 100755
--- a/tests/test-abs.sh
+++ b/tests/test-abs.sh
@@ -2,7 +2,7 @@
 # test the abs parser and eval error location
 
 DIR=~/go/src/github.com/abs-lang/abs
-ABS=$(command -v abs)
+ABS=`command -v abs`
 
 DEBUG=$1