Language
Arturo is a very simple language. Even without any prior experience, I estimate it would take you roughly half an hour before you are comfortable enough to write your first program.
Here, you'll find everything you may need to know about the language (and perhaps a bit more). In a single page.
Introduction
Arturo's syntax is probably as easy as it could get. Basically, you could say: there is no syntax.
Let's try to resume some key points of Arturo's no-syntax:
- Code is just a list of words, labels, symbols, values and attributes (you'll learn what all this is about very soon!)
- Code can be grouped into blocks (that is: a list of words, labels, symbols, and values within square brackets:
[ ... ]
- A block has no meaning until it is given one, or interpreted within some specific context, that is:
[ lorem ipsum ]
is perfectly valid Arturo code, until you try to "run" it - where the interpreter will complain that it has no idea whatlorem
andipsum
is. Pretty much like if I tell you a word in Swahili - you'll most likely need a dictionary to figure out what it might mean. - There are no reserved "keywords". Every word can be re-defined.
- You can format your code any way you want: no semicolons to add, no obligatory newlines, no commas, no parentheses, no Python-style indentation rules
- Every function expects a pre-determined number of arguments. Initially, we check during evaluation, and during runtime, we "consume" values until we reach the number of arguments required.
As you can see, there is not much to learn from scratch here:
Once you learn what the language main components are & a few details about precedence and evaluation, then only with the Library Reference at hand (the built-in functions that are already there for you, available to use), you're pretty much ready to write any program. ;-)
So, let's get to the gist of the matter!
The main components
Words
Words in Arturo are pretty much like words in English: a series of characters separated by spaces or some symbol. E.g.: this is a series of words
(in this case, this
, is
, a
, series
, of
, words
are all - well... - words.
💡 In Arturo, a word can be formed by any letter + the character
?
As with a real (spoken) language, every word has a specific meaning. And if you don't know the meaning of a word, you'll have to look it up in the dictionary. That's pretty much the way things work in Arturo as well.
In Arturo, a word may have 3 main different uses:
- refer to a value (that is: a variable, e.g.
x + 2
) - refer to an action, which does something (that is: a function, e.g.
doSomething
) - Arturo comes with close to 150 already defined such words/functions - refer to nothing (that is: a word without meaning, as in the
[lorem ipsum]
example above)
Labels
A label is nothing but Arturo's way of assigning meaning (to be read as a value) to a word - what you would normally call variable assignment or variable initialization. (In Arturo, these two terms can be used invariably, since there is practically no difference: you can set and re-define a word/variable as many times as you wish).
So, let's say you want to give a new meaning to the word x
:
x: 2
That was it: from now on, x
will mean 2
(until and if it's changed). So if you follow the above statement with:
print x
...Arturo will print 2
for you.
Symbols
As the mere word says, symbols are used to symbolize something, mainly as an alias to an existing word - although, by convention, as infix operators.
Hence, let's take the function add
. This takes two parameters, adds them up, and returns the result.
So, you may write:
print add 2 3
and Arturo will print out 5
for you.
Now, if you don't want to use the add
function (and prefix notation, which is the standard for all function calls), there is a symbol-alias for that: +
So, you could just as well write:
print 2 + 3
Only, this time you're expressing it more like you would in a normal math expression: with infix notation.
Some of the existing aliases in the built-in dictionary:
Symbol | Aliased word |
---|---|
+ | add |
∧ | and? |
++ | append |
@ | array |
<=> | between? |
?? | coalesce |
# | dictionary |
/ | div |
/% | divmod |
<= | dup |
= | equal? |
// | fdiv |
$ | function |
> | greater? |
>= | greaterOrEqual? |
< | less? |
=< | lessOrEqual? |
: | let |
% | mod |
* | mul |
⊼ | nand? |
¬ | not? |
<> | notEqual? |
∨ | or? |
^ | pow |
∏ | product |
.. | range |
<< | read |
./ | relative |
-- | remove |
~ | render |
- | sub |
∑ | sum |
? | switch |
>> | write |
⊻ | xor? |
💡 For the complete of recognized symbols in Arturo, you may have a look at here.
Values
Values are the very core of Arturo and are used to refer to pretty much all the different kinds of data you can have.
💡 Words, labels, and symbols - that we already saw above - can be considered "values" as well; and treated as such, unless we proceed to evaluate them!
We could split values into 2 categories: a) literal values - that is values you can directly define in your code and b) constructible - values that are created using some function.
The complete list follows:
:null
Null values generally denote nothing and it's mostly used as a return value by functions to inform us that something went wrong. If you want to set it as a value, you may just use the word null
, like:
x: null
:logical
Logicals are Arturo's logical values. They are like normal boolean values (true, false), with a twist: they fully support ternary logic and an additional maybe
value.
x: true
y: false
z: maybe
:integer
Integers represent positive or negative integers. When they are declared they are comprised only by digits ([0-9]+
) and they can be as long as you want - Arturo does support big numbers.
x: 2
y: 876528347613438247982374913423423947329
:floating
Floating values are basically floating-point numbers, that is: decimals. They begin with an initial all-digit part, followed by a .
(dot) and another all-digit part: [0-9]+\.[0-9]+
pi: 3.14159
:complex
Even though there is no special syntax for defining a complex number, you can always create one using to
. Arturo comes with full support for complex numbers and all the associated operations.
a: to :complex [1 2] ; a: 1.0+2.0i
b: to :complex @[pi pi] ; b: 3.141592653589793+3.141592653589793i
print a + b
; 4.141592653589793+5.141592653589793i
:rational
As with complex numbers, rationals can be defined using to
. And again, Arturo comes with full support for rational numbers and all the associated operations.
a: to :rational [1 2] ; 1/2
b: to :rational [3 4] ; 3/4
print a + b
; 5/4
:version
Version values are nothing but a fancy and portable way of defining SemVer-compliant versions (e.g. for packaging an application). The rule is rather simple: it has three numeric parts, separated by a .
(dot) and an optional part with build or prerelease information.
package: 1.0.3
if package > 1.0.0 -> print "yep, the version is right!"
or
version: 1.2.3-rc1+build123
:type
Type is a value representing another... type. To specify a type value, the format is a :
(colon) followed by a word - the type's name: :\w+
myType: :integer
or
if (type x) = :integer [ print "it's an integer!" ]
:char
Characters in Arturo can be declared using backticks: `\w`
ch: `a`
:string
A string is nothing but a series of characters, seen as one unit. In Arturo, to define a string, there are various ways:
Single-line strings
- using double quotes:
x: "this is a string"
(with escaped characters) - using right smart-quote notation
x: « This is a string
(in this case, everything following«
till the end of the line, will be stripped and considered one string)
Multi-line strings
using curly-brace blocks (the result will be stripped and un-indented):
x: { this is yet another very long string that spans more than one line }
using verbatim curly-brace blocks (the result will remain exactly as-is):
x: {: this is yet another very long string that spans more than one line :}
using dash notation (where everything after the line, until the end of the file, is a string - stripped and un-indented):
x: ------ this is the last very long string that spans more than one line
💡 If you want your string to contain sub-expressions that will be evaluated on the fly - that is string interpolation - all you have to do is include your code inside the string within pipe-bars and then call the function
render
(or~
) to process it accordingly: e.g.x: 2 print ~"my variable is: |x|" ; prints: ; my variable is: 2
:regex
A regex value is nothing but a string containing a regular expression (that is then compiled and treated as such).
The normal syntax is {/regex/}
:
type {/[A-Z]/} ; => :regex
replace "HelLo" {/[A-Z]/} "X" ; here we replace all capital letters
; with an X
While we may - optionally - use any of the supported PCRE-compliant flags:
i
: case-insensitive matchingm
: multilines
: dot-all
match "Hello" {/he/i} ; => ["He"]
:literal
Literals in Arturo are just a way of referring to the name of a word or symbol. Think of it as the string version of a word, or like Ruby's symbols.
For example, the function info
takes as an argument the name of the function for which you want some information. If you wrote like info print
, the interpreter would execute the function print
and try to... print something (which would not be there). If you wanted to refer to the name of the function -- that is: without actually calling it -- you would precede it with a '
(single-quote): '[\w+]
func: 'print
info func
However, literals may be used for much more - e.g. modifying a passed parameter in-place, without having to re-assign the result of an operation to a new variable. To learn more, have a look at In-place variable modification.
:symbolLiteral
Symbol literals are to symbols pretty much what literals are to words. That is: the "literal", unevaluated form of the symbol.
To declare a symbol literal, we may follow the example of normal, literals: single quote
+ accompanied by the symbol in question:
type '++ ; => :symbolliteral
:path
Paths in Arturo are a way of defining some hierarchy between values, something along the lines of parent -> child -> grandchild. For this, in Arturo, we'd use a series of values or words delimited with a \
(backslash). You can think of them as indexes in other programming languages.
print user\name
or
x: "name"
print user\(x)
or
myArray: ["zero" "one" "two" "three"]
print myArray\1
; prints "one"
:pathLabel
If paths are the way of defining some hierarchy between values, with pathLabels we are also able to assign values to some specific path.
user: #[
name: "John"
surname: "Doe"
]
print user\name ; will print "John"
; let's change this user's name
user\name: "Jane"
print user\name ; ok, now it should print "Jane"
:inline
Inlines in Arturo generally denote a list of words/symbols/values that are grouped and given some type of priority in evaluation. An inline block is denoted by (...)
(parentheses).
print (2+3)*4
Please note though that, apart from precedence, :inline
is a value type on its own:
a: [(one) two three]
print type a\0 ; that is: (one)
; prints :inline
:block
Blocks are a fundamental part of Arturo.
As we've already said, it's just a [...]
(square-bracket enclosed) block of words/symbols/values that - in contrast with inline blocks above which are evaluated in-place - are not evaluated until it's necessary.
myBlock: [one two three]
anArray: [1 2 3 4 5]
anotherArray: ["zero" 1 "two" 3 "cuatro"]
As you can see, blocks can contain practically anything: any word, any symbol, and any value. Of course, they can contain other blocks too:
x: [
1 2 [
3 4 [
5 "six" 7
]
8
]
9
]
:dictionary
Dictionaries in Arturo as what in other languages you'd call an associative array or hash table. If you want to create one, just give the dictionary
function (or use the #
alias) a block, with different labels, and it'll automatically convert it to a dictionary.
user: #[
name: "John"
surname: "Doe"
age: 34
]
What the #
function here does is:
- execute the block
- retrieve only the words/variables defined in there
- return a dictionary with the aforementioned words
💡 As you can probably assume from the above definition, a dictionary block doesn't necessarily have to contain just labels and word definitions - it may contain whatever you want, even function calls; only it will return you back just a table with the defined words in there
:function
Functions are another important value type in Arturo - and yes, you heard right: functions a value too. You can assign them to a word/variable, pass them around, re-define them and whatever you want with them, pretty much as you would do with another value, let's say a number.
To define a function, all you have to do is call the function
... function (or use the $
alias) followed by two parameters:
- the parameters' names (this can be either a literal, e.g.
'x
- if it's just one argument - or a block, e.g.[x y]
. If you want to use commas, for readability, like[x,y]
you are free to do so: Arturo will simply ignore them.
multiply: function [x y][
x * y
]
print multiply 2 3
; would print 6
or
addThemUp: $[x,y][x+y]
print addThemUp 2 3
; would print 5
:color
Colors can be easily defined using the #xxxxxx
syntax, where xxxxxx is either the RGB value of the color in hex, or its common-name alias, like #red
, #green
or #blue
:
color1: #red
color2: #0077BB
print color1 + color2
:date
Dates in Arturo are a distinct type of value. If you want to create one, you'll have to use one of the corresponding functions. For example, the function now
returns a :date
object, representing the current point in time. And it can be handled pretty much like you would handle a :dictionary
.
print now
; would print 2020-10-26T10:27:14+01:00
print now\year
; would print 2020
:database
Database values cannot be constructed literally. However, using the function open
, you can create a database value and then use it to query the database in question and much more.
db: open.sqlite "my.db"
print query db "SELECT * FROM users"
print type db ; would print: :database
:binary
Binary values are used to represent binary data, that is: an array of bytes. You cannot define them directly, however, you can sure convert other data to binary.
print to :binary "Hello world!"
; would print 4865 6C6C 6F20 776F 726C 6421
:bytecode
Bytecode values cannot be constructed literally. However, you can create them indirectly, e.g. using the function to
.
; let's create some Arturo bytecode from scratch
x: to :bytecode [
["print"] ; first block contains our constants
[1 112 155] ; second block contains the opcodes to be executed,
; in this case: ConstI1 (0x01), Call (0x70), End (0x9B)
]
; and execute it!
do x ; this will simply print... 1
Attributes
Another interesting feature of Arturo is what we'll analyze here: attributes.
Technically, attributes are nothing but an easy way of defining optional named parameters for functions - but which can however transcend between different function calls.
There are two types:
a. attributes
b. attribute labels
:attribute
Attributes are actually optional on/off-type of values, set during a function call, that is there to denote some variation of the initial meaning of the function. To define an attribute, we'll be using a .
(dot) followed by a normal word: \.\w+
Let's say we used the function split
, to split a string into parts:
split "hello world"
; => [`h` `e` `l` `l` `o` ` ` `w` `o` `r` `l` `d` ]
That does what it says: splits the string into an array of :char
s.
What if we want for example to split the string into words? For that, there is the .words
attribute for the function split
. So:
split.words "hello world"
; = ["hello" "world"]
💡 The order in which you pass the different attributes does not matter. Also, there is no issue at all whether you want to use spaces between them and the surrounding function call; Arturo will still be able to parse them and recognize them fine
:attributeLabel
Attribute labels are pretty much like simple attributes, only they can also take a value. As it worked with attributes, we'll be using a .
(dot) followed by a normal word, but now also followed by a :
(colon) -- exactly like with normal labels, as we've seen above.
Here's an example:
split .every: 2 "hello world"
; => ["he" "ll" "ow" "or" "ld"]
Precedence and Evaluation
The easiest way to explain precedence rules in Arturo is pretty much like it happened with our introduction: there are no precedence rules whatsoever.
So, in an expression like 2 * 3 + 4
, if you'd normally expect to get a result of 10
, you would be wrong: the result is 14
.
But in order to understand why, you'd have to understand how evaluation in Arturo works.
The right-to-left rule
The main expression evaluation order of Arturo is right-to-left. But with a tiny asterisk: Your code will be evaluated from left to right, it is the expressions passed to your function calls that will be evaluated from right-to-left.
Let's take a very simple example:
print add 1 2
print "Done"
As you would expect, the first function to be executed is the first print
function and then the second one. Nothing new here.
Now let's take the first print
. How is it working?
Let's see:
- First, the value
2
is pushed onto the stack - Then, we push the value
1
- Then, we execute the function
add
: it pops two values from the stack, adds them up, and pushes the result (3
) back onto the stack - Finally, we execute the function
print
: it pops the top value from the stack (3
) and prints it.
Then, execution would move on and... print "Done."
What you have to understand here is that evaluation within an expression will always be done from right to left, irrespective of what you might know from other languages or math operator precedence. In Arturo, you have to learn no precedence rules at all. You'll just have to remember to always calculate from right to left.
Re-visiting our previous, seemingly paradoxical, example:
2 * 3 + 4
💡 Remember: our
+
and*
operators are nothing but simple infix aliases to the functionsadd
andmul
respectively -- nothing more!
This is as if we had written (moving the operators in front):
* 2 + 3 4
which practically means: FIRST add 3 and 4 and THEN take the result and multiply it with 2.
If this is not what intended, then the right way in Arturo would be, either:
4 + 2 * 3
or (giving precedence to the multiplication, artificially, using parentheses):
(2 * 3) + 4
Scope and rules
Contrary to what you might expect, Arturo doesn't feature a traditional concept of scope. There are no real local or global variables, no local or global functions, no local or global blocks, no local or global anything.
Generally, if a variable has been previously declared at the moment and location of its usage, then it is available. Otherwise, it is not.
But let's see a couple of concrete cases to make this clearer.
Blocks
Arturo doesn't have a block scope.
In a few words, this means:
- A variable declared inside a block is available outside of it
- A variable previously declared outside of a block is available inside
- The changes of an existing variable, inside a block, persist after the block
x: 1 ; here, we declare a variable x
; and set it to 1
do [
x: 2 ; here, we re-assign the value of x
; to 2
print x ; prints 2
y: 3 ; here, we declare a variable y
; and set it to 3
]
print x ; prints 2 (the value of x has been changed)
print y ; prints 3 (the value of y is still available)
Iterators
Iterators (such as loop
, map
, etc) always work with a block as well. But in a special way.
Basically, the logic is identical to the one of blocks, but with a slight difference:
- the injected variables (e.g. the iteration arguments), are available only inside the block, but not after the iteration is over
- any previous value is "protected" and restored if needed
x: 3
loop.with:'i ["one" "two" "three"] 'x [
print i ; prints 0, 1, 2,...
print x ; prints "one", "two", "three",...
]
print x ; prints 3 (the value of x has been restored)
print i ; ERROR: variable not found
; (i is not available anymore)
Functions
Functions are totally different. Why? Because they do have their own scope.
The general idea is:
- A variable declared inside a function is available only inside the function
- A variable previously declared outside of a function is available inside
- The changes of an existing variable, inside a function, do not persist after the function
If we want to export a specific symbol to the outer scope, that is, make it available outside of the function, we can use the .export
option.
If we want to export all of the symbols - thus, practically making the function scope-less, we may use the .exportable
option.
In-place variable modification
In Arturo, every time you pass a parameter to a function, you can rest assured that the value of that parameter won't change (unless it's a string, block or dictionary and you - consciously - decided to use set
on it, in which case it does alter its inner structure).
So, basically, you when do this...
a: [1 5 2 4 3]
sort a
...all you do is to take an array, sort it, and push the sorted array onto the stack. Variable a
simply does not change.
So, what would you do if you wanted to get the array back, but sorted?
The simple - and most obvious - way would be to re-assign the returned result from sort
:
a: [1 5 2 4 3]
a: sort a
And now, yes, a
does contain the sorted version of the initial array.
But, what if you want to perform the modification in-place, which is normally faster and without the need for intermediate variables? Literals come to the rescue!
Using literals
As we've already said, "literals" ('i 'am 'a 'literal
) are nothing but string representations of a word, that is... the word itself. For that reason, they may come in very handy when you want to modify a variable in-place.
Let's revisit the above example and what the syntax of sort
is:
sort collection :literal :dictionary :block
As we can see, sort
takes one parameter (collection) which is either a :dictionary
or :block
OR a :literal
.
Why pass a literal? Simply because this turns in-place modification on! Let's have a look:
a: [1 5 2 4 3]
sort 'a ; yep, now a *has* been sorted in-place!
And this is very powerful: in Arturo, most of the built-in functions in the library come with this feature included. Practically, whenever you see a function taking a literal first parameter, that means you can use it for in-place modifying a variable (yes, even add
works like that!).
⚠️ Word of caution: Values in Arturo are always passed by reference - unless they are constant/readonly values. So if you want to assign one variable to another and then modify one of them in-place, make sure you use
new
; otherwise, both values will change!a: 1 b: a inc 'a ; both a and b are now 2 c: 1 d: new c ; we copy the value of c into d inc 'c ; now c is 2 and d is 1, as expected
Syntactic sugar
As you have hopefully seen so far, Arturo is rather simple, with fairly simple rules and that's pretty much it.
However, we also have some "syntactic sugar": a fancy way of referring to syntactic constructs, so that something more complicated will look better, or easier-to-write, or more readable.
Here you'll learn about some useful examples of syntactic sugar supported in Arturo.
Right-arrow operator: ->
The function of the right operator is rather straightforward: basically, it wraps the following terminal value inside a block.
Let's take a simple example.
x: -> 2
This is 100% equivalent to:
x: [2]
You can also use the right-arrow operator to make many common constructs far more readable.
For example:
if x=2 -> print "x was 2!"
is the same as writing:
if x=2 [ print "x was 2!" ]
As you can see, it can be pretty handy. Just remember that ->
can wrap only one terminal value.
For example:
x: -> 2 3
This doesn't result in x: [2 3]
but in x: [2] 3
Another interesting way of making use of the power of the right-arrow operator:
loop 1..10 'x -> print x
which is the same as writing (only much more elegant):
loop 1..10 'x [ print x ]
Fat right-arrow operator: =>
The fat right-arrow operator is like a super-charged simple right arrow operator (->
) as described above.
If ->
was used to wrap the following terminal into a block, the =>
operator does even more.
Let's take this simple example:
x: $ => add
This is equivalent to writing:
x: $[x,y][add x y]
Basically, it reads the following word, and if it's a function, pushes all its needed arguments as anonymous variables.
The same could work with a block argument, where &
can be used as a placeholder for the needed anonymous variables:
x: $ => [add & &]
(The first &
will pop the first argument, and the second the next one - and so on...)
As you can already imagine, this is perfect for easily defining functions or action blocks that take exactly one parameter.
For example, to print an array of the even numbers between 1 and 10:
print select 1..10 'x [even? x]
This could be easily written as (using the ->
operator):
print select 1..10 'x -> even? x
But pushing the limits more, we can also use the =>
operator:
print select 1..10 => even?
That's surely much more readable, isn't it?
Pipe operator: |
⚠️ This is experimental and may still not be stable enough for use in production scripts
The pipe operator is an easy way of reversing the default prefix notation of function calls and simulating what in OOP languages is called function chain.
Let's take this simple example:
1..10 | print
This equivalent to:
print 1..10
Or a bit more elaborate example (using pipes and the =>
operator):
1..10 | map => 2*
| select => even?
| print
which would be like writing:
print select map 1..10 'x [2*x] 'x [even? x]
Conclusion
If you made it here, then I can assure you: you've already learned more than you need in order to be fully proficient in Arturo.
Just head to the Library Reference and have a look at the built-in functions (with explanations and example code) and see what's already available for you or - if you want to see the language in action - just browse through the Examples: there are many (many!) working examples, to get more than just an idea.
Welcome on board! :)