Deck Language Reference

Introduction

Deck is a modern general-purpose programming language. It features:

It also has:

Deck has two goals: to be a useful general-purpose programming language on par with Java or Python and to be completely explorable by curious novice programmers. This means that the final Deck implementation needs to be a simple program written in Deck.

To accomplish this, Deck is a building-block language. Outside of a tiny core, all of Deck is just libraries written in Deck.

(The current implementation doesn't live up to this goal, but it's heading that way.)

Other Resources

The file intro.pod provides a more gentle introduction to the language. It tries to be both a tutorial and a tour of the language's most interesting features.

See the file libref.pod for documentation of all of Deck's functions, macros, mprocs and classes. Since Deck mostly just consists of those, it is necessary reading for making sense of the language.

The directory progs contains a few example Deck programs that may be worth examining.

Syntax

Deck source code is currently expected to be ASCII text. However, I am aware that not everyone in the world speaks a language that can be represented that way. The plan is that someday, Deck will also support one or more forms of Unicode.

Lines

Deck source code is read in as a sequence of lines. These lines are sometimes syntactically significant so it is important to be aware of the rules.

A line runs from either the start of the input file or from the character after the end of the previous line through to the next ASCII newline character or the end of the file. For example:

    line 1
    line 2
    line 3

In addition, a semicolon (;) can be used as a logical line separator instead:

    line 1; line 2; line 3

Conversely, it is possible to spread a logical line across multiple physical lines by ending the line with a backslash (\):

    line \
    1
    line \
    2
    line \
    3

Note that unlike other line-oriented languages (e.g. Tcl and Unix shells), the backslash is not an escaped newline. You may put other whitespace or a comment after a backslash and it will continue to mean the same thing:

    line \    # this is a comment
    1
    line \    # so is this
    2
    line \    # and this
    3

Note: How Deck handles newlines from different platforms (LF vs CR+LF vs CR) is currently dependent on the underlying Perl interpreter. This could be considered a bug.

Comments

