Deck is a modern general-purpose programming language. It features:
First-class functions
Closures
Dynamically-typed variables
Automatic memory management and memory safety
Macros
List-structured source code
It also has:
Infix expressions and a readable syntax
A pervasive, modern object system
Namespaces and packages
Continuations
An innovative exception system
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.)
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.
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.
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.
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.
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.
Deck supports the following data literals:
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.
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 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 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 $
.
Deck parses the following characters as special delimiters:
{
and }
, the curly brackets
(
and )
, the round brackets
[
and ]
, the square brackets
:
, the quote delimiter
=
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 (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 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.)
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.
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.
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.
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.
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.
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 (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:
In addition, it must take one of the following forms:
name = ...
A name that is not an unescaped operator followed by the word =
and the rest of the expression.
name @ *term* = ...
A name that is not an unescaped operator, followed by the word @
, followed by some term (either a literal, word or bracketted expression) followed by the word =
and the rest of the expression.
name1 . name2 = ...
A name that is not an unescaped operator followed by .
followed by another name that is also not an unescaped operator followed by =
and the rest of the expression.
term1
(|| or &&) term2
...
An odd number of terms separated by one of the boolean operators ||
or &&
.
=
...
Any LoL line beginning with a single =
will be parsed as infix.
For a line to be considered a prefix expression, it must either:
Contain no unescaped operators
Be a var
or const
expression with a =
as its second argument.
Each non-empty, non-comment line must meet one set of requirements. If it does not, it is an error.
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.
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:
Parameter names (the words between the |
symbols) may not be operators.
Any number of spaces or newlines may be used between tokens up to the last |
.
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.
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.
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.
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
.
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
.
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
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 is the abstract base class of all objects in Deck. It cannot be instantiated.
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.
The following types are visible but are not commonly used.
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.
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.)
A MethodCall
represents a method lookup on a specific object. It is callable.
Stringlike
is the abstract base class for some types that contain sequences of binary data. It cannot be instantiated.
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.
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
.
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
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.
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.)
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:
var
statement.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".)
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:
var x y z
var a \= b
(Obviously, this doesn't work for 'const'.)
var y = 42
const PI = [compute_pi]
var [a b c] = :[1 2 3]
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.
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.
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 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.
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
.
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 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.
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.
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 (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 (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.
A perlproc is a native Perl function accessible from Deck. It is described in more detail below.
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 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 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:
Turn a LoL argument into a sub.
Automatically quote a bare symbol or list
Ensure that a precise symbol is in the argument list.
Set a default value for an argument.
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.
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:
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.)
Exactly like sub <number> above except that the list contains the names of the sub's formal arguments instead of a
through z
.
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
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.
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:
numeric literals
string literals (but be careful with double-quoted strings)
quoted lists
quoted symbols
nil
If a default clause is used, then all of the following arguments must also have default clauses.
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
.
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 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.)
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
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.
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 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.)
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.
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
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 (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
.
object
macroThe 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
.
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 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
.
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.
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.
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:
These rules may be relaxed or other changes may be made in order to accomodate the limits of the filesystem.
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.
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:
Sys::PackageRun
to true.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.
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.
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.
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.
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 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.
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 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
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.
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
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.