A small dynamically typed programming language with first-class comments, where every value is explained by a comment.
This language was developed within 48 hours for the first langjam.
The interpreter depends on python3 and lark. Lark can be installed with pip install lark
.
Invoke the interpreter without command line arguments to start the interactive mode
$ ./pls_explain.py
>>> print("hello world")
hello world /*the string "hello world"*/
>>>
To exit press Ctrl-D
.
To execute a Program written in PlsExplain, pass the path to the program as the first command line argument.
$ ./pls_explain.py examples/hello_world.pe
Hello World /*the string "Hello World"*/
Comments are first-class values in PlsExplain. This means that they are expressions, can be stored in variables, passed as function arguments and be returned by functions.
>>> let comment = /* This is a comment */;
>>> print(comment)
/* This is a comment */ /* a comment */
>>> print(/*another comment*/)
/*another comment*/ /* a comment */
At a first glance comments might seem to be equivalent to strings. The difference is that comments can be used to explain something. In PlsExplain all values have an associated comment that explains the value. To explain a value with a comment, simply write it next to the expression. When called with a single argument, print
shows the associated comment.
>>> let x = 40 + 2 /* the number fourtytwo */;
>>> print(x)
42 /* the number fourtytwo */
Explaining comments don't have to be comment-literals:
>>> let comment = /* this is a comment */
>>> let x = 42 comment
>>> print(x)
42 /* this is a comment */
Trying to explain a value with something that is not a comment obviously results in a type error.
>>> 42 "this is not a comment"
Backtrace (most recent call last):
line 1:
42 "this is not a comment"
^~~~~~~~~~~~~~~~~~~~~~~~~~
type error: type JlString can not be used to explain values
>>> let s = "not a comment either"
If you see this error, it often means that you have forgotten to separate two expressions with an semicolon.
If a value is not explained by an explicit comment, the interpreter automagically generates a helpful comment.
>>> let x = 4
>>> print(x)
4.0 /* the number 4.0 */
>>> let y = x + 10
>>> print(y)
14.0 /* the sum of the number 4.0 and the number 10.0 */
The comment explaining a value can be retrieved with the ?
operator.
>>> print(x?)
/* the number 4.0 */
The auto-generated comment can sometimes be a little bit verbose:
let fact = fn(n) {
if (n == 0) {
1
} else {
fact(n - 1) * n;
}
};
print(fact(4));
This program prints:
24.0 /*the product of the product of the product of the product
of the number 1.0 and the difference of the difference of
the difference of the number 4.0 and the number 1.0 and the
number 1.0 and the number 1.0 and the difference of the
difference of the number 4.0 and the number 1.0 and the number
1.0 and the difference of the number 4.0 and the number 1.0
and the number 4.0*/
Since comments are first-class values we can manipulate them on the fly. This allows us to generate even more helpful comments.
let fact = fn(n) {
if (n == 0) {
1
} else {
/* lets generate a helpful comment for the return value */;
/* (Comments can be concatenated with +) */
let comment = /* the factorial of */ + n?;
/* explain the return value with the generated comment */
(fact(n - 1) * n) comment;
}
};
let h = 4 /*the number of hours i've slept*/;
print(fact(h));
The output of this program is more concise:
24.0 /* the factorial of the number of hours i've slept*/
Since comments are first-class values, comments are also explained by comments. Obviously comments explaining a comment are also explained by comments which in turn are explained by comments and so on.
>>> let x = 42 /* comment */ /* meta-comment */ /* meta-meta-comment */
>>> print(x)
42 /* comment */
>>> print(x?)
/* comment */ /* meta-comment */
>>> print(x??)
/* meta-comment */ /* meta-meta-comment */
>>> print(x???)
/* meta-meta-comment */ /*a comment*/ <-- autogenerated meta-meta-meta-comment
>>> print(x????)
/*a comment*/ /*a comment*/
...
Type | Description |
---|---|
Comment |
the most important type |
String |
unicode strings |
Number |
floating point numbers |
Bool |
True or False |
Unit |
only the value () |
List |
Lists of values |
Currently there is no special syntax for lists, instead the builtin functions list
,append
,put
and get
have to be used to create and manipulate Lists.
>>> let l = list(1, 2, 3)
>>> print(l)
[1, 2, 3] /*a list of the number 1 and the number 2 and the number 3*/
>>> append(l, "world")
>>> print(l)
[1, 2, 3, world] /*a list of the number 1 and the number 2 and the number 3 and the string "world"*/
>>> print(get(l, 2))
3 /*the number 3*/
>>> put(l, 0, "hallo")
>>> print(l)
[hallo, 2, 3, world] /*a list of the string "hallo" and the number 2 and the number 3 and the string "world"*/
The Syntax is Expression based.
Type | Examples |
---|---|
Comment |
/* this is a comment */ |
String |
"hello" "with\n escape \" chars" |
Number |
1 , -1.0 , 42.5 |
Bool |
True , False |
Unit |
() |
(a + b) * c
{ print("hello"); print("world") }
Multiple expressions can be grouped with curly braces. Expressions are separated with semicolons. The semicolon after the last Expression is optional. Blocks evaluate to the value of their last expression.
let f = fn (arg) {
print(arg);
};
Functions can be defined with the keyword fn
followed by a parenthesized list of parameters and the function body. The braces are optional, when the body is a single expression. All functions are anonymous and first-class.
print("hello")
Nothing special here.
let x = 42
Variables are declared with the keyword let
and must be initialized. Variables are lexicaly scoped. Declarations evaluate to the assigned value.
x = 100
Assignments evaluate to the assigned value.
-a
!b
Nothing unusual.
a + b
Operators ordered by decreasing precedence:
Operators | Examples |
---|---|
? |
x? |
* , - , % |
x * y , x % 5 |
+ , - |
x + y |
== , < , > |
x < y |
& |
x & y |
` | ` |
The only unusual operator is the explain operator (?
). See First Class Comments for more details.
The logic operators &
and |
are not short circuiting.
if (condition) { "true" } else { "false" }
Parenthesis around the condition are mandatory. Braces around single expressions and the else
branch are optional. If expressions evaluate to the value of the taken branch.
while (condition) { do_something() }
Parenthesis around the condition are mandatory. The Braces cant be omitted, if the body is a single expression. While loops evaluate to the value of the loop body during the last iteration.
Function | Description |
---|---|
print(args...) |
Print any number of values. When called with a single argument, the arguments comment is printed as well |
input() |
Read single line and return it as a string. |
str(value) |
Convert value to string. |
cmnt(value) |
Convert value to comment. |
num(value) |
Convert string to number. Returns () if the conversion fails. |
list(args...) |
Create list containing the arguments. |
append(list, value) |
Append value to list |
put(list, index, value) |
Put value into list |
get(list, index) |
Get value out of list |