Deck comments begin with a pound sign (#, aka number sign, aka hash mark, aka octothorp) and run to the end of the physical line:

    foo     # this is a comment

Comments end at the physical line: semicolons and backslashes do not have an effect on comments:

    bar     # this is a comment; this is still the comment
    quux    # this is a new line \
    baz     # this is not part of the previous line's comment

For parsing purposes, comments are considered whitespace.

POD Regions, lack thereof

By convention, Deck documentation is written in POD format. This includes the library docstrings, which are extracted via a utility and them passed to pod2html et. al. for processing.

At one point, Deck also supported inline POD documents. However, this has been removed because it was never used and because this freed up the leading = for other uses.

See the perlpod documention for a better explanation of POD.

Literals

Deck supports the following data literals:

Numbers

Deck currently only implements a single Number type but there are multiple literal forms. They are:

    12345           # Decimal integer
    0456            # Also decimal--leading zeroes are ignored
    -123.456        # Negative float
    0xDEADBEEF      # Hex literal
    0b10101010      # Binary literal
    0o755           # Octal literal

Note the octal form. Unlike most C-like languages, a leading zero in an integer literal does not indicate an octal constant; you need to prefix it with 0o. This is less of a gotcha to novice programmers. It also provides another entertaining way to type zero: 0o0.

In addition, numeric literals may contain underscores (_). These are ignored by the parser and so can be used to make your numbers more readable:

    1_000_000       # One. Million. Dollars.

The underscore can appear anywhere in a number except for the beginning:

    _3.14159        # WRONG!

This is needed to distinguish between numbers and symbols.

Strings

Deck has two kinds of strings: single-quoted strings and double-quoted strings. Both evaluate to the same kind of internal object but the quote determines how they are parsed.

Both kinds of strings can be spread across multiple lines.

Single-Quoted Strings

Single-quoted strings are delimited by one or more single quote characters ('). The text inside a single-quoted string is not altered in any way. It is stored literally without any kind of escaping or interpolation:

    'Hello, world!'

Because quotes are likely to appear in the text with no way to escape them, you can use a series of quotes as the delimiter instead of a single quote:

    '''I don't think this will be a problem. Nor will "''" be.'''

The string continues until the parser finds the same number of quotes. (But remember that more quotes is the same number of quotes followed by another sequence of quotes.)

Note that the delimiter must have an odd number of quote characters. Otherwise, it will look like an empty string (i.e. an odd-number-sized delimiter followed by the closing delimiter.):

    ''''Hello, world!''''           # WRONG!

    ''''''                          # Empty string

As a special case, any even number of single quotes is parsed as an empty string.

Double-Quoted Strings

Double-quoted strings allow interpolation and escaping. As in Perl and C-family languages, the backslash character is used as the escape:

    "She said, \"That's what he said!\""

In addition, Deck implements a subset of Perl's escape sequences:

    \t      tab
    \n      newline
    \r      return
    \a      bell
    \f      form feed

For example:

    "line 1\nline 2\nline 3\n"

The backslash also escapes the other special characters:

    \$      Dollar sign
    \@      At sign
    \\      backslash

For example:

    "\\Win \$\$\$\$\$ \@ my online casino!\\"

The $ and @ require some special attention:

Deck provides something like Perl-style variable interpolation. If a string contains a dollar sign ($) followed by a name (and possibly bracketted by curly brackets ({ and })), it will be replaced by the value of the variable's printable form:

    x = "foo"
    puts "$x $x"                # "foo foo"
    puts "${x}bar"              # "foobar"

Similarly, an at-sign (@) followed by the name of a list will expand to the printable form of each element in the list separated by a single space. If the variable is not a list, it is expanded in the same way as the $ for above:

    var x = :["foo" "bar" "quux"]
    puts "@x"                   # "foo bar quux"
    x = 42
    puts "$x"                   # "42"

Note that @ expansion only works for items of the type List. Datatypes that mimic a List still do not get expanded like one. However, this may change in a future release.

Beware: Double-quoted strings are sometimes converted into more complex expressions by the parser. This can react badly with certain other constructs to put a non-string where a string is expected. For example, if a procedure contains this docstring:

    proc foo {x} {
        "do the foo thing"
        ...
    }

everything will be fine. However, if you add an interpolation:

    proc foo {x} {
        "do the foo thing to $x"
        ...
    }

what you're actually doing is this:

    proc foo {x} {
        [_::mkstr "do the foo thing to " x]
        ...
    }

which is not only not a docstring but will attempt to call a string as a function.

So remember: a double-quoted string is a true string if and only if it does not contain an unescaped @ or $.

Delimiters

Deck parses the following characters as special delimiters:

  1. { and }, the curly brackets

  2. ( and ), the round brackets

  3. [ and ], the square brackets

  4. :, the quote delimiter

  5. = at the start of a LoL line indicates the line should be parsed as infix.

The first three items are the parentheses Deck supports.

The colon (:) is used to quote (i.e. delay evaluation) of the thing following it. See Quoting below.

The equal symbol (=) affects the parsing of LoL lines. See <LoLs> below.

Symbols

Symbols (aka words, aka names) are generally used as the names of variables and constants. There are two forms of symbols:

Textual symbols take the same form as most modern programming languages' variables: an alphabetic character or the underscore (_) followed by zero or more "word" characters (i.e. alphanumerics or underscore). Examples:

    foo
    _foo42
    this_is_a_variable
    _45         # Tricky: a variable, NOT a number

Operators consist of one or more of any of the following "operator" characters:

    - . | ! @ $ % ^ & + = ?  < > /

For example:

    (a + b)

Operators may be prefixed by the backslash (\). This does not change the meaning of the symbol but sometimes provides a hint to the parser (see Expressions below). For example:

    (++ == \++)         # This expression is true--they are the same symbol

Lists

Lists consist of zero or more items surrounded by square brackets ([ and ]) and separated by whitespace. They will nearly always need to be preceded by the quote operator (:) to keep them from being treated as expressions:

    :[1 "two" 3]            # Simple list
    :[]                     # Empty list
    :["foo" "bar" [1 2 3]]  # Nested list

Symbols in lists do not need to be quoted:

    :[+ a b]

Sublists may also be in infix form.

    :[+ a (b & c)]          # Nested list with infix sublist

Note that there are a few special cases. These are described in Syntactic Sugar below.

(As an aside, Lisp/Scheme hackers should be aware that the underlying data structure of a Deck list is an array (aka a vector), not a linked list of CONS cells. This is explained in more detail below.)

Expressions

Deck expressions ultimately take the form of Lisp-style prefix expressions but the parser will also accept several more readable forms and convert them into simple prefix expressions.

Quoting

The colon (:) is Deck's quote symbol. It is roughly the same as the single quote (') in Scheme and Common Lisp. A quoted expression is any Deck expression following a quote:

    :42
    :[1 2 3]
    :foo

The quote prevents the thing from following it from being evaluated. Instead, the result of a quote expression is the thing being quoted. A quoted list returns the list itself:

    :[foo bar quux]

The unquoted list

    [foo bar quux]

would attempt to look up foo, bar and quux, then call foo with the other values as arguments.

In the same way, quoting a symbol returns the symbol instead of its value:

    :foo        # symbol 'foo'
    foo         # value of variable 'foo'

Other literals (single-quoted strings and numbers) yield themselves when quoted. Double-quoted strings yield either the string (if there's no interpolation) or the quoted interpolation expression.

The various expression transformations that happen below (i.e. infix expressions, LoLs and prefix syntactic sugar) are all done before the expression is quoted. Thus, a quoted infix expression will still yield the prefix form of that expression:

    x = :(2 + 3)        # x gets set to [+ 2 3]

Note that the quote is a syntactic structure rather than a function. The parser creates a quote expression when it sees the colon. It does not attempt to look up the value of the variable : and call it as a function.

The quote and the next expression together make up a single expression. This has higher precedence than anything else except for parentheses.

Prefix Expressions

Deck prefix expressions are simple unquoted lists. Each item in the list is first recursively evaluated from left to right. The first expression in the list is expected to evaluate to a callable object (usually some kind of procedure). This procedure (or other) is called with the remaining items passed to it as arguments:

    [+ 1 3]                                 # Simple example
    [[getHook :panic] "Oh no!"]             # Less simple example

This is more or less the basic Lisp-style S-Expression.

Syntactic Sugar

As a special exception to the list parsing rules, the following symbols are always treated as infix expressions:

    '=>' '.'
    '->'

The first two have the same precedence and the third has lower precedence.

For any of the above operators, the sequence *expr1* *op* *expr2* is automatically treated as a parenthesized infix expression and becomes [*op* *expr1* *expr2*].

So,

    [foo b.c]               becomes     [foo [. b c]]
    [x->y 42]               becomes     [[-> x y] 42]
    [bar x => y]            becomes     [bar [=> x y]]

It is an error to start or end an infix expression with one of these operators:

    [a b ->]            # WRONG!
    [-> a b]            # WRONG!

This auto-infix behaviour can be disabled by prefixing the operator with a backslash (\) character. This does not alter the meaning of the token but will disable the syntactic sugar:

    [foo b \. c]
    [x \-> y 42]
    [bar x \=> y]
    [\-> a b]

The => is bound to a macro that creates closures. The . operator is used to access object attributes and the -> operator is used to look up methods in objects.

Infix Expressions

Any Deck expression surround by parentheses (aka round brackets, ( and )) is parsed as an infix expression. The operator precedence in decreasing order are as follows:

        . @ =>
        ->
        **
        * / // %
        + -
        << >> >>> <<<
        == === != !== < > <= >= <=>
        &
        | ^
        &&
        ||
        (user operators)
        =

All operators are left-associative except for =.

Any other sequence of operator characters is also valid as an infix operator but will have second-lowest precedence:

    (a !!! 2)           # does whatever !!! is defined to do

Like the syntactic sugar above, infix expressions are just another syntactic form for basic Deck expressions. The parser immediately converts infix expressions to the equivalent prefix expression. This is true even if the expression is quoted:

    :(a + b * c)        # Yields [+ a [* b c]]

Infix expressions need to have an odd number of items with unescaped operators in all of the even-numbered positions:

    (c / d & f)         # Right
    (c \+ d)            # WRONG!!!
    (c d e)             # WRONG!!!

The odd-numbered items may also be operators:

    (addOp = +)

As a special case, a one-item infix expression expands to a call to Lang::value with the element as an argument:

    :(a) == :[Lang::value a]

This guarantees that an infix expression always expands into a function call that, when evaluated, returns the expression's value.

Negation

The - operator with one argument negates that argument. In this case, it must be a prefix expression. There is no infix negation. However, numeric constants may begin with a - to indicate that they are negative and those may be used in an infix expression.

Specifically:

    (a + -3)            # Correct. -3 is one token.
    (a + [- b])         # Correct. Infix negate on b
    (a + [neg b])       # Correct.
    (a + -b)            # ERROR
    (a + (-b))          # ERROR

LoLs

LoLs (Line-oriented Lists or Lists of Lists) are another form of Deck expression. Each LoL is delimited by braces ({ and }) and is immediately converted into a quoted list containing zero or more other lists.

LoLs use line boundaries instead of brackets as the delimiters of their sublists. Specifically, a LoL line starts at the end of the previous LoL line (or the start of the LoL if this is the first line) and ends at the following line-end (or the end of the LoL if this is the last item) in which all brackets opened in this line have been closed.

Examples:

    {
        line 1                  # First Entry
        
        line 2 {                # Second entry
            continuing...       # Second entry continued
        } and done!             # Second entry finished
        
        line 3 [1 2 3           # Third entry starts
            4 3]                # Third entry ends
    }

LoL lines are logical lines (as described above) and so are subject to ; and \. In addition, comments are ignored as are empty lines (i.e. lines containing no non-whitespace characters):

    {                           # Empty line.  Ignored
        line 1 \                # Item 1 starts
        continued               #  ...and continues
        
        line 2 ; line 3; line 4 # Next three items
    }

Deck source files are implicitly LoLs, as is the interactive command prompt. They differ from ordinary functions a little in that each toplevel line is evaluated immediately before the next line is read.

Because the list delimiter is implied in a LoL, it is not obvious whether a LoL line should be treated as an infix expression or as a prefix expression. Which one is determined by the contents of the line.

The parser examines each LoL line and treats it as infix if it looks infix or as prefix if it looks prefix. There is a pretty high threshold for what is considered prefix or infix and if the parser can't decide, it is an error. This is reduce the number of surprising interpretations of code.

Informally, LoL line looks infix if it is an assignment operation or if it consists of a series of terms divided by boolean AND (&&) and OR (||) operators.

In addition, you can force a line to be parsed as infix by prefixing it with an equal symbol (=). This will be discarded and the rest of the expression will be parsed as infix. The expression still needs to be structured like an infix expression and it is an error if it is not. (Think of it as an assignment with no destination.)

Otherwise, it is considered a prefix expression. In the latter case, there should be no unescaped operators in the expression (with var and const expressions being the exception).

Here are some examples:

    {
        
        a = 42                              # infix
        
        load "hello"                        # prefix
        
        [load "hello"] || [die "error!"]    # infix
        
        obj.status = 42                     # infix
        
        array@n = x + 5                     # infix
        
        =a + b                              # infix
        
    }

In addition, the 'var' and 'const' keywords are a wierd special case in that they are infix expressions but allow the = word unescaped and will reparse some of their arguments as infix expressions:

    {
        
        var a = 42                          # special case prefix
        
        const {x = 2+z; y = x + 1}          # prefix
        
        var a = x + y * 11;                 # prefix but reparsed later
        
    }

Expressions that look like you made a mistake are usually considered errors. Specifically, prefix expressions (other than var/const) are not allowed to contain unescaped operators:

    {
        
        a = b + + c             # Error!
        
        a = n + 1 +             # Error!
        
        a \= b \+ \+ c          # Prefix (calls C<a> with arguments)
        
        = c add b               # Error!
    }

Escaping operators allows you to tell the parser that yes, you know what you're doing.

Here's the excruciatingly precise definition:

For a LoL line to be considered infix, it must:

1. Contain an odd number of elements.
2. Have only unescaped operators as the even-numbered elements.
3. Have no unescaped operators as the odd-numbered elements.

In addition, it must take one of the following forms:

For a line to be considered a prefix expression, it must either:

Each non-empty, non-comment line must meet one set of requirements. If it does not, it is an error.

LoL Wierdness

The following does not work:

    foo.isThingy || [return]

You need to parenthesize the first expression:

    (foo.isThingy) || [return]

This is because the first expression is an infix expression but not an assignment and not a purely boolean expression. Deck immediately assumes you've made a mistake and complains.

As Deck evolves, it will probably relax some of these constraints.

Compact Sub Declarations

Deck provides special syntax for declaring subs that looks mostly like an ordinary LoL. It is identical to a LoL except that it begins with a list of arguments delimited by | before and after:

    {|a b| = a + b}

This is identical to

    [sub {a b} {= a + b}]

In fact, the parser converts the former into (more or less) the latter at compile time. It is possible to view the expression using the quote operator. For example, this:

    puts :{|| = 42}

will display the resulting expression.

(See 'Closures' below for an explanation of sub.)

More precisely, a compact sub declaration takes the following form:

    '{' '|' I<word>* '|' I<remaining code...> '}'

In addition:

  1. Parameter names (the words between the | symbols) may not be operators.

  2. Any number of spaces or newlines may be used between tokens up to the last |.

Data

Deck has a number of fundamental data types. This section lists them. Note that in Deck, all objects are OOP objects and so implement methods. However, it is possible to (mostly) treat Deck as a purely procedure language and pretend Objects are dumb data.

Basic Types

List

Type List is the basic list type. Lists are arrays (not linked lists) of references. Class List implements the "Sequence Protocol" (as described below.)

Lists are created by the parser from list literals, via the list and listSized functions.

String

Class String is the Deck string type. Instances hold text in some internal encoding.

String implements the "Sequence Protocol". Each element in a String is individually addressable. However, Deck does not have a separate Character type. Instead, each element of a String is also a String of length one.

Instances are created from literals by the parser and by the function stringSized.

Note: At some time in the future, Deck will support Unicode or some other extended character set. Unfortunately, that day is a long time coming and depends on me learning a lot more about Unicode than I know now. In the meantime, Strings are just whatever 8-bit ASCII superset your terminal displays.

All of this vague definition of String as text of some sort is here as a sort of placeholder for that day. In particular, you're not supposed to assume that you can throw any arbitrary binary data into a String. That may work now but it won't in the future. If you need to deal with binary data, use ByteArray. It's designed for that.

Symbol

Symbols are used to represent names in Deck expressions. They are similar to Strings but are immutable. In addition, the system guarantees that each Symbol is unique. That is, there is only ever one symbol containing a particular sequence of characters.

Symbol implements the "Sequence Protocol" except for the ability to set elements. Attempting to modify a Symbol results in an error.

Instances are created by the parser from literals and by the function intern. The function unintern returns a string containing the same text as the symbol.

Symbols and strings with the same characters are equal according to op_Equals.

ByteArray

A ByteArray is an array of 8-bit bytes. It is here to give you a way to manipulate binary data. It implements the "Sequence Protocol". Only integers between 0 and 255 may be stored in a ByteArray.

Instances are created by the function bytesSized.

Number

Numbers represent numbers. Instances are created by the parser from literals and by arithmetic operations. Numbers are immutable.

Note: Number is just a wrapper around Perl numbers, which is not really ideal. The long term plan is for there to be several numeric classes with arithmetic operations smart enough to convert between them as necessary.

Nil

Nil is the class of nil, the undefined object. nil is the only instance of class Nil. Uninitialized values are set to nil by default.

Nil is a true object however and implements the basic set of methods. In particular, its implementation of attribute isNil returns true.

Object

Object is the abstract base class of all objects in Deck. It cannot be instantiated.

Class

Type Class is the type of all Deck classes. Instances are created via the 'class' and 'object' macros. It is described in much more detail below in the section Objects.

Obscure Types

The following types are visible but are not commonly used.

Quote

A Quote contains one other object which may be read. It exists solely to indicate to the compiler that the wrapped object should not be immediately evaluated. You will only ever need it if you are constructing code to be compiled.

Instances are created by the parser from the quote symbol (:) (but that will almost never reach you) and by the built-in function quote. Note that the function quote does not have any effect on the evaluation of its arguments.

Macro, Procedure, Method

These are the types of various types of procedure. The first two can be invoked as part of an expression and their isCallable attribute returns true.

(You can use this to do quick-and-dirty memoization:

    (foo.isCallable) && [foo = [foo]]
    return foo

The main problem is what to do if foo also returns a callable.)

MethodCall

A MethodCall represents a method lookup on a specific object. It is callable.

Stringlike

Stringlike is the abstract base class for some types that contain sequences of binary data. It cannot be instantiated.

Struct

Type Struct is the abstract base class for all structured objects. Structured objects are objects containing zero or more named instance variables--that is, objects as implemented by most object systems and programming languages.

This is described in Objects below.

Booleans

Deck does not have a dedicated Boolean type. Instead, each object is either true or false.

The following objects are false:

    0                       (the Number 0)
    ''                      (any empty String)
    :[]                     (any empty List)
    [intern '']             (the empty Symbol)
    [bytesSized 0]          (any empty ByteArray)
    nil

In addition, any object whose isTrue attribute yields nil is false. Implementations of isTrue should never return a different value if the object has not changed.

All other objects are considered true.

Deck defines two constants, true and false that refer to the "canonical" boolean values. These are the values that most boolean operators return.

true is defined to be 1 and false is defined to be nil.

Stupid Boolean Tricks

Deck provides two short-circuited boolean operators, && (logical and) and || (logical or). Because of the short-circuiting behaviour, they can be used as control structures.

For example, here's a common idiom stolen from Perl:

    [doThing arg1 arg2] || [die "doThing failed."]

doThing returns false on error and true on success. As a result, the die expression is only called if doThing fails.

Similarly, you can use the && operator to guard expressions:

    [canLoad x] && [load x]

The other interesting thing about these operators is that their result is the value of the last expression evaluated. You can leverage this to do all kinds of useful conditional assignments:

    x = x || ""                     # Ensure that x is initialized

    x = [cacheLookup] && [recalc]   # Recalculate if cache lookup fails

    y = (x > 5) && x || 5           # y = maximum of x and 5

Procedures and Callables

Deck procedures are opaque objects that can be called with optional arguments and that return a value that may be ignored. They are created from one or more lists of expressions by a compiler which gets invoked via various macros. The underlying compiler code is implementation-defined.

Note that in addition to procedures (a term with includes closures and methods here), Deck provides two other callable types: Continuation and MethodCall. Both are discussed below.

Expression Evaluation

A Deck procedure body consist of a list containing zero or more other lists. Each of these sublists is an expression. When invoked, the procedure evaluates these expressions from start to finish:

    :[
        [fn1 1 2]       # First expression
        42              # Literal.  Not really useful.
        [fn2 3 4]       # Second expression
    ]

Each expression must be a value of some kind. If it is a list, it is recursively evaluated. If it is a literal, it has no effect unless it is the last element of a sub; all other cases may trigger a compiler warning. (Previous versions of Deck required that all elements be list. This has changed. However, mproc's sub argument and some other macros not convert a list to a function if it contains non-list elements.)

Expressions are evaluated as follows:

1) Each element is evaluated starting with the leftmost element (at index 0) and working rightward (with increasing index values). If the element is an expression, it is recursively evaluated; if it is a variable, its value is looked up.

2) The value of the first element in the list is assumed to be a procedure (or more specifically, a callable--it is a runtime error if it isn't) and it is called. The values of the remaining elements are passed to as actual arguments to the procedure call.

(Note that this explanation assumes that all macros have already been expanded.)

Variables and Constants

The one exception to the execution model discribed above is the concept of variable declaration. The word var declares (and optionally initializes) one or more variables in the current scope. For example:

    proc {x} {
        puts x
        
        var y = x + 1
        puts y
    }

Notice that var appears later in the function body.

Specifically, a var declaration makes the compiler do three things:

1) Create the required variable(s) at the start of the procedure and initialize it to nil.
2) Ensure that there is no use of the variable before the var statement.
3) Place the initializer (if present) at the site of the var statement.

The reason for this bit of wierdness is that final blocks make it possible to access a variable before it is officially defined. Returning from a function with a final block is effectively a goto that may skip over one or more variable or const declarations. This way, the variable will always exist, even if it's just nil.

(Actually, it's a bit more complicated than that. var and const are macros that expand to some implementation-defined form and the result of that expansion contains the actual "magic word".)

Syntax

var and const have a unique syntax that needs more explanation.

A declaration consists of the word var or const followed by the declaration part.

The declaration part is either:

1) One or more words, including escaped operators.
    var x y z
        var a \= b

(Obviously, this doesn't work for 'const'.)

2) An infix assignment expression:
    var y = 42
    const PI = [compute_pi]
        var [a b c] = :[1 2 3]
3) A LoL containing assignments.
    var { x = 42; b = "bob" }
        var { [a b c] = :[1 2 3] }

The assignments in case 3) and the single assignment in case 2) following the word var or const are treated identically to other value assignments. That is,

        var {x = 42; y = "bobo"}
        var z = 1

is equivalent to

        var x y; x = 42; y = "bobo"
        var z; z = 1

The only difference is that you can't use 'set' instead of the '=' in case 2), as something like this:

        var set a b

will look like case 1).

This is perfectly fine (if ugly and unnecessary) in case 3, however:

        var {set x 42}

