Index Unicon

Unicon expressions

Everything in Unicon is an expression. Well just about everything, the source files are actually just text until processed, for instance.

Expressions can be chained in Unicon, and the overall multiple expression expression still counts as an expression in terms of the lexical syntax.

For example: The every reserved word is syntactically defined as

every expr1 [do expr2]

That can be

every 1 to 5 do writes(".")

Or it can be

every i := 1 to 5 do write(i)

The assignment expression, inserted in the every still counts as expr_1 for every, from a compiler syntax point of view. This is a powerfully concise feature of Unicon and almost all programs take advantage of this grouped expressions within an expression paradigm.

Expression blocks, contained within braces, also count as a single expression, in terms of the Unicon parser. Some operators, such as alternation and conjunction, and other syntax elements provide even more flexibility when forming expressions.

Success and Failure

All Unicon expressions produce a value, or fail. This is a very powerful computational paradigm. Procedures can return a full range of values, and need not worry about reserving sentinel values for error codes or out-of-band results. For errors, or simple end-of-file status, the procedure can simply fail. When error status codes are required, variables can be set.

Procedures always produce values, unless they fail. The default value for return when no expression is given is &null. Falling off the end of a procedure without a return or suspend signals failure.

# default-return-value.icn, default expression for return, is &null
procedure main()

# this actually returns &null, not void/nothing
procedure subproc()


prompt$ unicon -s default-return-value.icn -x

There is no practical use for the concept of void in Unicon. But fear not, the foreign function interface layer loadfunc, can include wrappers that manage the C concept of void returns and transform things to usable Unicon values, when necessary. &null is not void. The closest thing to void in Unicon is failure.

Failure propagation

With goal-directed evaluation, Unicon expressions always strive to produce a result for surrounding expressions. Failure will propagate outwards when no result can be produced. When an inner expression fails, a surrounding expression will attempt any possible alternates, until it either succeeds or must also fail. This propagation will continue until a Bound Expressions marker terminates any goal-directed backtracking.


The null value, represented by &null is the default value for unset variables, omitted arguments, and is specially treated in expressions.

Many expressions will cause a runtime error when a null value is dereferenced as part of a computation.

# null value runtime error
procedure main()
    a := b + c


The sample above abends with a runtime error when the null values of b and c are used in a calculation. See &error for ways of controlling how Unicon handles runtime errors. Runtime errors can be converted to expression failure, allowing programmer control of abend states.

prompt$ unicon -s runtime-null.icn -x

Run-time error 102
File runtime-null.icn; Line 12
numeric expected
offending value: &null
   {&null + &null} from line 12 in runtime-null.icn

Through this document, null, and &null are used interchangeably, null is the value, represented by the keyword &null.


It is very much worthwhile getting used to the precedence rules with Unicon, It is not as tricky as it may first seem, but the level of complexity of some Unicon expressions will be easier to read with a sound understanding of the order of precedence rules. When in doubt, use parentheses to control the evaluation order of complex expressions.

Updated for Unicon and reformatted slightly from [1]

Shown in order of decreasing precedence. Items in groups (as separated by empty lines) have equal precedence (left to right). [2]

For instance: exponentiation ^ is higher than addition +, so

a + b ^ c

parses as

a + (b ^ c)

Left to right associativity means

a ++ b -- c

parses as

(a ++ b) -- c
High Precedence Expressions

    (expr)                             # grouping
    {expr_1;expr_2;...}                  # compound
    x(expr_1,expr_2,...)                 # process argument list
    x{expr_1,expr_2,...}                 # process co-expression list
    [expr_1,expr_2,...]                  # list
    expr.F                             # field reference
    expr_1[expr_2]                       # subscript
    expr_1[expr_2,expr_3,...]             # multiple subscript
    expr_1[expr_2:expr_3]                 # section
    expr_1[expr_2+:expr_3]                # section
    expr_1[expr_2-:expr_3]                # section

Prefix Expressions

    not expr                           # success/failure reversal
    | expr                             # repeated alternation
    ! expr                             # element generation
    * expr                             # size
    + expr                             # numeric value
    - expr                             # negative
    . expr                             # value (dereference)
    / expr                             # null
    \ expr                             # non-null
    = expr                             # match and tab
    ? expr                             # random value
    ~ expr                             # cset complement
    @ expr                             # activation (&null transmitted)
    ^ expr                             # refresh
    .> expr                            # pattern cursor position assign

