TABLE OF CONTENTS
Languria is a toy programming language made for self-teaching and personal use, written by referencing the excellent crafting intepreters book by R. Nystrom.
The name (a homophone for "the watermelon" in italian) is the first thing that came to mind that starts with lang-. After having looked the name up, I learned that Languria is also a nice looking beetle, which easily became the language's mascot.
When a piece of code produces a value (an instance of one of the types in the table below), it's called an expression. When, instead, it represents an instruction, it's called a statement.
In Languria, statements are always expressions; that is to say, every part of the code that can be thought of as an action taken by the program also produces a byproduct value.
1 // values themselves are expressions. needless to say, a value evaluates to itself.
(2 + 3) * (4 + 5) // both (2 + 3) and (4 + 5) are expressions. Of course, the entire line is also an expression.
This is an overview of the primitive types:
Type | Example | Overview |
---|---|---|
Integer | 1 |
link |
Float | 1.5 |
link |
Boolean | true |
link |
String | "example" |
link |
Association | [1: "one"] |
link |
Option | ?0.8 |
link |
Applicable | |n|n * 2 |
link |
Literal | x |
- |
A scope, which is a section of code between {}, evaluates to whatever is explicitly returned in the middle of it, or what's produced by the last expression in the scope.
rps = {
return "rock"
"paper"
"scissor"
}
phrase = {
"how"
"are"
"you"
}
"{phrase} {rps}" == "you rock" // strings accept by default tokens between curly brackets.
A variable is a literal that is holding a value.
x = 1 // variables can only come into existence when they are assigned a value.
1 == (x = 1) // an assigment expression returns the assigned value.
x => 2 // equivalent to x = max(x, 2)
x =< 1 // equivalent to x = min(x, 1)
// other assign operators: =*, =/, =%, =@
// =@ assigns the result of an application back to the variable holding the application's main value. these terms will be explained in the applicables' section.
times_two = ||it * 2
x = 5
x =@ times_two // equivalent to x = x @ times_two
x == 10
You can print the value produced by an expression by prepending $
to it. The $expr
expression evaluates to expr
.
$"Hello, world!"
$x = 1 + 2 // an assignment is an expression that evaluates to the assigned value, so it can be printed.
y = 2 * $(3 + 4) // you can also print a subexpression in the middle of a bigger expression.
Multiple $
on the same line are printed separated by a comma, in the order in which the program executes. This is, in some cases, not intuitive.
In the example below, we can rest assured that the assignment is the last thing to be evaluated, so the last thing fed to STDIN will be 25. However, there's no good reason to expect that this whole line will print "9 16 25" as opposed to "16 9 25".
$math = $(3 * 3) + $(4 * 4)
To remedy this, you can also tag any of your print statements as such.
$math = $<a>(3 * 3) + $<b>(4 * 4) // prints "a: 9 b: 16 25"
$$
prints the current location of the line. Note that $$
is an expression!
$$ $3 // [test.lgr:4] 3
$$ * 2 // not only this prints [REPL:1], but it also evaluates to [REPL:1][REPL:1]
The following expressions all evaluate to "Hello, world!"
:
"Hello, " + "world!"
"He" + 2*"l" + "o, world!"
{
greeting = "Hello"
object = "world"
"{greeting}, {object}!"
}
Note that a single string {placeholder} only accepts one single literal: you cannot evaluate expressions inside a string.
An option value is a container in which there could either be another value, or nothing.
yes_int = ?2
no = ?_ // the empty option
To pull out a value from an option that contains one, you can use the |>
operator.
This operation fails if the option isn't holding a value.
?3|> == 3
// ?_|> // cannot apply `EXTRACT` to operands `[?_]`
A boolean value is either true
or false
. Booleans can be combined with the logical operations not
, and
, or
, xor
.
The postfix operator ?!
can be used to interpret a value as a boolean:
0?! == false
2.9?! == true // integers and floats are intepreted as true when strictly greater than 0
""?! == false
[]?! == false
"something"?! == true
[1: 2]?! == true // strings and associations are true when not empty
?_?! == false
?"something"?! == true // option values evaluate to true if they contain a value
Integers and floats are 64bit and signed. Languria implicitly converts between the two if needed be.
The builtin operations are: +
, -
, *
, %
, /
(whole division when both operands are ints), **
(exponentiation, also used for roots if you pass a fractional exponent).
17 % 3 // 2
17/3 // 5
17/3 // 5.666666666666667
8 ** 4 // 4096
8 ** (1.0/3) // 2.0
An association is a mapping from certain values (keys) to certain values (still called values). The keys are ordered and cannot repeat.
You can pull a value from an association using the operator >>
pointing to its key. The result of this operation is an option, containing the value you were looking for inside it if the pull was successful.
You can also skip this extra step and get the associated value right away through the |>>
operator.
$int_to_name = [1: "one", 2: "two", _: "not saying"]
(int_to_name >>2) == ?"two"
(int_to_name |>>2) == "two"
(int_to_name >>100) == ?"not saying" // the special _ key associates any unmapped "something" to the value mapped by _. It's always the last key.
no_default = [1: 2]
(no_default >>100) == ?_
[1: 1, "two": 2, 3: ?3] // neither keys nor values have to share their types.
If you're following along and trying the commands out, you probably noticed something strange the first line of the above example:
its REPL output was [1: (not yet evaluated), 2: (not yet evaluated), _: (not yet evaluated)]
.
This happens because, by default, values in associations are only evaluated when pulled out.
In the case above, being queried by 2
caused the key "three"
to evaluate to that string.
When needed, you can prepend !!
to an association to evaluate its values right away.
!![3: $"now", 1: $"printing", 2: $"everything"]
To push a key/value pair into an association, the <<
operator is used.
new_association = [] << |1, 3| // [1: 3]
new_association << |1, 4| // [1: 4] // if the key already exists, its value is overridden.
new_association << |_, 5| // [1; 4, _: 5] // you can also set a default value like such.
new_association << |1, _| // [_: 5] // pushing an underscore signifies that we want to drop that key.
new_association << |_, _| // [] // by the same token, you can also drop the association's default value in this way.
There some shortcuts to instantiating an association in often-used configurations.
If you only care about the keys, you can omit defining the values with [:keys]
(this will set the values to the boolean true).
If, instead, you want an ordered collection of values, :[values]
will associate the numbers 0 to n to the elements.
list = !!:["faster", "way", "to", "declare", "associations"] // [0: faster, 1: way, 2: to, 3: declare, 4: associations]
set = !![:"faster", "way", "to", "declare", "associations"] // [associations: true, declare: true, faster: true, to: true, way: true]
If the side of the key/value pair that you care about is a contiguous range of integers from a to b not including b, you can use this range syntax:
another_list = !!:[1..6]
// if the range start is greater than the range's end, the numbers are reversed.
// note that the range start is still inclusive, and the range still stops before reaching the range's end.
reversed_list = !!:[5..0]
Intersection (v
) and union (^
) are important operations performed on associations. Both act on the associations' keys, and both prefer to keep the keys of the application on the left of the operation.
// notice that the intersection is made on a setty range
!!:["A", "B", "C", "D", "E", "F"] v [:3..5] // [3: D, 4: E]
!!:["A", "B", "C", "D", "E", "F"] v :["this", "is", "not", "kept"] // [0: A, 1: B, 2: C, 3: D]
!![4: "puzzle", 0: "like", 2: "of"] ^ !![1: "pieces", 3: "a"] // [0: like, 1: pieces, 2: of, 3: a, 4: puzzle]
!!:["this", "is", "not", "kept"] ^ !![2: "absolutely", 5: "!"] // [0: this, 1: is, 2: not, 3: kept, 5: !]
An applicable is an expression that holds onto a piece of code with some placeholders,
and that undergoes an additional evaluation phase, called application, if fed values through the operators @
or @@
.
During an application, the applicable's body is evaluated with those values in place of the placeholders.
Let's examine the usage of an @-applicable:
repeat = |times, separator| (it + separator) * (times - 1) + it
add_dot = || it + "."
"hello"
@ repeat(2, " ")
@ add_dot
@ (it + "..")
// prints "hello hello..."
In this example, repeat
is receiving the values: "hello"
, 2
, and " "
.
The first of the three, called main value, has "preferential treatment": during the application phase, it alone gets to the applicable via @
;
additionally, when defining an applicable, it maps to the builtin placeholder it
.
The others, called contour values, instead map to the named placeholders defined between pipes, before the applicable's body.
As for add_dot
, note that even though it takes no contour values, it was necessary to pass an empty piping ||
when storing the applicable into it.
On the contrary, during an application empty parentheses are optional.
Lastly, the example above also shows that, when no contour values are needed, it's possible to directly apply a value
to an expression with the it
placeholder.
What follow are some examples of an @@-application:
assoc = [1: 2, 3: 4]
assoc @@ ($"\tposition n°{idx} of association is the pair ({it}, {ti})\n")
// the result of this application is the string "position n°1 of association is the pair (3, 4)"
"hello" @@ it == "o"
"hello" @@ idx == 4
@@-applications at the moment only accept a main value. This value needs to be a composite data type (an association or a string).
@@-applying a value on an expression evaluates the expression's body once for every element belonging to the input value. Compared to a @-application, the exposed placeholders change as such:
it
now stores the key of the element;ti
its value;idx
holds the number of past iterations.
The value produced by @@-applications is the one produced by the last expression in the last iteration.
Feeding _
to an @@-applicable will cause it to only stop when it encounters a return value.
In the example that follows, also note it was useful to store the outermost it placeholder in a variable in order to reuse its value in the inner application.
generate_odd_digits = || { amount = it
result = !!:[1]
_@@ { result@@idx == amount - 1 and return result
result << | result @@ idx + 1, result @@ ti + 2|
}
}
10 @ generate_odd_digits
When you store an unapplied expression into a variable, you can use that variable in the expression's body.
countdown = || [
$it > 1: (it - 1) @ countdown,
_: 0
] |>> true
5 @ countdown // prints 4 3 2 1 and returns 0
Some more examples of applicables:
add = |other| it + other
3 @ add(2) == 5
ok_or = |default| [it?!: it|>, _: default] |>> true
?3 @ ok_or(4) == 3
?_ @ ok_or(4) == 4
map_val = |fn_val| {
new_assoc = []
it @@ {
new_assoc << |ti, it @ fn_val|
}
new_assoc
}
:[1..6] @ map_val(||it ** 2) // [1: 0, 2: 1, 3: 4, 4: 9, 5: 16]