Note that in case 2), the assignment looks like an infix expression. It is not as far as the parser is concerned. Instead, it is parsed as infix by the macro implementing var or const. One consequence of this is that escaped operators will lose their escapedness by the time that expression is parsed. For example, this:

        var \= = 42             # Assume this is inside a procedure

will fail with a parse error because the escape will have been stripped out before the var macro can parse the argument. Oddly enough, this:

        var \+ = 42

will work (at least at the time of this writing--don't depend on it). Of course, you can always use var {\= = 42} instead.

There is a special parsing rule that lets var and const get away with the unescaped = in what is technically a prefix expression.

It is possible to take the result of a variable declaration. However, the value is always nil.

Assignment

Variable assignment is performed using either the = operator or the set macro:

    a = 42
    set a 42

The two are equivalent (and in fact are just different names for the same macro) but the former is generally parsed as infix. The following are equivalent:

    a@42 = 1
    set (a@42) 1

As are:

    a.foo = "foo"
    set a.foo "foo"
    a->foo_set "foo"

= examines its left-hand-side argument to determine how to expand and what to do with its right-hand-side argument. The LHS can be either a bare name, a list access (@) expression or an attribute (.) expression.

If the LHS is a bare word, it must be the name of a variable in the scope and that variable has the RHS assigned to it. In this case, the expression expands to a call to a function in the _ namespace (currently _::set) which magically performs the assignment.

    var foo
    foo = 42

If the LHS is an \@ expression, the entire expression is replaced with a call to atPut where the LHS of the \@ is the object, the RHS of the @ is the first argument (the index) and the RHS of the = is the second argument (the value).

    var foo = [list 1 2 3 4 5]
    foo@3 = 42

If the LHS is a . expression, the expression is replaced with method call. The LHS of the . is the receiver and the method is the RHS of the . (which must be a word) with _set appended to the name. The RHS of the assignment becomes the argument of the method.

    var foo = [SomeClass->new]
    foo.field = 42

If the LHS is an unquoted list containing only non-operator variable names, the expression is treated as a multi-value assignment. In this case, the RHS must evaluate to a list-like object (i.e. one that implements the method at). Each variable in the LHS is assigned the next value in the RHS. While the LHS looks like a function call, It's not; this is syntactic sugar.

    var a b c d e
    [a b c d e] = :[1 2 3 4 5]

The RHS must have at least as many elements as the LHS but may have more. This restriction is checked at run time.

Docstrings

Packages, procedures, methods and classes may optionally begin with a single string literal (Beware: interpolated strings don't count.) If present, this string literal and other information about the object it annotates are stored in the interpreter.

They can be retrieved using the built-in functions _::docstring_keys and _::docstring_get to create documentation. The utility progs/utils/print_docstrings.dk does this for the system classes.

Although there is no formatting requirement, existing docstrings are in POD format. This is the preferred format.

Toplevel Expressions

Toplevel expressions are expressions written and evaluated outside of any procedure, method or closure. A Deck source file consists of a series of toplevel expressions.

Toplevel expressions are read and evaluated one after another with each expression being evaluated before the next one is read:

    var foo         # Read, compiled and evaluated.
    foo = 42        # Read, compiled and evaluated.

This is slightly different from procedure bodies where the entire source is compiled at once.

Variables and constants declared at the toplevel are global (subject to package rules.)

A procedure definition is simply a toplevel expression whose side effect is the creation of the procedure. All of the other definitions work the same way. The only exception to this is the package declaration, which is a magic word stripped out by the interpreter when the package is loaded.

Procedures

Argument Lists

All procedure types share a common type of argument list. It must be a list containing symbols, each the name of a procedure argument. arg-list may be a simple list or a LoL. That is, these are equivalent:

    proc foo {a b} {puts a b}       # One definition of foo
    proc foo [a b] {puts a b}       # Equivalent definition of foo

The relevant macro automatically smoothes out the differences between the list formats.

If the final argument in the argument list is named args, all subsequent actual arguments will be packed into a List and the list passed as args:

    proc bar {x args} {
        puts "x: " x " args: " args
    }

    # ...

    bar 1 2 3 4 5       # Yields: "x: 1 args: [2 3 4 5]"

If there are no subsequent arguments, args is an empty list. It is a fatal error to name any argument other than the last one args.

Procedure Bodies

A procedure body is a quoted list of lists (I.e. a LoL) containing the expressions which are actually evaluated when the procedure is invoked. The LoL syntax creates a suitable procedure body but you can also use the more explicit notation if you hate readable code:

    proc foo [] :[[puts "hello, world"]]

But don't do that.

Continuations

Continuations are callable objects associated with an individual function call. When the continuation is called, the function call that created it will return.

All procedure types have continuations. In procs and methods, they are named return; in subs, they are named next.

In the most common case, continuations behave like the common return statement of most programming languages:

    proc max {a b} {
        if (a > b) {return a}
        return b
    }

However, return is not a control structure. It is an object that may be assigned, passed as a parameter or treated as any other data object. When invoked, the continuation will immediately return the procedure call in which it was defined. Any nested procedure calls are also returned. For example:

    proc f1 {r} {
        r 42
        puts "Done!"            # Never reached
    }
        
    proc f2 {r} {
        f1 r
        puts "Done!"            # Never reached
    }
        
    proc f3 {} {
        f2 return
        puts "Done!"            # Never reached
    }

f3 calls f2 which calls f1. f1 calls the continuation, which immediately returns the entire call stack.

Continuations may take one argument. If given, the argument is the return value of the function returned. If not, the result is nil.

It is a fatal error to call the continuation created by a function call which has since returned:

    var r
    proc f1 {} {
        r = return
    }
        
    f1
    r 42                # Fatal error: r is no longer valid

The library class Exception wraps continuations as a way to implement exception-based error handling.

Final Blocks

Deck procedures allow an optional second block of code after the body. This is called the final block.

A final block is effectively part of the procedure's body. It is guaranteed to be executed just before the procedure returns. This allows you to add cleanup code tht will be called even if the procedure is terminated by a continuation.

For example:

    proc readInfo {resource} {
        var rh = [openResource resource]
        [readHdr rh]    || [return nil]
        [readBody rh]   || [return nil]
        [readEnd rh]    || [return nil]
        
        return 1
    } {
        close rh        # rh is guaranteed to be closed
    }

In the above example, the handle rh is guaranteed to be closed when readInfo returns.

If a function returns before a local const or variable is initialized, it is visible to the final block but has a value of nil.

This can lead to subtle errors. For example, suppose that there is a continuation referenced by argument resource in the above example and that in some cases, it is invoked. This leads to a final block calling close on fh with a value of nil. In this case, the final block needs to read:

        fh && [close fh]

It is a fatal error to invoke any continuation in a final block that is not also caught within the confines of that final block. In particular, invoking return is not allowed.

Procedure Types

Basic Procedures (proc)

A proc is the workhorse procedure in Deck. It is declared with the proc macro:

    proc <name> <arg-list> <body> <final>

name is a bare word. It is the name of the procedure to define. It may be a qualified name. body is a procedure body and final is the optional final block.

name may also be omitted. In this case, proc will define the proc but will not bind it to a global name. However, proc always returns the resulting proc and so it can be assigned this way. For example, the following to expressions are roughly identical.

    proc foo {a b} {return (a + b)}
    
    const foo = [proc {a b} {return (a + b)}]

Procs all define const return when called. It is bound to the function's return continuation.

proc may be called anywhere, including inside other procedures. The resulting procedure does not inherit the scope of the procedure that calls it however. For example:

    var x = "global x"              # Global variable
    proc mkproc {} {
        var x = "local x"           # Local to mkproc
        proc y {} {puts x}          # Proc
    }
        
    # Later ...
        
    mkproc                          # Defines y
    y                               # Prints "global x"

In addition, the name of the procedure is also always global. This is a deliberate feature. If you need access to the local scope, use a closure.

procs are declared as const names.

By default, a proc returns nil. You can return other values with return.

proc may also be called with only the procedure name and argument list:

    proc foo {a}

This has the effect of pre-declaring the procedure. Subsequent procedures may then call it even if they precede the actual definition.

If a procedure is declared in advance, it must be defined before the end of the module. The act of defining a declared variable magically overrides the constness of the definition.

(Bug: Deck currently does not currently detect a mismatch between the arguments in a pre-declaration and procedure definition. It also tolerates advance declarations with no argument )

Closures

Closures (aka sub) are small unnamed procedures. Unlike procs, they inherit the lexical scope in which they are created. That is, a closure created in the body of another procedure has access to every variable that the outside procedure can access. This access survives the outside procedure's return:

    proc foo {} {
        var x = 0
        return [sub {x = x + 1; x}
    }
        
    # ...
        
    var f = [foo]
    puts [f]                    # Output: 1
    puts [f]                    # Output: 2
    puts [f]                    # Output: 3

Closures are typically created by the macro sub. sub can take up to three arguments: an argument list, a procedure body and a final block:

    sub {a} {puts a} {puts "Done!"}

If called with one argument, it is taken as the body with the argument list assumed to be empty:

    sub {puts "Hello, world}

When called with two arguments, the first argument must be the argument list and the second the body:

    sub {a} {puts a}

Thus, if you want to create a closure with no arguments and a final bock, you need to provide an empty argument list:

    sub {} {puts "Called!"} {puts "Done!"}

Most Deck control structures work by converting LoL arguments into subs. mprocs can do this automatically.

Closures return the result of their last expression by default. The exit continuation of a closure is named next. Aside from the name, it is identical to the return continuation in procs and methods.

=>

The => operator is a shortcut for calling sub. The left-hand argument is the argument list and the right-hand argument is the procedure. Because Deck automatically converts prefix uses of the operator into infix subexpressions, it is sometimes a more compact form of closure syntax.

For example, these are equivalent:

    [apply {a b} => {value (a+b)} :[1 2]]

    [apply [sub {a b} {value (a+b)}] :[1 2]]

In fact, the parser (more or less) turns the first into the second.

Methods

Methods (aka "member functions") are the code associated with an object. Semantically, they are identical to procs with three exceptions:

1) Methods have direct access to the instance variables of the object to which the belong.

2) Methods have a local const named self with references the object on which the method was called.

3) Methods return self by default instead of nil.

Methods are described in more detail below.

perlproc

A perlproc is a native Perl function accessible from Deck. It is described in more detail below.

Macros

Macros are an extremely powerful feature of Deck. They are, in effect, plugins to the compiler and so allow you to extend the language syntax in a (semi-) controlled way.

Deck has two kinds of macros: proper macros and mprocs. In general, you should always try to use mprocs if you can.

Proper Macros

Proper macros are basically functions that get called by the compiler to transform the expression it is compiling. If the first term in an expression is a name and that name references a macro, it is called as a function. It is given the expression as its arguments and the result of that function call is in turn compiled by the compiler. For example:

    macro bobo {mn a} {
        return [list :Lang::puts [quote mn] " " a]
    }
    
    # ...
    
    bobo 42                 # Outputs: :bobo 42

If the macro is returned by a more complex expression, it is a runtime error. Macros may not be called as functions. For example:

    [value bobo] 42         # ERROR!

The result of a macro call may also be a macro. If so, it is also evaluated by the compiler. This will continue until the result is not a macro or until some upper limit is reached. (Currently this is hardcoded at 20.)

The first argument of a macro is a Symbol, the name by which the macro was called. Macros may be referenced by more than one name and this is the name used in this instance:

    const blort = bobo
    blort 42                # Outputs: :blort 42

Unlike Scheme, there is no special syntax or semantics for proper macros. You have the full power and full responsibility of programmatically transforming code.

There are two things that Deck provides that may help you: firstly, Deck provides the functions subify and subifyOrDelay to convert lists into subs in an ordered way. See the library reference for details. Secondly, Deck's qualified names let you specify global variables in a way that the local environment cannot override. This is explained in the section on namespaces below.

MProcs

MProcs are lightweight macros. They let you do most of the things you normally need macros for but without the debugging headaches. An mproc definition creates both a proc and a macro. The macro expands its calls into calls to the functions with various changes made to the arguments.

The macro can:

For example, consider this proc. It applies a given procedure (typically a sub) to each element in a list:

    proc dofor {pr seq} {
        for elem in seq {pr elem}
    }

Calling it is pretty simple, too, if a bit clunky:

    dofor {a} => {[isValid a] || [db->store a]} objects

or

    dofor {|a| [isValid a] || [db->store a]} objects

Here is the equivalent mproc:

    mproc dofor {
        sub 1 pr
        seq
    } {
        for elem in seq {pr elem}
    }

It's identical to the proc above in all but one way: the first argument will be turned into a sub with one argument ("a") if the caller passes it a LoL. This makes the call look like this:

    dofor { [isValid a] || [db->store a]} objects

which is simpler.

MProcs are created by the mproc macro. The syntax is:

    mproc <name> <mproc-arg-list> <body> <final>

This is identical to proc except for the argument list.

Note that the object named by <name> is a macro, not a procedure. If you take a reference to it, you will not be able to invoke it at runtime. The related function has the same full name as <name> but is in the namespace __ (two underscores). So this:

    mproc Main::foo { ... } { ... }

creates macro Main::foo and function __::Main::foo.

It is probably not a good idea to invoke the function directly. I don't have any plans to change the naming or argument order, but I might get a bright idea at some point. You probably want to wait a bit just in case.

MProc Argument Syntax

The mproc argument list must be a LoL. Each sublist describes a single argument. Typically, I put each argument on its own line; that's what I'm going to do here.

Each argument takes the following form:

    'strict'? <type>? name <default>?

Where <type> takes the form of one of:

    sub 0-9
    sub [arg1 ... argn]
    symbol
    list
    word

And <default> takes the form:

    [default <literal>]

where <literal> must be a number, string literal, quoted list, symbol or nil.

Some examples:

    strict sub [a b c] aProc [default {}]
    symbol name
    strict list fl [default :[1 2 3]]

The word strict makes the macro require that the argument be transformable. Without it, if the argument is not of the expected type, it is just passed through as is.

Types do the following:

sub <number>

If the argument is a LoL, it is converted into a sub and that is passed as the argument. number is the number of formal arguments. Arguments are named a through z. More than 26 arguments is an error.

If the argument is not a LoL, it is passed unmodified, unless the argument is prefixed with strict in which case it is a fatal error.

(Note: earlier versions of Deck also allowed a list containing a single expression. This is no longer allowed. Finding a non-list entry in a sub argument will disqualify it from being converted to a sub.)

sub [arg1 .. argn]

Exactly like sub <number> above except that the list contains the names of the sub's formal arguments instead of a through z.

symbol

If the argument is a bare symbol, quote it. The procedure gets the symbol itself instead of the value of whatever variable (if any) it names.

If strict is present, it is an error if the argument is not a bare symbol

word

word is similar to symbol. However, it is an error if the argument is not the same symbol as the argument name.

This is intended to aid in adding words for synctac sugar such as the in in a for macro.

strict has no effect.

You can set the desired word as a default argument to make the argument optional.

list

If the argument is an unquoted list, it is first quoted. It is thus not evaluated at the call site. Any other kind of argument is evaluated normally. (Be aware though, that Deck expressions are lists and will thus be quoted.)

If strict is present, it is an error if the argument is not a list.

The default clause is used to set a default value. It consists of a two-item list containing the word default followed by a value. Legal default value types are:

If a default clause is used, then all of the following arguments must also have default clauses.

Objects

All Deck data are also objects (in the "object-oriented programming" sense of the word.) Every type is also a class and every object is an instance of a class.

The inheritance heirarchy is as follows:

    Object
        Struct
        Class
        List
        Stringlike
            String
            Symbol
            ByteArray
        Number
        Nil
        Quote
        Macro
        Procedure
        Method
        MethodCall

Be aware that I reserve the right to reorganize the heirarchy as I see fit, so it is not safe to depend on the inheritance structure. It is safe (well, relatively) to assume that the methods will not be removed from any objects' interfaces.

Classes are organized (mostly) according to their underlying implementation. Class Struct represents the traditional Object (methods and named fields) and will be the base class for most traditional OOP. (All of it, actually--the current implementation doesn't let you subclass anything except Struct or one of its subclasses.)

The usual object construction mechanism--the new method and function--only works on classes derived from Struct.

Overview

The Deck object system is modeled after Smalltalk's, albeit with a different metaphor. Smalltalk presents objects as receiving "messages" which it typically answers by invoking the method with the matching name. If there is no corresponding name, it invokes the method doesNotUnderstand: with the message as its argument. By default, doesNotUnderstand: will treat this as a fatal error but a class may override it and do other things, including mimic the expected behaviour or forward teh message to another object.

Deck's object system provides all of these capabilities. However, in Deck, the "message send" has been replaced with the "method lookup".

Here is a typical Deck method call:

    someObject->someMethod "first arg" :secondArg

Syntactic sugar treats the -> expression as a parenthesized infix expression, i.e.:

    (someObject->someMethod) "first arg" :secondArg

Looking at it like this reveals the semantics of what's going on. Calling a method has been split into two parts. The first is performed by the -> operator. It performs the lookup, querying someObject for a method named someMethod and returning a MethodCall object.

In the second part, the MethodCall gets called like a procedure (it is a callable object so that works).

Method Lookup Semantics

Method lookups are typically performed by the -> operator. This operator is actually a macro which usually expands to a call to function getMethod with the LHS and RHS values as arguments. This function does the actual lookup.

If it finds a method named by the RHS in the LHS object's class heirarchy, it returns a MethodCall object for that object/method pair. If there is no matching method, the method doesNotUnderstand is substituted. In addition, the method name is prepended to the argument list. Default behaviour for doesNotUnderstand is to exit immediately with an error message but classes may override this behaviour.

The only time -< does not expand to getMethod is if the LHS is the word super. In that case, it expands to getSuperMethod with self as the object being queried. This behaves identically to getMethod except that the search for a matching method starts at the superclass of self.

(Actually, -> expands to _::getMethod or _::getSuperMethod, both of which are (currently) aliases to getMethod and getSuperMethod respectively.)

MethodCall

Instances of MethodCall are callable objects encapsulating an object and one of its methods. Calling a MethodCall calls the method with the MethodCall's argument and the object as its self.

Like procedures, MethodCalls can be assigned to variables or passed around as arguments:

    var x = someObject->doSomething         # x = the MethodCall

    x 42                                    # Invoke the MethodCall

    foo x                                   # Pass the MethodCall to foo

The Metaclass System

As in Smalltalk, everything is an object in Deck and every object has a class. It follows, therefore that classes are objects and they themselves have classes (i.e. metaclasses), which also have classes and so on.

Deck handles this by having one metaclass, the class Class. Every class in Deck is an instance of class Class. Specifically, Class is an instance of itself. (Cue "I'm My Own Grandpa".)

Another consequence of this is that every class has exactly the same public interface. This means that Deck has no concept of a "class" or "static" method--that is, method of a class that is unique to that class and performs tasks related to it. Instead, Deck provides procedures, modules and singleton objects.

Class's methods and attributes it are provided to return information about the class itself. The common method new usually creates and returns a new instance of the class.

Defining Classes

Classes are defined using the macro class. class takes two or three arguments: a word used as the class name, an optional superclass and a LoL containing the body:

    class <name> <superclass> { ... }

name can be any legal name. A new const is created referencing the new class.

superclass can be any expression that evaluates to a class reference. It is the class from which the new class will be derived. If omitted, class defaults to using Struct.

(Bug: currently it is impossible to subclass any class not derived from Struct.)

body is a LoL containing field or method definitions. It is not a procedure body.

class (or more specifically, the internal function that is the target of its expansion) takes the arguments and compiles them into a class, returning a reference to it.

If a class defines a method named _init, it is called when the object is created and all of the arguments passed to the new method or procedure are passed on to it.

Here is an example:

    class Complex {
        readable real im

        method _init {r i} {
            real = r
            im = i
        }

        method add {other} {
            return [Complex->new (real + other.real) (im + other.im)]
        }
    }

Methods

Methods are functions which execute inside the object's scope. Fields are directly accessible to the method's code, as is the object itself via the local cost self.

A method definition looks pretty much exactly like a proc definition except that the word method replaces the word proc and that it has to reside entirely inside a class definition:

    class ... {
        method <name> <arg-list> <body> <final>?
    }

Methods may have final blocks but are not required to.

Unlike proc which returns nil by default, a method returns the object (i.e. self by default.)

Private methods

Methods whose names begin with an underscore (_) are private. Specifically, the -> operator will fail with an error message if the RHS starts with an underscore and the LHS is not self or super. In terms of protection, this is actually more like the C++ concept of a "protected" method.

Note that this is enforced by the macro that implements ->. It is possible to defeat the protection level using getMethod or getSuperMethod. However, doing so is a Bad Idea and people who do so are not very bright.

op_*

Most operators are implemented as macros that expand into matching method calls. For example,

    a + b

expands into

    a->op_Add b

Here is the full list of operators and matching methods:

    +       op_Add
    %       op_Mod
    *       op_Mult
    /       op_Div
    //      op_DivTrunc
    <       op_Lt
    <=      op_Lte
    >       op_Gt
    >=      op_Gte
    **      op_Pow
    |       op_BitOr
    &       op_BitAnd
    ^       op_BitXor
    ==      op_Equals
    <<      op_LShift
    >>      op_RShift

Finally, not really part of the same series is:

    @       at

Attributes

A common pattern in object-oriented languages is the getter/setter method pair. This is where an object provides access to some named internal object via a pair of methods, one to return the value and the other to set it. Smalltalk uses this as the primary way to access fields while some Java framework use it as a way to provide controlled access to data fields.

In Deck, a getter/setter pair plus its underlying field (not all of which need to be present) is called an attribute. Deck provides a special syntax for them.

Outside an object, reading and writing an attribute is done via the . operator.

Reading an attribute takes the form <object . <attribute> >. The . operator has a very high precedence and so usually groups by itself in a prefix expression. In an infix expression, the parser automatically wraps the items to the left and right of a . with round parens. For example:

    foo 42 c.real c.im
    a = [sqrt (c.real * c.real + c.im * c.im)]

The . is a macro that expands to a method call on its LHS object. The method name is the name of the attribute with _get appended to its name:

    method real_get {} {return real}

The method must take no arguments.

Setting an attribute looks like an ordinary assignment where the destination is the assignment expression. Specifically, it looks like obj . attr = value . For example:

    c.real = 1.01

In this case, the assignment operator expands the whole expression into a method call on the LHS of the .. The method takes one argument, the assignment value, and its name is the name of the attribute with _set appended to it. For example:

    method real_set {r} {return (real = r)}

Attribute methods may contain a docstring. If so, the documentation documents the attribute as a whole. If the attribute has both a getter and a setter, the getter is used as the source of the docstring. The setter is used only if the getter is not present.

The class may automatically add attributes to some existing variables.

Fields

Fields (aka "instance variables") are named variables that exist inside an object. They are visible only to methods of that object that were defined in its class. Unlike Smalltalk, a subclass does not have direct access to its superclass's fields. In C++ parlance, all instance variables are declared private.

Fields are declared with the word var followed by one or more names. Each is initialized to nil when the object is created. Any other intialization is done by the method _init.

For example:

    var real im

Alternately, one or more fields may be declared following the one of the words public, readable or writeable. If a field is declared with readable and there is no _get method for it, the compiler automatically creates one which simply returns the value of the field. Similarly, if it is declared with writeable and there is no matching _set method, the compiler creates a trivial one that stores its argument in the field and then returns it. public behaves as though both readable and writeable were present.

Examples:

    readable real im
    writable mode
    public foo bar

If the above isn't clear enough, the compiler will never replace a hand-written _get or _set method.

Fields are only allowed in classes descended from Struct.

The object macro

The macro object defines a singleton object. It creates an unnamed class and one instance which is referenced by a const it defines. It takes three arguments: a name, a superclass and the body.

The name is a Symbol that will be the object's name.

The superclass is an expression which evaluates to the superclass of the anonymous class. Unlike its equivalent in class, it is not (yet) optional.

The body is a LoL of exactly the same format as accepted by class.

The Sequence Protocol

Deck does not need to provide a formal mechanism for specifying a common interface the way Java and C# do. However, it is often useful to specify a set of methods to treat as an interface.

One such selection is known as the sequence protocol. It is the interface that indexable objects present to the world. Most List-oriented operations (e.g. for, map and reduce, etc) actually only use the sequence protocol. This allows any object to mimic a list simply by implementing the necessary methods.

The interface consists of the following methods:

at {index}

Returns the item at index index. Index must be positive and valid for all values between zero the result of size_get minus one.

This is normally called by the @ macro.

atPut {index value}

Stores the item value at index index, which has the same constraints as the index argument of at. This is normally called by the assignment operator with an @ expression as its LHS.

Read-only sequences must not implement atPut since the caller can test for writeability by testing for the presence of atPut.

size_get

Readable attribute returning the number of elements the object can hold.

isIndexible_get

Returns true if the object implements at least the readable part of the sequence protocol, false otherwise. Object provides a default implementation that returns false. Note that non-sequence classes may implement some or all of the rest of the sequence protocol with (somewhat) different semantics so long as isIndexible_get returns false.

maker_get

Returns a function which, when called with a number returns a new sequence of that size and whose type can hold any object the implementer can hold.

Object provides a default implementation which returns listSized.

Packages and Namespaces

Packages are another way to divide software into manageable chunks. A package consists of a Deck source file associated with a unique namespace. Names (variables and consts representing procedures, classes or ordinary data) defined in the source file are automatically put in the namespace.

The use macro imports a package into the current namespace, loading and compiling it first if necessary.

Names defined by the Deck command prompt or the initial source file occupy namespace Main.

Local vs Qualified names

Deck provides a single, flat global dictionary. All names take the form:

    <qualifier> '::' <baseName>

The qualifier may also contain '::', giving us nested namespace. For example:

    IO::StatResult                                  # StatResult in IO
    Office::WordProcessor::SpellChecker::Dictionary # Variable named Dictionary

Deck allows you to express any global name in its qualified form (unless it's private--see below).

Most names, however are unqualified. That is, they only contain the part after the last '::'. In this case, the compiler prepends the name of the current package (along with :: to the name. Thus, in package Smurf,

    smurfilicious = smurfy + 1      # smurfy and smurfilicious are globals

is resolved as

    Smurf::smurfilicious = Smurf::smurfy + 1

Of course, if you wanted to use the variable <smurfy> defined in package TrademarkInfringement, it would look like this:

    smurfilicious = TrademarkInfringement::smurfy + 1

and would expand to

    Smurf::smurfilicious = TrademarkInfringement::smurfy + 1

Usually, this is unecessary.

Private Names

A name beginning with an underscore (_) is considered private to the package that defines it. The use macro will not import any such names into the current namespace. For example, this will not work:

    use Foo with {_priv}        # Error!

In addition, the compiler will forbid the referencing of any qualified name if the name part begins with an underscore and the namespace part is different from the current namespace:

    package Foo
    
    puts Bar::_priv             # ERROR
    
    puts Foo::_priv             # legal
    puts _priv                  # equivalent to the previous one

There is no such restriction on namespaces themselves. However, the system-defined private toplevel namespaces have names containing only underscores by convention.

Defining Packages

A package is a Deck source file. Its first statement must be a package statement: the word package followed by the name of the package as a bare word. For example:

    package IO::FileUtil

This must be the first statement of the package. The package statement is the only purely syntactic construct in Deck. It is not executed; instead, the compiler checks for its presence and fails if the package name is not what it was expecting.

The word following package is used as the package's namespace. All unqualified names defined or referenced in the package are put into this namespace.

The package's filename path must correspond to the name in the following way:

1) Each '::' is replaced by a directory separator.
2) The last element must be the actual filename.
3) The filename must have the extension '.dk'.

These rules may be relaxed or other changes may be made in order to accomodate the limits of the filesystem.

Package Docstrings

If the following statement is a string literal, it is treated as a docstring for the package. For example:

   package IO::FileUtil
   
   '''Utility functions for manipulating files.'''

The usual requirements for docstrings apply.

Running Packages as Scripts

It is also possible to directly execute a package source file with the Deck interpreter in the same way you would execute a Deck program or script. If the interpreter finds a package declaration at the start of the program, it:

1) Sets the namespace to the package namespace.
2) Sets the global variable Sys::PackageRun to true.
3) Reads the docstring if present.
4) Executes the rest of the source file as usual.

No filename checks are done in this case. The source file name does not need to be related to the package name.

The global Sys::PackageRun is useful for detecting when a package is being treated as a script and changing its behavior. A package could, for example, run its own regression test in this case.

Loading and Importing Packages

Packages are loaded via the use macro. use takes the form

    use <name> <qualifiers>

where qualifiers is optional.

The name argument is a bare word. It is the name of the namespace of the package being imported. If there is no corresponding namespace already in existence, the name is converted to a filename according to the rules listed above.and the compiler searches for the matching source file and compiles it.

The global variable Sys::ModPath is an array of strings, each of which should be a path to a directory. These are the paths the compiler searches when trying to load a module.

For example,

    use Database::SQL::MySQL

loads "Database/SQL/MySQL.dk" if it exists and if Database::SQL::MySQL does not already exist.

Once it has been established that the namespace exists, use creates local consts with the same names as the public package items (possibly modified by the qualifiers section) and pointing to the same objects.

If the imported name already exists in the current namespace, it is a fatal error.

Qualifiers

use qualifiers consist of the word with, without or rename followed by a LoL. Each LoL sublist can be either a bare unqualified name or a bare unqualified name followed by = followed by another bare unqualified name. Examples:

    use VectorMath
    use VectorMath with {add; subtract; eigenValue}
    use VectorMath without {multiply; divide}
    use VectorMath rename {vectorAdd = add; vectorMinus = subtract}

