POOL – Thoughts on its syntax

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 the CODE returns (or emmits) VALUE
  • CODE #~> VALUE means that the CODE returns (or emmits) something equivalent to VALUE
  • , ... 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 be nil 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 booleans 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 or 10f or 1.0E1 or 1fE1 (the number behind E is the exponent x in zEx = (10 ** x) * z) or (of course) Float.new(10)
Integer
The syntax is equivalent to the Float syntax: 10 or 1E1 or Integer.new(10).
If an Integer 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 or TrueClass.new(), false or FalseClass.new().
More information in the section about boolean expressions.
Nil
nil or NilClass.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 or NullClass.new() acts like false in boolean expressions.
Is the value of known but unset variables. May be removed later.
Range
BEGIN..END or Range.new(BEGIN, END), BEGIN...END
or Range.new(BEGIN, END, false). Range objects created using ..
run from the beginning to the end inclusively. Those created using ...
exclude the end value. BEGIN and END can be of type Integer, Float or Literal.
RegExp
/[Regular expression]/ or RegExp.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 or not EXPR
Negates the following boolean
EXPR && EXPR or EXPR and EXPR
Logical and.
EXPR || EXPR or EXPR 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 DEPTth 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.

Author

  • Johannes Bechberger

    Johannes Bechberger is a JVM developer working on profilers and their underlying technology in the SapMachine team at SAP. This includes improvements to async-profiler and its ecosystem, a website to view the different JFR event types, and improvements to the FirefoxProfiler, making it usable in the Java world. He started at SAP in 2022 after two years of research studies at the KIT in the field of Java security analyses. His work today is comprised of many open-source contributions and his blog, where he writes regularly on in-depth profiling and debugging topics, and of working on his JEP Candidate 435 to add a new profiling API to the OpenJDK.

    View all posts

New posts like these come out at least every two weeks, to get notified about new posts, follow me on Twitter, Mastodon, or LinkedIn, or join the newsletter: