A Tour of Pyro
Pyro begs, borrows, and steals from every language I've ever used, but it drags Python down a back alley and leaves it naked, bleeding, and penniless. If you've used Python you'll find many aspects of Pyro's design philosophy familiar.
- Comments
- Variables
- Numbers
- Strings
- Characters
- Booleans
- Nulls
- Equality
- Comparisons
- Printing
- Loops
- Conditions
- Functions
- Classes
- Errors
- Panics
- Modules
- The Main Function
- Unpacking
- Source Code
Comments
Comments begin with a #
symbol and run to the end of the line:
# Full line comment. var foo = 123; # Partial line comment.
Variables
Variables are declared using the var
keyword:
var foo = 123;
Variables must be declared before use. If an initial value isn't specified the variable has the value null
.
var foo; assert foo == null;
You can declare multiple variables in a single statement:
var foo, bar; var baz = 123, bam = 456;
Variables have lexical scope and declarations inside blocks will shadow outer declarations:
var foo = 123; { var foo = 456; assert foo == 456; } assert foo == 123;
Identifier names (this includes variables, functions, and classes) should begin with an ASCII letter or an underscore and contain only ASCII letters, numbers, and underscores.
Names beginning with a $
symbol are $pecial
— this namespace is reserved by the language for builtin variables and functions.
Numbers
Pyro has two numeric types: i64
for 64-bit signed two's-complement integers, and f64
for 64-bit IEEE 754 floats.
Both behave as you'd expect and support the usual range of numeric operations.
+ | Addition. Returns an integer if both operands are integers or a float if either or both are floats. |
- | Subtraction (binary) or negation (unary). Subtraction returns an integer if both operands are integers or a float if either or both are floats. |
* | Multiplication. Returns an integer if both operands are integers or a float if either or both are floats. |
/ | Floating-point division. Both operands will be converted to floats and the result will be a float. |
// | Truncating division. Returns an integer if both operands are integers or a float if either or both are floats. |
% | Modulo/remainder operator. Returns an integer if both operands are integers or a float if either or both are floats. |
** | Power operator. Both operands are converted to floats and the result is a float. |
Integer literals can use binary, octal, decimal, or hex notation:
var a = 0b101; # a == 5 var b = 0o101; # b == 65 var c = 101; # c == 101 var d = 0x101; # d == 257
Integer literals can use underscores to improve readability:
var foo = 123_456; var bar = 0b1010_0101;
Float literals require a decimal point and can also use underscores to improve readability:
var foo = 1.0; var pi = 3.141_592_654;
Strings
A string, str
, is an immutable array of bytes.
var string = "hello pyro";
Pyro strings have methods that let you manipulate them as ASCII or as UTF-8 but the string type itself is agnostic about its encoding — a string can contain any sequence of byte values including null bytes or invalid UTF-8.
You can find a full description of the string type here.
Characters
A character, char
, is an unsigned 32-bit integer representing a Unicode code point.
Character literals use single quotes:
var c1 = 'a'; var c2 = '€'; var c3 = '🔥';
You can find a full description of the character type here.
Booleans
A boolean value is either true
or false
.
You can convert any value to a boolean using the $bool(arg)
function.
assert $bool(123) == true; assert $bool(null) == false;
Values which convert to true
are truthy, values which convert to false
are falsey.
-
The values
false
andnull
are falsey, as is anyerr
value. - All other values are truthy.
Nulls
Uninitialized variables have the default value null
.
var foo; assert foo == null;
You can use the null-coalescing operator ??
to supply a default value in place of a null
:
var foo = maybe_null() ?? "default";
Functions which don't explicitly specify a return value have the return value null
.
Equality
Numbers are equal if their values are numerically equal:
assert 1 == 1.0; assert 'a' == 97; assert 'b' == 98.0;
Strings are equal if they have the same content:
var foo = "foobar"; var bar = "foobar"; assert foo == bar;
Tuples are equal if they have the same length and their elements are equal:
var foo = $tup("foo", 123); var bar = $tup("foo", 123); assert foo == bar;
By default, other objects are equal only if they are the same object:
class Foo {} var foo1 = Foo(); var foo2 = Foo(); assert foo1 == foo1; # True. assert foo1 == foo2; # False.
You can overload the equality operator to customize its behaviour for your own types.
Comparisons
You can use the comparison operators (<
, <=
, >
, >=
) with any combination of numerical types:
assert 2.0 > 1; assert 'a' > 42;
The comparison operators also work with strings:
assert "abc" < "def"
Strings are compared lexicographically by byte value.
You can use the comparison operators directly on tuples:
assert $tup(1, 2, 3) < $tup(1, 2, 4);
Tuples are compared lexicographically by element.
Printing
The echo
statement prints to the standard output stream. It's useful for simple printing and debugging.
echo "hello pyro";
You can echo
any value — echo
stringifies its argument before printing it.
(It's equivalent to calling $str()
on the argument first and printing the result.)
You can supply multiple arguments to echo
, separated by commas.
echo foo, 123, "bar";
echo
prints the stringified values separated by spaces.
Pyro also has a family of $print()
/$println()
functions with support for format strings:
$print("hello pyro"); # "hello pyro" $print("{} and {}", "foo", "bar"); # "foo and bar" $print("{} and {}", 123, 456); # "123 and 456"
Calling the $print()
function with a single argument is equivalent to calling $str()
on that argument first and printing the resulting string.
Calling it with more than one argument is equivalent to calling $fmt()
on those arguments first and printing the resulting string.
See the string formatting documentation for a detailed look at the $fmt()
function and format strings.
The only difference between $print()
and $println()
is that $println()
automatically adds a newline character to the output.
Loops
Pyro has support for several different looping constructs. The simplest is the infinite loop:
var i = 0; loop { i += 1; if i == 5 { break; } } assert i == 5;
The loop
statement also supports C-style loops with an initializer, a condition, and an incrementing expression:
loop var i = 0; i < 10; i += 1 { echo i; }
The for
keyword in Pyro is reserved for looping over iterators:
var vec = ["foo", "bar", "baz"]; for item in vec { echo item; }
Finally, Pyro supports while
loops:
var i = 0; while i < 10 { i += 1; } assert i == 10;
All the looping constructs support break
and continue
.
Conditions
Conditional statements in Pyro look like this:
if money > 100 { echo "we have lots of money"; }
Conditional statements evaluate the truthiness of their conditions.
As you'd expect, they support optional else if
and else
clauses:
if money > 100 { echo "we have lots of money"; } else if money > 10 { echo "we have some money"; } else { echo "we're poor"; }
Pyro also has support for conditional expressions using the ternary operator ? :|
:
var status = money > 100 ? "rich" :| "poor";
Functions
Function definitions look like this:
def add(a, b) { return a + b; }
If you don't explicitly return a value from a function, its return value is null
.
Inner functions declared inside outer functions can capture the outer function's local variables, including its parameters:
def make_adder(increment) { def adder(num) { return num + increment; } return adder; } var adds_one = make_adder(1); adds_one(0); # 1 adds_one(1); # 2 var adds_two = make_adder(2); adds_two(0); # 2; adds_two(1); # 3;
Functions are first-class citizens in Pyro, meaning you can pass them around just like any other value. You can also declare and use functions anonymously:
var add = def(a, b) { return a + b; }; add(1, 2); # 3 add(3, 4); # 7
Classes
Class definitions look like this:
class Person { var name; var role = "programmer"; def $init(name) { self.name = name; } def info() { $println("{} is a {}.", self.name, self.role); } }
Create an instance of a class by calling its name:
>>> var dave = Person("Dave");
Arguments are passed on to the optional initializer method, $init()
.
Call a method on an instance using the method access operator, :
:
>>> dave:info(); Dave is a programmer.
Get or set an instance's fields using the field access operator, .
:
>>> dave.role = "pointy headed manager"; >>> dave:info(); Dave is a pointy headed manager.
Boo Dave.
Classes are a big enough topic to deserve a page of their own which you can find here.
Errors
Pyro has a dedicated error type, err
, which a function can return to indicate failure.
var foo = $err("oops!");
You can check if a value is an err
using the $is_err(arg)
function.
Alternatively, you can provide a default value for a function call that might fail using the error-coalescing operator !!
:
var foo = might_fail() !! "default";
Panics
A panic in Pyro is similar to an exception in other languages — it indicates that the program has attempted to do something impossible like divide by zero or read from a file that doesn't exist.
An unhandled panic will result in the program exiting with an error message and a non-zero status code.
You can trap a panic using a try
expression:
var foo = try might_panic();
A try
expression returns the value of its operand if it evaluates without panicking or an err
if it panics.
try
is a unary operator with the same precedence as !
or -
so you should wrap lower-precedence expressions in brackets:
var foo = try (1 / 1); # 1 var bar = try (1 / 0); # err
Note that you can use the error-coalescing operator !!
to provide a default value for a panicking expression:
var foo = try (1 / 1) !! 0; # 1 var bar = try (1 / 0) !! 0; # 0
You can raise a panic from your own code by calling the $panic()
function with an error message:
$panic("oh no!");
If the panic is unhandled, the error message will be printed to the standard error stream and the program will exit with a non-zero status code.
Modules
A module is a Pyro file loaded as a library. Modules are loaded using the import
keyword.
Assume we have a file called math.pyro
containing math functions:
import math; var foo = math::abs(-1);
Modules can contain submodules. Assume we have a directory called math
containing a file called trig.pyro
:
import math::trig; var foo = trig::cos(1);
Modules are a big enough topic to deserve a page of their own which you can find here.
The Main Function
When you run a script file Pyro first executes the script then looks for a function called $main()
.
If it finds a $main()
function it runs it automatically.
def $main() { echo "hello from $main()"; }
Note that the $main()
function won't be run if you import the same file as a module.
Unpacking
You can unpack the values of a tuple, error, or vector in a var
declaration using the following syntax:
var (foo, bar, baz) = $tup(123, 456, 789); assert foo == 123; assert bar == 456; assert baz == 789;
Similarly, you can unpack the loop variable of a for
loop:
var map = { "foo" = 123, "bar" = 456, }; for (key, value) in map { echo key; echo value; }
You can discard unwanted values by assigning them to the unnamed variable _
, e.g.
var (foo, _, baz) = $tup(123, 456, 789); assert foo == 123; assert baz == 789;
Source Code
Source code outside of string literals is assumed to be UTF-8 encoded. String literals can contain arbitrary byte sequences including null bytes and invalid UTF-8.