ABS, a scripting language with a simple syntax and the convenience of bash
Tags: githunt, scripting, bash
Published: 2019-05-12 23:26:37

---
title: ABS, a scripting language with a simple syntax and the convenience of bash
published: true
description: Presenting ABS programming language
tags: #githunt #scripting #bash
---

# ABS programming language: the joy of shell scripting.

According to the words of his creator: 

> It allows the mixing of convenience of the shell with the syntax of a modern programming language.

The way i see [ABS](https://github.com/abs-lang/abs) is like a layer on top of bash. It fills that gap for when Bash feels awkward and Python (or Node) is just too much. It features a simplified C-like syntax and an easy way of calling shell commands and handle their output.

## What's good about it?
- It's writing in [go](https://golang.org/).
- There are [binaries](https://github.com/abs-lang/abs/releases) for Windows, Linux and macOS.
- Easy to learn, as in there isn't anything extraordinary in the syntax. It is very similar to many mainstream programming languages.
- The [documentation](https://www.abs-lang.org/) is good enough.

## What's the catch?

- It's new, very new. Like 5 months old at the time of this writing.

- There isn't any tool around this (yet), no fancy plugins for editors, no syntax highlight, no linters or language server protocol stuff. 

- Community is basically none existing.

## ABS & Bash side by side

This snippets are taken from the [homepage](https://www.abs-lang.org/), they show how to fetch our IP address and print the sum of its parts.

This is how you could do it using good old Bash.
```bash
# Simple program that fetches your IP and sums it up
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")
IFS=. read first second third fourth <<EOF
${IP##*-}
EOF

total=$((first + second + third + fourth))
if [ $total -gt 100 ]; then
  echo "The sum of [$IP] is a large number, $total."
fi
```

I don't do much shell scripting these days so i'm kinda lost in here. I know a little bit about what's going on but only because of the variable names and some common commands. This syntax is so different from mainstream programming languages that it feels like dark magic to many of us.

Now lets have a look at the "translated" version in ABS.

```go
res = `curl -s 'https://api.ipify.org?format=json'`

if !res.ok {
  echo("An error occurred: %s", res)
  exit(1)
}

ip = res.json().ip
total = ip.split(".").map(int).sum()
if total > 100 {
  echo("The sum of [%s] is a large number, %s.", ip, total)
}
```

Since i spend most of my time writing Javascript and PHP i feel right at home in here. Notice that is so similar to other languages that the `go` syntax highlight works fine-ish in that markdown block. 

### What's going on in there?

I'll explain the second snippet now.

Lets start at the beginning.
```go
res = `curl -s 'https://api.ipify.org?format=json'`
```

That is one way of [executing a command](https://www.abs-lang.org/syntax/system-commands) in ABS. Anything inside the backticks in considered a bash command. And this is just half of the awesomeness, the other half is in the return value of this command.

What you get from the command is like a javascript object, it has methods and properties, it also uses the dot notation to access gain access to them. Because this is a command result it has an `ok` property that lets you know if the command ended successfully or with an error, so you can do a validation like this:

```go
if !res.ok {
  echo("An error occurred: %s", res)
  exit(1)
}
```

Our old friend the `if` statement does exactly what you think, and you also have access to the [logic operators](https://www.abs-lang.org/syntax/operators) that you know and love. Inside `if` block you can see two [builtin functions](https://www.abs-lang.org/types/builtin-function) that can call common commands without a special syntax. As you might expect `echo` and `exit` do the same thing as their Bash counterpart.

And the goodness doesn't stop there. This `res` variable is a [String](https://www.abs-lang.org/types/string) so that means it has other methods, including `.json()` that can convert a string type into a [Hash](https://www.abs-lang.org/types/hash) (if is a valid json string). Once we have a Hash type that represents the http request made by `curl` we can start processing the IP value.

```go
ip = res.json().ip
total = ip.split(".").map(int).sum()
```

I want you to pay attention to this part right here: `.map(int)`, if you take a look at the [Array](https://www.abs-lang.org/types/array) type methods you'll see that `map` takes a function as its argument, this is big deal because that means that higher order functions are possible (check [this article](https://dev.to/damcosset/higher-order-functions-in-javascript-4j8b) to know why this is awesome.)

## Lets play with it

I use [cmus](https://cmus.github.io/) as my music player. It is so "lightweight" that it doesn't even show desktop notifications when it starts playing a song, but it does give you the chance to execute a program every time it does, by setting the `status_display_program` option. We'll try to do almost the same thing [this python script](https://github.com/cmus/cmus/wiki/status_diplay_notify_send.py) does.

I'll keep this simple so i will ignore a lot of things in the python version, we'll focus on the notification part. This is what i'm interested in from the python script:

```python
# See if script is being called by cmus before proceeding.
if sys.argv[1].startswith("status"):

#... moar code ...

# We only display a notification if something is playing.
if status_data("status") == "playing":

#... moar code ...

# Execute notify-send with our song data.
subprocess.call('notify-send -u low -t 500 "' + \
                 notify_summary + '" "by ' + \
                 notify_body + ' "', shell=True)
``` 

That is what i want to achieve. Our main block in ABS should look like this:

```go
if is_playing {
  summary, body = get_data(process_args())

  `notify-send "$summary" "$body"`
}
```

Our script will have more than that but it is basically what i want to do. This are the steps we are going to make.

- Figure out how we can get the status.
- Gather the rest of the arguments that cmus gives us.
- Get the important stuff.
- Send the data to the `notify-send` command.

Lets start with `is_playing`. 

If you remember in the python version we get the status from the `sys.argv` property and it checks if the arguments list start with the string 'status'. In Bash you would check the special variables `$1` or `$2` or `$3`... you get the point, each one of these hold the value of a positional argument given to the script. In ABS we have the `arg` function, you give it a number and it returns one of the arguments. The arguments lists starts at 0 and the first two are always the binary of the interpreter and the script path so we should check argument with the index 2 and 3, they will give us the status string and the status value.

We define our condition like this:

```go
is_playing = arg(2) == "status" && arg(3) == "playing"
```

Now lets get the rest of the data the cmus provides. At this point it would be nice to have a function that returns an array with the arguments list, but we don't have it. Instead, we will create a function that calls `arg` with a number until we get an empty string which will mean we have reach the end of the list. This function will store the arguments in a Hash and return it.

```go
process_args = f() {
  result = {}
  index = 4
  current_arg = arg(index)

  while current_arg != "" {
    result[current_arg] = arg(index + 1)

    index += 2
    current_arg = arg(index)
  }

  result
}
```

This is a user defined [function](https://www.abs-lang.org/types/function). There are a couple funny things in it, first is that we create a function with the "keyword" `f` and some parenthesis. The other funny thing is that we don't have a `return` keyword, instead we have lonely hash just sitting there at the end of our function. There is a `return` keyword but is only necessary when you want an early return or when the interpreter doesn't know what to return in the last statement of the function.

The last piece of the puzzle is `get_data`, this function will handle the result from `process_args` and ensure that we always get a `summary` and a `body` for our notification.

```go
get_data = f(args) {
  title  = args.title   || args.file.split("/").pop()
  artist = args.artist  || "Unknown"
  album  = args.album   || false
  
  msg = fmt("<b>Artist:</b> %s", artist)

  if album {
    msg += fmt("\n<b>Album:</b> %s", album)
  }

  return [title, msg]
}
```

We can't trust the argument that cmus gives us because most of the data that we want is in the metadata of the file and it might be missing, that's why we evaluate every property and have an "alternate" value with the `||` operator just in case. Next, we create a nice message for the body of the notification, we use the `fmt` function that is almost like `printf` in other languages. Once we have everything we need we return an array.

And so the final product would be this:

```go
process_args = f() {
  result = {}
  index = 4
  current_arg = arg(index)

  while current_arg != "" {
    result[current_arg] = arg(index + 1)

    index += 2
    current_arg = arg(index)
  }

  result
}

get_data = f(args) {
  title  = args.title   || args.file.split("/").pop()
  artist = args.artist  || "Unknown"
  album  = args.album   || false
  
  msg = fmt("<b>Artist:</b> %s", artist)

  if album {
    msg += fmt("\n<b>Album:</b> %s", album)
  }

  return [title, msg]
}

is_playing = arg(2) == "status" && arg(3) == "playing"

if is_playing {
  summary, body = get_data(process_args())

  `notify-send "$summary" "$body"`
}
```

## Still want to know more?

Be sure to visit the [author's site](https://odino.org/), the [documentation](https://www.abs-lang.org/) or the [examples directory](https://github.com/abs-lang/abs/tree/master/examples) in the github repository.

If you know `go` consider contributing to the project.
{% github abs-lang/abs %}

---

Thank you for reading. If you find this article useful and want to support my efforts, [buy me a coffee ☕](https://www.buymeacoffee.com/vonheikemen).

[![buy me a coffee](https://res.cloudinary.com/vonheikemen/image/upload/v1618466522/buy-me-coffee_ah0uzh.png)](https://www.buymeacoffee.com/vonheikemen)