Varyx (A Programming Language)


Introduction

Varyx is a general-purpose, high-level programming language in development since mid-2015.

Summary:

Future Goals:

Components

The Varyx source code is AGPL-licensed and published on GitHub.

Design Principles

Synopsis

All the following samples are valid Varyx code and currently supported.

Every value in Varyx is a list of zero or more items. Every item has a type.

Basic types

# integer
12345, -6, 27742317777372353535851937790883648493, 0xFFFD, 0b101010

# string
"", "Hello world\n", "NULs\000embedded\x00here\0"

# packed
x"", x"4E75", x"14def9dea2f79cd65812631a5cf5d3ed", b"101010"

# byte
'A', '\n', '\0', '\007', '\xFF'

# boolean
true, false

# null
null

The type of a list is simply a list of the types of each of its items.

Lists are not items, and therefore lists can’t contain lists. A list is just a group of items. What defines a group is proximity. If you place two groups together, you get one larger group.

# these are all the same value
1, 2, 3, 4, 5
(1, 2, 3, 4, 5)
(1, 2), 3, (4, 5)
(((1, 2), (3)), (4, (), 5))

Parentheses are optional and may be used for grouping in the same manner as other expressions — with one exception: The empty list can only be written with parentheses. It’s a list of zero items.

# empty list
()

Since parentheses are otherwise optional, there’s no distinction between an item and a list of that item.

# item / list of one item
"This is an item"

Containers

An array is a sequence of items.

# array
[ 1, 2, 5 ]
[ "egg", "sausage", "bacon" ]

Arrays are heterogenous, i.e. they can contain items of different types.

[ 1, 2, "red", "blue" ]
[ null, false, 0, '\0', "" ]

Arrays are themselves items, and can contain other arrays.

[ ["egg", "bacon"], ["egg", "sausage", "bacon"], ["egg", "spam"] ]

A mapping is a kind of dyad — an item that contains two other items, a key and a value. The key can’t be null.

# mapping
"one" => 1
"two" => 2
7 => "seven"
8 => "eight"
"false" => false
true => "true"

Another mapping syntax will automatically quote bareword keys as strings. It does nothing to values, or to keys that aren’t barewords.

one: 1
two: 2
7: "seven"
8: "eight"
false: "This key is the string 'false'"
(true): "This key is the value `true`"

A mapping can contain an array:

squares: [ 0, 1, 4, 9, 16, 25 ]
primes:  [ 2, 3, 5, 7, 11, 13 ]

And an array can contain mappings:

[ 1: "who", 2: "what", 3: null ]

A table is an associative array. It’s like an array of mappings, but it emphasizes finding keys and retrieving the corresponding values. To create one, you have to provide an array of mappings and additionally specify the key type.

# table
string^[ Varyx: "table", Perl: "hash", Python: "dict" ]
integer^[ 0: "stdin", 1: "stdout", 2: "stderr", 3: "/etc/motd" ]

[TODO: Replace and deprecate this syntax.]

Symbols

A symbol is a named location for a value. A symbol must be declared before it’s used. Once declared, it can be assigned a value, until which it’s undefined. Symbols are either variable (var) or constant (let / const).

# symbols
var total = 0
let combo = 12345
const url = "https://www.vcode.org/"

A constant symbol can’t be assigned a value more than once (even if the second assignment repeats the value).

# these are okay
++total
total += 10
total = 100

# this is not okay
combo = 12345

Containers store elements by value, so you can’t assign to an element of a container which is stored in a constant symbol.

let counting = [1, 2, 3]

# this is right out
counting[ 2 ] = 5

However, the undefined state is not a value, so it’s possible to declare a constant in one place and define it in another.

var i = 0
const foo

# i is modified here
...

foo = i

Any access of a symbol in an undefined state (other than assigning it a value) is an error.

Symbols are not items or even values, and therefore can’t be stored within containers or in other symbols. When a symbol is used in a context that requires a value, the value stored in the symbol is substituted.

Functions

Varyx has functions as first-class objects (which means you can store them in arrays, for example). Most often you’ll define one and give it a name with def:

def cube (x)
{
    return x^3
}

Following the function name is its prototype, which lists the names of the function’s parameters. The parameter values are filled in by the arguments passed by the function’s caller.

def is_pythagorean_triple (a, b, c)
{
    return a^2 + b^2 == c^2
}

# prints "true"
print is_pythagorean_triple( 3, 4, 5 )

You can return a function from another function:

def get_handler (c)
{
    def add (a, b) { return a + b }
    def sub (a, b) { return a - b }
    def mul (a, b) { return a * b }
    def div (a, b) { return a / b }

    let ops = byte^
    [
        '+': add,
        '-': sub,
        '*': mul,
        '/': div,
    ]

    return ops[ c ]
}

# prints "7"
print get_handler( '+' )( 3, 4 )

Functions are closures, which means they can access variables defined in an outer function, even after it’s exited:

def make_counter
{
    var i = 0

    def counter
    {
        return ++i
    }

    return counter
}

let count = make_counter()

# Prints 1, 2, and 3 on separate lines
print count()
print count()
print count()

# Prints 1 again
print make_counter()()

# Prints 4
print count()

Function prototypes can be omitted when you don’t need to look at the arguments. An anonymous block is a simpler form of closure. We could have used one above:

def make_counter
{
    var i = 0

    return { ++i }
}

You can alter the flow of control with branching and looping structures. Here’s branching with if/then:

def th (n)
{
    let ones = abs n % 10

    if ones > 3 then
    {
        return "th"
    }

    return ["th", "st", "nd", "rd"][ ones ]
}

def ordinal (n)
{
    return string( n, th(n) )
}

# returns "42nd"
ordinal( 42 )

And here’s looping with for. It declares a symbol which you can’t modify, but its value changes to each member of the sequence in turn.

for x in ["bacon", "egg", "spam", "sausage"] do
{
    if x == "spam" then
    {
        print "That’s got spam in it!"
        break
    }
}