Steve's poplog primer - GetPoplog/Seed GitHub Wiki

For people who already know programming you can make a few framing statements like: Pop-11 is a dynamically typed language with full garbage collection, it is not object oriented but does have OOP libraries (it largely pre-dates the OO revolution), it supports full lexical binding but by default uses an older system called dynamic shallow binding, it includes an extremely powerful coroutine facility but lacks async facilities.

And then get into details like: top-level variables are normally declared with "vars" e.g.

vars x = 99;

And top-level functions (procedures) are defined with the define ... enddefine syntax. e.g.

define factorial( n ); if n <= 1 then 1 else n * factorial( n - 1 ) endif enddefine;

Conditionals are written using if or unless, the latter being a shortcut for if not( ... ). Like the majority of Pop-11 forms the opening keyword is balanced with a closing keyword i.e. endif or endunless. The "cascaded" conditional syntax is supported via elseif and elseunless, e.g.

if TEST1 then
    STATEMENTS1
elseif TEST2 then
    STATEMENTS2
elseunless TEST3 then
    STATEMENTS3
else
    STATEMENTS4
endif

Loops are always introduced with the for syntax word but there are a variety of forms:

vars i;
for i from A to B do ... endfor  ;;; A to B inclusively

Note that Pop-11 does not use 0-indexing and half-open intervals but 1-indexing with fully closed intervals.

IMPORTANT: loop variables are not automatically declared, which is a hangover of Pop-11 being an older language.

Iteration over an iterable is done with the for ... in syntax:

vars i;
for i in LIST do
    STATEMENTS
endfor

In addition to these forms Pop-11 also has a while TEST do STATEMENTS endwhile form. It also has an until ... do ... form but this is merely equivalent to while not( ..... ) do ..

And it also has a specialised form for repeating a fixed number of times:

repeat N times STATEMENTS endrepeat
And a repeat-forever form
repeat STATEMENTS endrepeat

Local variables are normally declared using the lvars syntax. e.g.

define longest( string_list ); 
    lvars s;
    lvars max_so_far = -1;
    lvars best_so_far = false;
    for s in string_list do
        lvars n = length( s );
        if n > max_so_far then
            s -> best_so_far;
            n -> max_so_far
        endif
    endfor;
    return( best_so_far )
enddefine;

lvars is mnemonic for 'lexical vars'. Lexical variables in Pop-11 are always local to some scope. Even when they are used at top-level they are local to the scope of the compilation-unit. In practical terms the compilation-unit is the same thing as a file (for advanced programmers: it's actually the scope of a sysCOMPILE). Because of this "lvars" are frequently used at top-level to define private variables.

Pop-11 supports several non-structured syntax forms: return, nextloop (like continue in C syntax), quitloop (like break in C syntax). The return keyword performs an immediate exit from the current dynamic scope, exactly the same as in other languages. But if you want to return a value it must be enclosed in parentheses: return( EXPRESSION ). Aside: if a syntax word does not have an explicit closer, it usually has mandatory parentheses. The return keyword also has conditional variants: returnif( TEST )( VALUES ) and returnunless( TEST )( VALUES ) where returnunless TEST is the same as returnif( not( TEST ) ).... The quitloop syntax is much the same as break in languages such as C/C++/Java/C#/ECMAScript/Python. However you can specify how many loops are exited by supplying a count. e.g. quitloop(2) will break out of 2 enclosing loops and not just 1. And it has variants quitif and quitunless that work as you might expect and also optionally can take a count of how many loops to break out of. And the nextloop syntax works in exactly the same way as quitloop. It is equivalent to continue in C-like languages. It has variants nextif and nextunless and also takes an optional number referring to the enclosing loops. e.g. nextloop(2) will break from the current loop and force continuation in the next loop out.

In addition, Pop-11 supports full-lexical goto. Very few programmers will be familiar with this and it is hardly ever useful. Look it up if you want extra grey hairs. (It was actually added to Pop-11 to support Common Lisp.)

Datatypes

Pop-11 has a collection of data types that is ... eclectic.

Booleans are true and false. However conditionals are only interested in false and not-false. So false plays a special role and true can be replaced by any other value.

The short-circuit conditionals and and or directly support this. So false or 3 will evaluate to 3 and not true, for example. This is a pervasive idiom in Pop-11 (which it inherited from Lisp).

Note that not is an ordinary function. It turns false to true and vice versa.

Pop-11 has unlimited precision integer and rational arithmetic (much the same as Python). It supports single and double precision floating point and complex arithmetic. (Aside: the rules are identical to those of Common Lisp)

Pop-11 also extensively uses a protocol it calls repeaters. A repeater is a procedure with signature:

<T> () -> (T | termin)

In other words a repeater of type T is a procedure that each time you call it returns an object of type T or the special value termin. termin is a unique, singleton value that is exclusively used to signal the end of a repeater. (Advanced programmers: termin is not a first-class value but is reserved for this use and no other.)

Pop-11 presents all stream based operators in terms of repeaters. For example, to read from a file F one calls discin(F) and gets back a character repeater.

Lists in Pop-11 are singly-linked lists. This is the workhorse datatype for collections. Unlike Python/Java lists, there is no list-object that you push/pull from. Instead they are used declaratively via the fundamental constructor H::T, which constructs a new 'pair' with head H and T. It is important to note that T is typically shared with other 'pairs'.

Being the workhorse datatype, lists have a lot of syntactic complication in Pop-11. The biggest complication that faces programmers from other languages is that the list construction syntax [... ] immediately enters a new syntax context. Technically this is a quasi-quoted context, where tokens literally stand for themselves, unless explicitly escaped using ^ and ^^. It is important to know that you can switch back to normal expression context using %.

So the simplest use of lists is simply

[% 1, 2, 3, 4 %]

All the items that are put on the stack between [ and ] are gathered up and turned into a linked chain of pairs. The pair that is returned will have 1 as the head and [% 2, 3, 4 %] as the tail - and so on down the chain. The chain is terminated by a value that counts as the empty list. Although Pop-11 has a unique value nil that counts as an empty list, it is not the only way to represent the empty list. When a dynamic list is exhausted the end of the list is, in fact, a specially configured pair object. As a consequence, the correct test for empty lists is null( LIST ) and not LIST == nil.

Although lists are very heavily used in AI programming, they are not appropriate when random access to members is required because getting the Nth element of a linked list takes N steps. Nor are they appropriate when large data structures are used because each pair consumes 3 long words of store, effectively triple the store that might have been expected.

Pop-11 supplies a 1D array type called a vector. Vectors are compact and fast to index. They are nearly as widely used as linked lists and have their own syntax that is very closely based on list syntax. They differ mainly in using { ... } instead of [ ... ]. Just like lists, the { bracket immediately switches to a quasi-quoted context.

Perhaps shockingly to Python programmers, Pop-11 vectors do not support adding or removing any members. Nor is there a built-in type that does so. Why is this? This is an advanced topic. But the short story is that the need for imperative data structures is much reduced because of the pervasive use of the open-stack. In addition, Pop-11 predates the OO revolution and so the switch away from functional programming never happened.

⚠️ **GitHub.com Fallback** ⚠️