Syntax Overview

General Prog syntax overview.

Introduction

Following is a general syntax overview of Prog, designed to present a quick reference for the overall look and feel of Prog as it develops. Syntax is the least important feature of a language from a design standpoint, but from a usability standpoint it's huge, so naturally it's a point of contention among programmers. Making a syntax that's at once expressive and accessible is one of the key challenges of language design.

Table of Contents

General

Everything is an expression. Every expression has a type. The goal of the Prog runtime is to convert an expression of some type to a value of some other type, performing side-effects specified by operators along the way. In this sense, evaluation is a side-effect of typing, and not, as is typical, the other way around. Built-in functions and flow-control statements are actually operators.

Semicolons

The semicolon operator (;) has the lowest precedence of any operator. It is used to separate expressions, not terminate them, so a semicolon is unnecessary at the end of a block or file. It evaluates its left operand and tests whether the result is an uncaught exception. If it is, the exception is rethrown to the enclosing scope. If no exception was thrown, the result is discarded and the right operand is returned unevaluated.

ends_with_a_semicolon();
does_not_have_to()

Blocks

Blocks are delimited with curly braces ({}). The result of a block is an anonymous function object of type code, which can be assigned to an identifier or composed with other functions. See Functions.

{ "This is a string in a block." }

Comments

There are two kinds of comments: single-line and multi-line. Single-line comments begin with a hash (#) and continue to the next newline. Multi-line comments begin with a hash-colon pair (#:) and end with a colon-hash pair (:#) and can span multiple lines. To offset a comment for documentation, use double hashes (##) for single-line and double colons (#::...:#) for multi-line comments.

# Single-line comment.

#:
    Multi-line
    comment.
:#

## Single-line documentation comment.

#::
 : Multi-line
 : documentation
 : comment.
 :#

Grouping

Parentheses (()) are used for grouping and function invocation. Square brackets ([]) are used for indexing in arrays and associative containers.

w = 2 * (3 + 5);
x = (1, 2, 3);
y = x[0];
z = x[-1];
function(param1, param2);

foo["bar"] = "baz";
thing[(complex_key, of_weird => type)] = value;

Idiomatic

The following structures appear commonly in idiomatic Prog code.

Contexts

The context selection operator (as) is used to select the context of an expression. If the expression is a value, as performs a conversion, if one is possible. If the expression is not just a value, as performs a selection, if one is possible, or falls back on conversion.

What this means is that the programmer controls what the expected type of an expression is and can account for that. Among many other things, this lets you overload a function based on its return type alone:

def value() => string {
    return "Pi"
};
def value() => int {
    return 3;
};
def value() => float {
    return 3.14;
};

output << "' value() as string ', ' value() as int ', ' value() as float '\n";

Templates

Templates are commonplace in Prog. Prog forgoes the "traditional" (C++) template syntax (template<param>) in favour of a template instantiation operator (:), which is easier to parse, easier to read, and fits better with Prog's notion of templates.

A template is, quel surprise, a template for producing things from other things. If the arguments to the instantiation operator are static, then instantiation is done at compile time and the instance is statically typed; otherwise, instantiation is deferred until runtime and the instance is dynamically typed.

Because of how Prog's type system works, there is no difference between template instantiation and function application. The only reason there's an instantiation operator at all is to disambiguate construction (vector(int), construct a vector of type objects containing one copy of int) from instantiation (vector:int, the type of a vector of int values).

Templates typically produce type instances, but there's nothing to say that a template can't produce values of another type:

def fraction(num as int, denom as int) => real {
    return num / denom as real;
};

f = fraction:(1, 3);       # @f == static real
i = something() as int;    # @i == dynamic int
g = fraction:(1, i);       # @g == dynamic real

Context selection and template instantiation are deeply intertwingled, though neither is necessarily deep magic.

Ranges

Ranges are used for many things in Prog, from string and container splicing to looping and iteration to searching. Ranges are created with the unary relational operators (<, >, <=, >=, ==, and !=) and the range operators (.. and ...). To obtain the union, intersection, or difference of two ranges, a range and a set, or a range and a value, use the union (|), intersection (&), or difference (^) operators, respectively. See Ranges.

adult = (>=18);
if (age & adult) output << "Adult.\n";

teen = (13 .. 19);
if (age & teen) output << "Teenager.\n";

young = <10;
young_at_heart = young | >80;

people = (13, 26, 59);
under_50 = people & <50;
target_demographic = people & (>=18 | <=35);

students = ("Turing", "Hopper", "Stroustrup", "Wirth", "Wall", "Van Rossum", "Matsumoto");
group_1 = students & <"N";
group_2 = students & >="N";

for (99 .. 1) output << "Beer.\n";

Radioactive Types

Some types like to "decay" into other types. Any operation on a value of a radioactive type R results in an R containing a closure describing the operation. When the R must produce a value, the operations defined by the chain of closures are performed in sequence according to the semantics of R.

each is a very common radioactive type used to perform operations on each element of a sequence of some type T. The each itself only exists while the expression is being evaluated, after which it decays back into a T.