Angort

Angort1 is a stack-based concatenative programming language, based on the venerable Forth, with some extra features. These include:

  • variables and function parameters;
  • higher order functions with (nearly) full lexical closure;
  • lists and hashes (dictionaries) with garbage collection;
  • native C++ plugin architecture.

It’s available at Github.

Why?

Angort was initially written out of curiosity, driven by my need to understand how high-level constructions such as garbage-collected values and closures worked down at the machine code level. As a veteran assembler and C/C++ programmer, I found languages like Python and even Java difficult to “trust”, in a sense, without this understanding.

My first attempt was a language typical of the Algol lineage, Lana. While successful, Lana wasn’t particularly interesting and so I abandoned it. Later, I found I needed a control language for a robotics project: the ExoMars rover locomotion prototype, Blodwen. The Blodwen control system used a fairly complex C++ API, which was appropriate for many tasks but not for ad-hoc control or experiment scripting. My initial instinct was (as always) to quickly write an interpreter for a Forth-like language.

As the project progressed, I found the language an interesting platform for investigating the high-level features mentioned above. Notably, my final year project required a subsumption system a la Brooks, and I found this much easier to write in the new language than in C++. Prompted by this discovery I continued to work with and add features to the language over the next year, and was surprised by how powerful a stack-based language with anonymous functions and collections could be. As I started my Ph.D. I found working with Angort a natural way to script experiments, particularly once I found a good set of paradigms for interfacing with C++ code, and so continued working with it.

Angort combines the power and ease of a modern dynamic language with the convenience of a Forth-like language, and has been used in various applications including:

  • building command/control/monitoring environments for robotic systems;
  • scripting experiment runs for neural networks;
  • writing fairly complex data analysis tools;
  • generating visualisations of neural networks and other data;
  • algorithmically generated music;
  • simple 2D games.

Brief examples

First, the familiar recursive quicksort algorithm:

# Colon at the start of a line introduces a named function definition.
# This function has one parameter "lst" and one local variable "piv".
:qs |lst:piv| 
    # if length of list is <=1...
    ?lst len 1 <= if
        # return the list itself
        ?lst 
    else 
        # otherwise remove the first item as the pivot
        ?lst pop !piv
        # quicksort the list of items less than the pivot
        ?lst (?piv <) filter qs 
        # add the pivot to that list
        [?piv] + 
        # quicksort the list of items greater than the pivot
        # and add it.
        ?lst (?piv >=) filter qs + 
        # resulting list is left on the stack, and so returned.
    then;

Angort doesn’t (yet) have tail-call optimisation so actually doing it like this is probably a bad idea.

Next, a program to read a CSV file and print the sums of all the columns:

# load the CSV plugin but do not import the symbols into the
# default namespace, so we have to access them with csv$...
`csv library drop

# load the CSV file using the "quick read" method and store it
# in the "CSV" global. This will be a list of dictionaries, each
# of which is keyed by a symbol.

"magic.log" csv$qread !CSV

# make a list of keys from the first item in CSV.
[] ?CSV fst each {i,} !Keys

# create a "slug" (an anonymous function which runs immediately,
# to provide local variables and multiline flow control)
# with a local variable i.

(|:i|
    # for each key, store the iterator value in "i" so it's
    # accessible within a closure
    ?Keys each { i!i
        # create a pair consisting of the iterator (i.e. key name)
        [i, 
         # and the sum of the key's values, done by using map/reduce:
         # the map extracts the key's values, the reduce performs the sum.
         0 ?CSV (?i swap get) map (+) reduce
         ]
        # print the pair
        "," intercalate.
    }
)@ quit # run the slug and quit

  1. The name is an entirely random pair of syllables, it has no significance. ↩︎

James Finnis
James Finnis
Lecturer in Computer Science

Research interests: artificial neuroendocrine systems, unusual neural network architectures, autonomous off-road driving, image processing.