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.)
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:
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. This lets you do a limited form of memoization easily:
(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.
The const return
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:
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:
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.