Infix (some postfix Unicon 13) Expressions

    expr_2 \ expr_1                      # limitation [2]
    expr_1 @ expr_2                      # transmission
    expr_1 ! expr_2                      # invocation
    expr_1 >@ expr_2                     # send to other out-box
    expr_1 >>@ expr_2                    # blocking send to other out-box
    expr_1 <@ expr_2                     # receive from other out-box
    expr_1 <<@ expr_2                    # blocking receive from other out-box

    expr_1 ^ expr_2                      # exponentiation
    expr >@                            # send to default out-box
    expr >>@                           # blocking send
    expr <@                            # receive
    expr <<@                           # blocking receive

    expr_1 * expr_2                      # multiplication
    expr_1 / expr_2                      # division
    expr_1 % expr_2                      # remainder
    expr_1 ** expr_2                     # cset or set intersection

    expr_1 + expr_2                      # addition
    expr_1 - expr_2                      # subtraction
    expr_1 ++ expr_2                     # cset or set union
    expr_1 -- expr_2                     # cset or set difference
    expr_1 -> expr_2                     # conditional pattern assignment
    expr_1 => expr_2                     # immediate pattern assignment

    expr_1 || expr_2                     # string concatenation
    expr_1 ||| expr_2                    # list concatenation

    expr_1 < expr_2                      # numeric comparison
    expr_1 <= expr_2                     # numeric comparison
    expr_1 = expr_2                      # numeric comparison
    expr_1 >= expr_2                     # numeric comparison
    expr_1 > expr_2                      # numeric comparison
    expr_1 ~= expr_2                     # numeric comparison
    expr_1 << expr_2                     # string comparison
    expr_1 <<= expr_2                    # string comparison
    expr_1 == expr_2                     # string comparison
    expr_1 >>= expr_2                    # string comparison
    expr_1 >> expr_2                     # string comparison
    expr_1 ~== expr_2                    # string comparison
    expr_1 === expr_2                    # value comparison
    expr_1 ~=== expr_2                   # value comparison

    expr_1 | expr_2                      # alternation
    expr_1 || expr_2                     # pattern concatenation

    expr_1 to expr_2 by expr_3            # step wise number generation
    expr_1 .| expr_2                     # pattern alternate

    expr_1 := expr_2                     # assignment
    expr_1 <- expr_2                     # reversible assignment
    expr_1 :=: expr_2                    # exchange
    expr_1 <-> expr_2                    # reversible exchange
    expr_1 op:= expr_2                   # (augmented assignments)

    expr_1 ?? expr_2                     # pattern match

    expr_1 ? expr_2                      # string scanning

    expr_1 & expr_2                      # conjunction

