This article is an collection of my thoughts on the syntax of the POOL programming language. It may evolve and be updated (and written) over time. Furthermore it’s like a blue print for my later work on the parser and the grammar, therefore it’s written like a language reference. It’s my first article of this kind (and its using terms from Java, JavaScript and Ruby), but I hope it’s none the less readable and gives a good (and deep) overview of the language.
Notes on the formatting of this article
- Uppercased words or words in brackets are placeholder.
CODE #-> VALUE
means that theCODE
returns (or emmits)VALUE
CODE #~> VALUE
means that theCODE
returns (or emmits) something equivalent toVALUE
, ...
means that the pattern used before the comma is repeated
Syntax basis and philosophy
The syntax of the language is heavily based on the syntax of JavaScript (JavaScript is even to some extend valid pool code) and Ruby as JavaScript has an easy syntax and I like Ruby (it’s syntax is great and easy to understand). Therefore the syntax for simple comments, assignments, property access, method calls and primitive data types (I mean numbers, arrays, strings and booleans) is the same as in JavaScript (and Ruby) and so I assume that you understand this parts of the syntax when used here, I set it for now at side and cover them after core parts of the syntax later.
The philosophy of this syntax to emphasize that everything (yes, everthing) is an object and to allow as much flexibility as possible.
Objects
Objects are the base of the language, so their syntax one is really simple (an bourroughed from JavaScript):
obj = {"KEY": [value or expression], ...}
, if KEY
is a valid variable name or not a string obj = {KEY: [value or expression], ...}
or an empty object empty_object = {}
.
Elements of the object can be accessed either with obj["KEY"]
or if KEY
is a valid variable name obj.KEY
. Not existing properties have the value nil
, if a not existing method is called on this object, it’s undefined_method
function is called (as you may have recognized, I use the term method
and the term function
mostly as synonyms). Inside the object, you can directly access its properties with just KEY
if KEY
is a valid variable name (but later more on this topic).
Every object has some properties and methods built in (which can be overriden, but be really careful), some of them have aliases for convenience:
$size
- the number of key value pairs in the object minus the built in ones (and the ones beginning with
$
. function $contains(KEY)
- checks whether the object contains the
KEY
function $delete(KEY)
- deletes the
KEY
value pair this
or$this
- the current object
$parent
- the object this object is a part of – the parent object.
@KEY
is a short cut for$parent.KEY
$module
- the module this object is part of (
$module
and$parent
could possibly be the same) $type
or$class
- type of the object:
obj.$type == "Object"
: the object is a simple objectobj.$type == "Module"
: the object is a moduleobj.$type == "Function"
: the object is a functionelse the class name of object, alias for$class.name
$class
- Class object this object is an instantiation of.
Normal object: Object, …
Class object: Object function undefined_method([method name], [parameter object])
- This method is called whenever a not existing method is called on this object (e.g.
obj.not_existing_method(34)
). The default implementation throws an exception. $private
- An object containing the private properties of this object.
$protected
- An object containing the private properties of this object which are also accesible from subclasses (is only important in class definitions)
$module_private
- An object containing the properties accesible from within the same module.
The object inherits some more built in methods and properties which will be described in a detailed object reference.
Modules
Modules are simply objects with a different syntax, the ability to import functions and objects of another module in to it via the built in import([module reference or module name])
function (if there is no such module, the interpreter searches and requires the appropiate file if their is one, if not throws an error). The code inside a module is excecuted when it’s loaded or required (via function require("FILE_NAME")
, if FILE_NAME has no file ending, the function adds .pool
). Modules can be nested in side one another, but not into objects.
Their syntax is easy:
module MODULE_NAME {
[CODE]
}
or in a more literal and Ruby like style:
module MODULE_NAME
[CODE]
end
Example:
module Test.Module //Every valid reference is alowed. Just Test has to be a module (it will be created if is doesn't exist)
import(IO) //IO is a built in module
obj = {"3": 3} //creates an object, that can be accessed from the outside via Test.Module.obj
end
Every code outside a module (and modules outside of other modules) are placed into the STDModule
.
As Modules are (after they’re loaded) some kind of objects they inherit the built in functions and properties of them.
Functions
Define a function:
function [variable name]([parameter list]){
[CODE]
}
Define a closure:
[variable name] = function([parameter list]){
[CODE]
}
Or in a shorter way
[variable name] = {|[parameter list]|
[CODE]
}
or (of course)
[variable name] = do |[parameter list]|
[Code]
end
||
can be omited if the closure has no parameters.
Closure and functions are over all the same, in fact the only difference between the two is, that functions allow back referencing (the interpreter recognizes them during parsing). Therefore function can be used as closures and so everything I say about functions will (if I say nothing explicit) also apply to closures.
The parameter list: [parameter (a normal variable name, the given value is assigned to)], [another parameter], ...
Or if you want to set a default value for one parameter:
[parameter]: [default value], ...
If you use an expression instead of an explicit value, the value will be evaluated every time the default value is used. But if you put a ~
in front of the colon (~:
) the value will only be evaluated once and then cached (so don’t use $caller
there). It’s also important, that the scope of such an expression is the function scope (you’re able to use $caller
, $this
, …).
If you want to auto clone the given value of a parameter (I know, it’s the other way aorund in PHP but hopefully it’s not to hard to memoize the difference) you can prefix PARAMETER
with an ampersand (&PARAMETER
).
You’re able to preset a return value for a given set of parameters of a function:
FUNCTION(3) = 4
Built in variables of of the function object:
this
or$this
- the current function object
$this.$caller
or simply$caller
- the caller function object
$local
- the local variables
$param
- the current parameter object,
$param = {[first parameter name]: [value], ..., $other = []}
.
If a parameter isn’t given, it’s value is nil, if a parameter is given without a corresponding parameter name, it’s added to the$other
array
Please don’t use constructs like the caller of the parent object, as it’s value is going to benil
and therefore is useless. $default
- the parameter default values,
$default = {[first parameter with default value]: [default value], ...}
$ast
:- it will probably be added later and will allow the modification of the Abstract Syntax Tree of the function body.
$yield
- Closure the function is called with. Short for
$param.$yield
Instead you only using the keywords function
or def
you’re also able to create special function types by pefixing these words, [prefix]function
or [prefix]function
(they can also be combined). Special types and their prefixes are the following:
sef_
- sef stands for “side effect free”. This function type caches the results in
$cache = {[parameter object]: [function result], ...}
(could be changed in the future) pdf_
- pdf stands for “preset default value”. It’s a function type that returns the same function with the given parameters preset (their default values, to be correct), if other parameters (with no default values) aren’t set.
They’re simply short cuts for setting the value of the property $is_pdf
or $is_sef
to true
accordingly.
Example:
pdf_function pow(base, exponent) { base ** exponent }
pow_two = pow(exponent: 2) #-> {|base, exponent: 2| base ** exponent}
pow_two(3) #-> 9
Return values
Values are returned explicit via return VALUE
or implicit (the value of the last expression is returned).
Call a function
You call a function as you would expect via FUNCTION([first parameter], [second], ...)
. You’re able to replace [first parameter]
with [parameter name]: [parameter value]
to set the value of a special parameter out of the normal order (e.g. FUNCTION([first parameter], [parameter name]: [value], [second parameter])
). To pass a nil
value for a parameter, you can either type nil
directly, or omit the value (e.g. FUNCTION([first parameter],, [third parameter])
, FUNCTION([first parameter], [parameter name]:, [third parameter])
). A nil
value is replaced by the default value of the parameter (if there’s one).
You can optionally pass an code block stored in the $yield
variable in the function:
FUNCTION(PARAMETERS) CLOSURE
arr.search() {|a, b| a > b }
Variable names
Valid variable (and function and module) names consist of an alphabetical or _
character or a dollar sign optionally followed by alpha numerical or _
characters and optionally ended by a ?
or !
character (or as an regular expression /$?[a-zA-Z][a-zA-Z0-9_][!?]?/
).
Variable names are case sensitive and only allow ASCII characters (no UFT8).
The following are some naming conventions (partly coming from Ruby, JavaScript and Java and not enforced by the interpreter):
– functions returning boolean
s end with a ?
– only functions that modify the object, they’re called on, and with a !
– built in functions and properties of objects, functions, etc. start with a $
, some have aliases for convenience that aren’t (e.g. $this
has the alias this
).
– class and module names are camel cased, the other variable names not (using _
instead)
Comments
As I said at the beginning of this article, you’re able to write comments as you’re used to in JavaScript or Ruby, both syntaxes are supported:
One line comment:
//COMMENT TEXT
or #COMMENT TEXT
Multi line comment:
/*
MULTILINE
COMMENT
*/
or
MULTILINE
COMMENT
Documentating of modules, classes and functions
Documentation consists of multi line comments in front of and is modeled after JavaDoc.
Structure of the comment text:
SHORT DESCRIPTION
OPTIONAL DETAILED DESCRIPTION
OPTIONAL INFORMATION
(the second free line is optional)
Optional information tags (have to be in different lines):
– @version VERSION
:
version of the class or etc.
– @license LICENCE
License the code is licensed under.
– @author AUTHOR
Author of the code
Function specific tags:
– @param PARAMETER_NAME TYPE DESCRIPTION
information for the parameter PARAMETER_NAME
, if you want to write more than one type, just seperate the types by |
characters (e.g.
TYPE1|TYPE2
.
- @returns TYPE description
information about the return type of the function
Variable assignment
=
(in object definitions :
is used instead but it works similar) without a prefix is asimple assignment by reference. The allowed prefixes are the following:
– ~
: lazy assignment
the expression at the right side is evaluated the first time the value of variable is really used
– &
: assignment by value
assigns the cloned value of the right side expression to variable
– :
: final assignment. (set is_final of the variable object to true)
- ?
: sets the value of the variable if it's current value is nil or null, can't be used to assign a value to a set of parameters of a function.
Assignements can be cascaded via commas: [assignment], [assignment]
Variables
:VAR : variable object containing the value of VAR
properties:
set : called when the value of the variable is set
get : called when the variable is accessed
is_final : true if constant
value : value of the variable
Class
class CLASS {new:: function(){}, super: class obj, static: {}, possible: array_get [i], push [<<], pop [>>], add [+], sub [-], mul[*], div [/], mod [%], pow [**]}
for usability reasons some of the magic is performed by the interpreter indirectly, to allow a simpler method definition. I can’t write much about that, as it depends strongly on the implementation. New objects are created via CLASS.new(PARAMTER)
TODO write more text…
Built in types
Built in types are types with a native syntax, like Strings or Numbers
(but without modules, classes, object and functions in this context). The following lists them with their native syntax and a short description.
Float
- Syntax to create an floating point number 10:
10.0
or10f
or1.0E1
or1fE1
(the number behindE
is the exponentx
inzEx = (10 ** x) * z
) or (of course)Float.new(10)
Integer
- The syntax is equivalent to the
Float
syntax:10
or1E1
orInteger.new(10)
.
If anInteger
is used in a expression with a float it will be used as a float. In general float values are rounded to smaller integer if they are converted to an integer. String
- Strings are created via
"[characters, can span across lines]"
or'[characters]'
.
You can insert code into the string (it’s result will be actually inserted) via#{[CODE]}
into"
Strings (backslash expression like\n
are also only converted in this type of String
This code will be treated as a closure:"TEXT #{[CODE]} TEXT"
is equivalent to"TEXT" + {[CODE]}() + "TEXT"
Literal
- Essentially Strings consisting of one character, no new class but used in the following as a term.
Boolean
true
orTrueClass.new()
,false
orFalseClass.new()
.
More information in the section about boolean expressions.Nil
nil
orNilClass.new()
acts like false in boolean expressions.
Is the value of an unknown variable or of a function or expression returning no value.Null
null
orNullClass.new()
acts like false in boolean expressions.
Is the value of known but unset variables. May be removed later.Range
BEGIN..END
orRange.new(BEGIN, END)
,BEGIN...END
orRange.new(BEGIN, END, false)
. Range objects created using..
run from the beginning to the end inclusively. Those created using...
exclude the end value.BEGIN
andEND
can be of type Integer, Float or Literal.RegExp
/[Regular expression]/
orRegExp.new('[Regular expression]')
.
Is a regular expressions, strings can be matched against.
The actuall regexp syntax is determined during implementation.
Boolean expression
Every value except null
, nil
and false
evaluates to false
all other values evaluate to true
. There are different types of modifiers in boolean expressions, listed below in their precendence.
EXPR
or(EXPR)
- The value of the expression, which could also be a single value
!EXPR
ornot EXPR
- Negates the following boolean
EXPR && EXPR
orEXPR and EXPR
- Logical and.
EXPR || EXPR
orEXPR or EXPR
- Logical or.
EXPR xor EXPR
- Exclusive or.
EXPR == EXPR
or(EXPR).equals(EXPR)
- True if both values are the same (no automatic casting, therefore no
===
is needed) EXPR =~ EXPR
- Used when only if one of the to values is an regular expression, the other value is tested against, or if the first value is a range and the second value is included in the range
Control constructs
EXPR is a boolean expression. Variables defined in the code blocks are only visible in this code block, but variables of the sourrounding (module, class, object, function) scope can be accessed without any prefix.
An important information is that such constructs return the value of the last expression of their code and that you can break out such a construct via break
(use break DEPT
if you want jump out of the DEPT
th parent construct), returns the value of the expression before the break
.
Conditions
There are several ways to define a simple condition:
if (EXPR){
[CODE]
}
or with only a single of code:
if (EXPR) [single line of code]
or (maybe not implemented)
[single line of code] if (EXPR)
More advanced conditions with if and else:
if (EXPR){
[CODE]
} else {
[CODE]
}
or with only a single of code:
if (EXPR) [single line of code]
else (EXPR) [single line of code]
the same with several conditions:
if (EXPR){
[CODE]
} else if (EXPR) {
[CODE]
} else {
[CODE]
}
You can replace if
with unless
if you want to execute the code block when the boolean expression is false.
The ternary version is also allowed (as you know it): EXPR ? CODE : CODE
.
Switch-case
Switch case statements in POOL are similar to them of other languages like JavaScript, so I'm only going do cover the differences.
switch ([Value or variable, VAL]){
case [EXPR]:
[CODE]
break;
case [EXPR]: [CODE]; break
case~ [EXPR]:
[CODE executed if EXPR =~ VAL is true]
break
case+ [EXPR]:
[CODE executed if EXPR is true]
break
else:
[CODE executed if no other case-condition matches]
}
If switch
is replaced with switch+
breaks will be auto inserted after each case statement.
Loops
As you know and love them.
For loop
for ([assign a variable or not]; [condition tested after each loop cycle, or not (then you've got and endless loop)]; [statement executed after every loop cycle]){
[Code]
}
While loop
while(EXPR) {
[CODE]
}
or if you want execute code while the expression is false:
until(EXPR) {
[CODE]
}
Begin-while loop
begin {
[CODE]
} while (EXPR)
or if you want execute code while the expression is false:
begin {
[CODE]
} unless (EXPR)
Infinite loop
loop {
[CODE]
}
Exceptions
The specific exception classes are defined during the implementation. All exception classes extends the class Exception
.
Exception throwing
throw [exception class].new([parameters])
Please add a @throws
tag to the appropriate comment.
Exception handling
Exceptions are handled with a Java like syntax:
try {
[unsafe code]
} catch ([Exception class, if you catch more than one, seperate them with a vertical bar] [variable the thrown exception is assigned to]) {
[CODE]
}
You can optionally add a finally { [CODE] }
after the catch statement, CODE
will than be certainly executed and you can add serveral catch statements after the first.