A with qualifier allows you to specify exactly which symbols will be imported. Symbols named in the list will be imported. For = expressions, the new symbol will be named by the LHS but will reference the same object as the package's RHS symbol.

For example, this:

    use VectorMath with {plus = add; minus = subtract; eigenValue}

creates a local copy of VectorMath::eigenValue. It also creates local consts plus and minus which reference, respectively, the same object as VectorMath::add and VectorMath::subtract.

A without qualifier imports all public symbols in the namespace except for the ones in the list. It is an error to put a = expression in a without-qualified use statement.

A rename qualifier imports all public symbols in the namespace but will rename all symbols according to the = expressions in the list. A rename-qualified use may not have any simple bare name expressions.

A use with no qualifier simply imports all public symbols. It is identical to a without followed by an empty list.

Default and Reserved Namespaces

The following namespaces are defined and/or reserved:

_ (underscore)

This namespace is used for implementation-defined system features. You should never directly call or access anything in this namespace.

__, __::* (two underscores)

This namespace and its sub-namespaces contain the functions created by mproc.

Lang

This namespace contains objects considered part of the language or its standard library. It is automatically imported into every new namespace.

Lang::*

Namespaces beginning with Lang:: are reserved but currently unused.

Main

This is the default namespace. The Deck command prompt or the initial input file are both in this namespace.