Low Precedence Expressions

    break [expr]                       # break from loop
    case expr of {                     # case selection
       expr_1 : expr_2
       [default: expr_3]
    create expr                        # co-expression creation
    every expr_1 [do expr_2]             # iterate over generated values
    fail                               # failure of procedure
    if expr_1 then expr_2 [else expr_3]   # if-then-else
    next                               # go to top of loop
    repeat expr                        # loop
    return expr                        # return from procedure
    suspend expr_1 [do expr_2]           # suspension of procedure
    until expr_1 [do expr_2]             # until-loop
    while expr_1 [do expr_2]             # while-loop
[1]ProIcon was a commercial Macintosh Icon extension venture by Bright Forest and Catspaw. When ProIcon was taken off the market, materials were placed in the public domain for redistribution by the Icon project. Unicon has deep roots in computing for the public good.
[2](1, 2) The generator limitation expression is a rare Unicon infix operator that is evaluated right to left. The limit needs to be known before the first result is suspended by the generator. Like all nifty Unicon expressions, the limit can actually be a generator as well.

Variable scope

Unicon supports variables with the following scope:

  • local
  • global
  • static
  • package

All procedures are global in scope. Methods are local to the enclosing class. Parameters to a procedure are local to the body of the procedure. local can be used to create localized references within a procedure hiding any global variables. Static local variables retain their values between invocations of the enclosing procedure.

Packages add another layer to the scoping rules of Unicon, and act as namespace containers for all procedures belonging to a package.


add more details to package scoping

Semicolon insertion

Unicon expressions are separated by semicolons.

i := 3 + 3; write(i);

That code is the same as

i := 3 + 3;

Which, during unicon translation, is the same as

i := 3 + 3

The lexer makes life easier, by automatically inserting semicolons at the end of a line if the last token could legally end an expression, and the first token of the next line is legal at the beginning of an expression. This has implications. Although automatic semicolon insertion is usually an invisible assistive feature, you need to be careful with expressions that span lines. They need to break at a point that avoids the automatic semicolon.

# semicolon.icn example of semicolon insertion issues
procedure main()
    i := 3 +
    i := 3
         + 3


prompt$ unicon -s semicolon.icn -x

The second result is equivalent to

i := 3; +3; write(i);

Unicon will gladly accept +3 as an expression, then discard the result, unused.

In the sample, the first evaluation, ending with +, which is not a legal expression ending token, avoided the automatic semicolon and set i to 6.

i := 3+3; write(i);

Discarding results is a feature of the Unicon implementation and happens all the time. All parameters to a function or procedure are evaluated, but only the required ones are dereferenced and passed. Conjugation evaluates expr_1, and produces the value from expr_2 if expr_1 succeeds, but the value of expr_1 itself is discarded. Side effects may happen, but the result is discarded in terms of the surrounding context.

Just a thing to be wary of when splitting expressions across lines. The Unicon lexical analyzer is very flexible and does the right thing in the vast majority of cases, but white space is not the only concern when splitting expressions across line breaks. The computer will always do what it is told, which may not always match what a human intended.

Visible semicolon insertion

To verify how the compiler treats multi-line expressions, the preprocessor output can always be used to see where semicolons are inserted:

prompt$ unicon -E -s semicolon.icn
#line 0 "/tmp/uni25472276"
#line 0 "semicolon.icn"

procedure main();
    i := 3 +
    i := 3;
         + 3;

Bound Expressions

Along with the order of precedence, and goal directed evaluation, bounded expressions can be used to control the level of backtracking. A lot of bounded expressions are implicit. Intuitive, but nearly invisible with the automatic semicolon insertion that occurs at the end of each line (or multi line expression as explained in Semicolon insertion).

Bound expressions block backtracking, a complex feature (for the Unicon language designers and compiler authors). This features allows goal directed evaluation to work, without rewinding all the way back to the main entry point every time an alternative is required.

# backtrack-bound.icn
procedure main()
    # not found; a keeps the value 1, alternate not used
    a := (1 | 2)
    if find("h", "this") == a then
        write("found at ", a)
        write("not found: a is ", a)

    # found; alternative is allowed when striving for the goal
    if find("h", "this") == (a := (1 | 2)) then
        write("found at ", a)
        write("not found: a is ", a)


The first expressions is implicitly bound at the newline between the a assignment and the if find, in the first code fragment. Unicon will not backtrack to reassign a to 2 when attempting to satisfy the find goal. This is a good thing. In the second fragment, backtracking is not bound as part of the a assignment, and find is allowed to try 2 when striving for the goal.

not found: a is 1
found at 2


The available Unicon documentation, which includes the Icon document collection, is a vast treasure horde of gems and jewels. The Unicon mailing list is also a source of learning and shared knowledge. Bruce Rennie asked about this one:

# suspended conjugation
procedure main()
    every write(subproc())

procedure subproc()
    every (suspend 1 to 3) & 4

That code produces:


Normally conjugation (the & operator), returns expr_2 when expr_1 and expr_2 succeed.

In the above instance, the parenthesized suspend actually fails when resumed after the 3 is delivered. The conjugation then fails and the 4 is never used. The write eventually fails, and the every in main fails after the to generator write side effects.

# suspended conjugation
procedure main()
    every write(subproc())

procedure subproc()
    every suspend 1 to 3 & 4

That code will display the 4, and only the 4, the expr_2 result of the conjugation.


The key expression here is grouped as

every suspend ((1 to 3) & 4)

1 to 3 succeeds, and conjugation produces expr_2 when expr_1 succeeds.

Unicon Co-Expressions

Co-expressions are another ahead of its time feature of Icon/Unicon. They are usually used in the context of generators, allowing sequence results to be generated when needed.

Co-expressions are also a key part of the multi-tasking features built into the Unicon virtual machine. See Multi-tasking for more information on this aspect of Unicon programming potentials. Multiple programs can be loaded into a single instance of a running Unicon machine and task co-expression activation can make for some very interesting possibilities.


co-expression entries

User defined control structures


control structure examples

Index | Previous: Data Structures | Next: Operators