This is a guide to the Pluvo programming language for people who already know what makes programming languages tick. If, after reading this guide, you have any further questions, feel free to ask them on the Pluvo mailing list.
Pluvo programs consist of commands, separated by line breaks. Commands consist of a function and its whitespace separated arguments. Parens are not used. For example, the "say" function prints its arguments to stdout, separated by spaces:
say "Hello, world!"
In this command, "Hello, world!" is a String. Other important datatypes include Number, which is a decimal number not an int or float; Table, which is an array and hashtable hybrid; URI, which also doubles as a file accessor; and Regexp. There are also code blocks, but those are implemented as Tables of commands. A program is a code block itself.
One of the most important features of Pluvo is that in commands, the function does not have to come first. So, for example, commands using the + function (used to add numbers) would normally have to be written with the plus first in most other command-oriented languages:
+ 3 5
And indeed this is legal in Pluvo, but Pluvo will also do something special: it searches through a command and uses the first callable argument as the function. So the preferred way to do the addition above is as follows:
3 + 5
The process of finding the function to use is called coördination. This means that operators are no different from any other function. For example, the = operator sets its first argument to its second argument, and is usually written as follows:
eight = { 3 + 5 } say eight
The part in curly brackets is a code block. The last evaluated value of a code block is returned from it, so the variable eight will now be set to the number 8. Note that is is an equally valid but unpreferred way of writing the above:
= eight { + 3 5 } say eight
And also that "def" is a synonym for "=":
def eight { + 3 5 } say eight
Coördination enables Pluvo syntax to be very natural, but because of its power it also opens up the door for very messy code. A function's documentation should advise on its usage, but usually common sense is just as helpful, and the final decision is down to the whim of the programmer.
There are lots of built in functions that you can use, and accessing them is made trivial through the use of namespace sigils.
There are two namespaces in Pluvo: the program variable space, for the language to use, containing builtin functions and so on; and the user variable space, for everything else. A variable name must match the regexp [A-Za-z]+, examples of which we've already seen.
To reference the program variable space unambiguously, you can prefix the variable name with an "@" character; to reference the user variable space unambiguously, you can prefix the variable name with a "$" character . These prefix characters are called namespace sigils, somewhat à la perl, though their use here is very different. If you leave off the namespace sigil altogether, then the variable will be searched for in the user variable space and then the program variable space, in that order.
So, for example, "say" is a builtin function in the program variable space, and as such to reference it unambiguously we can use "@say" instead:
@say "Hello, world!"
But were we to use "$say" instead, we would get an error: there is no such user variable, it's only in the program variable space. But if we set $say ourselves, we can then use it by referring to "$say" later on:
$say = "Hello, world!" @say $say
In fact, because leaving the namespace sigil off will mean that the user variable space is searched through first, the above could be written as:
$say = "Hello, world!" @say say
Confused yet? Examples like the above are shown as edge cases, and should be avoided in clear code. What the prefix system allows you to do is use short and unadorned variables in places where it's obvious what's going on, but to use the prefixes in contexts where it's not so clear so as to make it unambiguous.
Normally you will be writing variables without any namespace sigils at all, with one major exception: library functions. In Pluvo, there are builtin functions (like "say"), and there are functions which only exist in the standard library. These latter functions are not loaded into memory when a program starts, for speed and to cut down on memory usage. But unlike, for example, C where you have to #include external functions, or Perl or Python where you have to use/import them, in Pluvo you can just reference them as you would do any normal function, with the exception that you must use the @ namespace sigil.
So for example, "say" is the command to print to stdout, and can be used in both of the following ways as long as there isn't a user variable of the same name set:
say "Hello, world!" @say "Hello, world!"
On the other hand, @lines is a library function, and you can only reference it with the @ namespace sigil. It loops through a Table (that is, an arry or list) of filenames providing the lines from each file. For example:
@lines ("test.txt" "input.txt") { say line }
The 1st argument, the Table, starts with an open parenthesis, has two strings in it (the names of files), and ends with a close parenthesis. The line variable is automatically set by @lines and passed to the 2nd argument codeblock.
Though there are lots of syntactic tricks and shortcuts like coördination, namespace sigils, and (as we'll see in the next section) block indentation, it really helps to try to remember what is actually going on in the language.
Functions in Pluvo are, more specifically, closures. A closure is a set of function code, basically a code block, and the lexical environment at the time of its creation. You don't have to worry about the mechanics of that particularly, since the result is much like you get in most programming languages. Here's how to create a simple function:
add = (p q) { p + q } say { add 35 150 }
This makes a function called "add" which takes two arguments, p and q, and will return the value of p plus q. The say line shows an example of how to call add, and will print "185" to stdout. Note that blocks, even if they only contain one command, can be spread out over multiple lines since you can put an optional amount of line breaks before and after any command:
add = (p q) { result = { p + q } say "The result is: $result" }
The example also shows that variables can be interpolated into strings using the $... syntax. Brackets are mandatory if the variable follows or precedes a word character, e.g. "Test${result}".
Remember that the program above contains just a single command, whose function is "=" and has three arguments: add, the Table (p q), and a code block. When the last argument of a command is a code block, you can indent it as follows:
add = (p q) result = { p + q } say "The result is: $result"
In other words, the brackets are there optional, as in Haskell for example. Note that you can use blocks inside indentations, but you can't use indentations inside blocks. Functions can nest within one another:
show = () seven = () { 5 + 2 } say "Seven:" { seven }
Here, note that as seven is a function it has to appear in a block to be called. If seven were not a function, we would not have to, in fact would not be able to, call it:
show = () seven = { 5 + 2 } say "Seven:" seven
Of course that could be done with interpolation too. So, what if instead of evaluting { 5 + 2 } and assigning it to seven, we want to assign the block { 5 + 2 } to seven? We have to use the following syntax:
{seven} = { 5 + 2 }
The variable seven is now set to a block, which is basically a closure without any argspec. This is different to having () as an argspec: it has no argspec at all. This isn't really useful to know at this stage, but gives a glimpse of the Way of Pluvo, and opens the door for some more involved topics concerning the way functions and code blocks work.
Documentation is very important in Pluvo. It basically models itself on Masahide Kanzaki's three-tiered annotation system, that he developed for use with XSLT. Simple comments can be done by prefixing a line with "#":
# Print Hello, world! to stdout say "Hello, world!"
Simple comments should be used for asides, issues, and other notes that shouldn't appear in documentation about a particular function or block. For documentation, on the other hand, you can use the appropriately named documentation comments. There are two kinds of documentation comments. There's a multiline syntax, which starts with "% " (that's a percent sign and a space) and ends at the first blank line; and there's a single line syntax, which starts with "%% " (two percent signs and a space) and runs to the end of the line.
Multiline documentation comments are especially great for giving documentation about an entire program. Here's an example showing both multiline and single-line documentation comments:
#!/usr/bin/env pluvo % test.pvo - Test Script Author: Sean B. Palmer, inamidst.com test = () %% Print out hello world say "Hello, world!"
Note that there is also a simple comment in here too: the shebang counts as a simple comment.
You can't use any type of comment at the end of command; they must exist by themselves on a line. This is because both simple and document comments are actually commands. They do nothing except for return the current default return value, so that the following still works as expected:
test = () "Hello," + " world!" # Will return "Hello, world!" say { test }
Document comments, with % and %%, are intended for automatic extraction by documentation scripts. Something that helps a lot here is that everything structural in Pluvo, which is to say commands and blocks, are actually made from Tables. When you see a block such as:
{ 3 + 5 }
That's actually implemented as a command Table inside a block Table. So blocks are also rather useful as a simple way to write nested Tables, in a straightforward format.
We've already met the String, Number, and Table types. There's also a couple of other handy types: Regexp and URI. Regular expressions are delimited with the "/" character, as is common in many languages:
re = /test/
In this example, the variable re is now set to the regular expression that matches the string "test". In fact, in Pluvo regular expressions aren't anchored, so it's more of a search than a match. If you want to make it only match at the beginning of the string, you can use the ^ metacharacter, e.g. /^test/.
You can use a Regexp as a function. It takes two args: the String to match, and a block to run if the string matches. For example:
input = "Here is an example string." /ring/ input { say "This matches ring: $input" }
You can interpolate strings into Regexps:
vowels = "[aeiou]" /b${vowels}+ng/ "boing" { say "boing!" }
You can also interpolate into URIs. URIs are the only way to do I/O in Pluvo, so they double up as file accessors too. They use triangular brackets as delimiters:
test = <test.txt>
So the test variable is now set to a URI object representing the file with the filename "test.txt". The method accessor character in Pluvo is a full stop, ".", like in most modern language. In keeping with the Way of Pluvo, though, "." is actually a function itself. Here's an example of using the read method of a URI to read the bytes from a file and print them out using the "out" command. The "out" command differs from "say" in that it doesn't automatically append a newline:
out { <test.txt>.read }
Note again that the block could be written as { . <test.txt> read
}
, but that it's standard practice to coördinate the method
accessor function, for readability. Also, the read method as in most languages
is not an efficient way to do I/O since it means buffering the whole file. To
do I/O properly, you'll probably want to use loops.
The "for" function takes three arguments: a variable name, a Table or thing to loop over, and a block of code to execute. In fact there are a couple of handy variations, but this is the basic pattern. For each item in the Table or iterator (2nd argument), for will assign the variable name (1st argument) to it, then pass it to the block (3rd argument) and run it. For example:
for name ("Alice" "Bob") say "Hello, $name!"
URIs are also iterators, and will be treated as though they're Tables containing all of their lines. For example, to output all the lines in the file test.txt:
for line <test.txt> out line
The main two variations are that, first, you can use "in" as the 2nd argument for extra readability:
names = ("Alice" "Bob") for name in names say "Hi $name!"
And, secondly, that that you can omit the variable name (and "in") altogether and for will pass whatever items it finds in the iterator as the variable "arg" to its block argument:
for ("Alice" "Bob") say "Hi $arg!"
Two other features of for are that the thing to iterate through can be returned from a block; and that the variables to assign to can be a Table of variables, if Table objects are returned from the iterator of the same length. To demonstrate both of these features:
for (i line) in { @enumerate <text.txt> } out "$i: $line"
Note that @enumerate is a library function, not a builtin, so we need to refer to it with the namespace sigil, the "@" character.
Normally when you bind a variable, its type doesn't matter. You can make test be a String one moment, then a Number the next:
test = "The first Zeisel number is: " say test test = 105 say test
This is known as dynamic typing. But Pluvo also allows optional strict typing, which means that a variable can only take particular types as its values. To strictly type a variable, simply put the type before the value. Here's an example of a typed variable assignment and then a reassignment which works because the new value is of the required type:
hello = @String "Hello, world!" hello = "Hi, world!"
On the other hand, here's an example that'll give an error, "Error: expected String when setting hello", because we try to assign a Number to a variable that's strictly typed as being a String:
hello = @String "Hello, world!" hello = 5
At the moment Pluvo is quite limited in what types it will let you use, but eventually it could be expanded to even allow blocks to do type checking, in keeping with the behavioural duck typing aspect that prototype based object orientation engenders.
Object orientation in Pluvo is done using prototypes. As Wikipedia says, "Prototype-based programming is a style of object-oriented programming in which classes are not present, and behaviour reuse (known as inheritance in class-based languages) is accomplished through a process of cloning existing objects which serve as prototypes." In Pluvo, prototypes are closures, i.e. functions.
For example, let's say we want to create a new Dog prototype. We make it just like we would any other function, but we can do something extra: we can give it methods. These methods can then be accessed on any "instances", i.e. copies, of the prototype. Here's how we could go about defining the Dog prototype:
Dog = (name) method speak = (bark) say "$name says: " say "$bark! $bark! I am a dog."
Note the two levels of variable binding. The variable name is a prototype-wide variable that can be accessed from anywhere within the prototype and is passed at the time of "construction", i.e. when the prototype is copied. On the other hand, bark is a method variable and is only available for use inside that method. It is passed when the method is called. Here's an example of copying the prototype and calling a method on the copy:
rover = Dog("Rover") rover.speak "WOOF"
This will print out:
Rover says: WOOF! WOOF! I am a dog.
Note how close this syntax is to the normal way of doing object orientation, and how clean the scoping is. There are no special prefixes or objects needed to access instance variables from within a method; you just refer to them by name. The "method" function is used to get the scoping right: if you just use equals, the method may actually be bound in a parent scope of the prototype, rather than in the scope of the prototype itself.
That's it for this little introduction to Pluvo, but if you want to learn more the best way to do that at the moment is to look at some examples. There are three good places to do this: the basic functions reference, test suite, and the examples directory. In the test suite, it's best to look at the higher numbered tests since the earlier tests were done when the language was still growing, so they don't always use recommended style.
As well as that, there's a Pluvo mailing list on which you can ask questions.
Sean B. Palmer, inamidst.com