Sys

This namespace holds documented, publicly-accessible system features.

Selected Objects

Most of Deck's procedures, classes and other objects are documented in the Library Reference. This section address those that are not or those that need a bit more explanation.

Sys::Argv0, Sys::Argv

Sys::Argv0 is string variable. It is set to the path to the Deck program being run. If Deck is running interactively, it is set to '-'.

Sys::Argv is a list of strings containing the script's argument list. It contains all arguments that follow the program being run in the Deck interpreter's command line.

Sys::ModPath

This is the list of directories to search when looking for a package to load. If the environment variable DECKLIB has a value, that value is split on the colon (:) and each substring becomes an entry in Sys::ModPath.

Then, if there is a directory named 'lib-deck' in the same directory as the Deck interpreter (and if the code can find it--not always possible), that is appended to the list.

The Perl Interface

The current implementation of Deck is in Perl. Sometimes it is necessary (or just really convenient) to get at the the underlying Perl interpreter. This section describes how.

Note: at some point in the distant future, Deck may move off of Perl and onto some other platform. In that case, these capabilties may go away.

perluse

perluse forces the Perl interpreter to load the given Perl module via the require function. The module is loaded into its own namespace. This should be harmless as long as the module does not have the same namespace as the Deck implementation (i.e LL).

A perluse consists of the word perluse followed by a bare word containing the name of the module to load. This name is passed to require unmodified which ensures that the module is loaded but does not export the module's public members to any other namespace.

For example:

    perluse File::Find

Accessing this from a perlproc will require using the full name.

perlproc

The macro perlproc defines a procedure written in Perl but callable from Deck. perlproc takes three arguments: the name, the argument list and a string containing the body text.

Like procs, name and argument are a bare word and an argument list. The third argument is passed unmodified to the Perl interpreter. It is a fatal error if Perl cannot compile it.

perlproc adds code to convert the formal arguments into comparable Perl data. Specifically, the following conversions are done:

    Number          -- Perl scalar number
    Stringlike      -- Perl string
    List            -- Perl array with all elements recursively converted.
    Nil             -- undef
    Procedure       -- The procedure reference.  Be careful with this.
    PerlObj         -- The wrapped Perl object.

The return value is also converted back to a Deck type, specifically:

    Deck references -- passed back unmodified.
    Arrays          -- List with elements recursively converted
    undef           -- nil
    Perl numbers    -- Numbers
    Strings         -- Deck strings.
    Unblessed object reference
                    -- The target of the reference recursively converted.
    All others      -- Wrapped in a PerlObj.

Obviously, it is a Really Bad Idea to use perlprocs to modify the state of the Deck runtime.

PerlObj

PerlObj is a Deck object that contains some Perl object. It is used to pass information to code written in Perl and running on the underlying Perl interpreter.

You may get a PerlObj back from Perl code. You can do nothing with it in Deck except hand it back to a perlproc.

On the Perl side of things, a PerlObj is simply a blessed array ref to a 1-element array containing some Perl object. They are created by calling LL::PerlObj-new>:

    perlproc _get_perl_hash {} '''return LL::PerlObj->new({});'''

perlprocs will return PerlObj instances unmolested. Passing a PerlObj to a perlproc will strip off the wrapping automatically.