
Astro is a fairly trivial programming language with interesting features that make it a great fit for introducing (1) compiler and interpreter writing, and (2) formal language semantics.
This document defines the language Astro.
A programis a sequence of one or more statements. There are only two kinds of statements, assignments and print statements. Comments begin with // and extend to the end of the line.
// A simple program in Astro
radius = 55.2 * (-cos(2.8E-20) + 89) % 21; // assignment statement
the_area = π * radius ** 2; // another assignment
print hypot(2.28, 3 - radius) / the_area; // print statement
Apologies for the old-fashioned semicolons, but they do make the language somewhat easier to parse.
All values in Astro are either:
All values in Astro have a type. Numeric values belong to the type $\textsf{Num}$. Functions of $n$ parameters belong to the type $\textsf{Fun}\,n$.
Numeric values in Astro are denoted with literals as in JavaScript:
2
2.0
55.9
819.999e-15
2E+10
5.89999e2
There are no function literals.
A variable is a named container for a value. Variables in Astro get their name and initial value through anassignment, which binds an identifier to a variable. There are five built-in variables. All other variables must be assigned to before they can subsequently be used.
sister = 5 + 1; // variable declaration of sister (Ⅴ + Ⅰ = Ⅵ)
print sister; // OK: sister has been assigned
// print cousin; // ERROR: cousin has not been assigned
print π; // Turns out to be okay because π is built-in
Variables can be mutable or immutable. All variables bound in the standard library are immutable. All other variables are mutable.
let x = 1;
x = 2; // OK: x is mutable
// sqrt = 5; // ERROR: sqrt is immutable
Functions can only be called. They cannot be used in a context where a number is expected:
// print sin; // ERROR
// t = sin; // ERROR
// strange = sin * 3 // ERROR
Functions declared with $n$ parameters must be passed exactly $n$ arguments when called.
A statement is code that is executed solely for its side effect; it produces no value. The kinds of statements are:
= $e$ ;
(Assignment statement) $e$ is evaluated, then the value of $e$ is copied into $i$. $i$ must be either unbound or bound to a mutable variable holding a numeric value.
;
(Print statement) Evaluate $e$ then writes its evaluation to standard output.
An expression produces a numeric value. For numeric literals $n$, identifiers $i$ and $f$, and expressions $e$, $e_1$, and $e_2$, the Astro expressions are:
A numeric literal produces the value of the number it denotes.
Here $i$ must be an identifier bound to a variable holding a numeric value. Produces the value of the variable it names.
- $e$
Evaluates $e$ and produces the negation of $e$.
** $e_2$
The subexpressions are evaluated in any order and ${e_1}^{e_2}$ is produced.
* $e_2$
The subexpressions are evaluated in any order and their product is produced.
/ $e_2$
The subexpressions are evaluated in any order and their quotient is produced.
% $e_2$
The subexpressions are evaluated in any order and the remainder of $e_1$ divided by $e_2$ is produced.
+ $e_2$
The subexpressions are evaluated in any order and their sum is produced.
- $e_2$
The subexpressions are evaluated in any order and their difference is produced.
(Function call) Evaluates each $e_i$ then calls the function bound to $f$ with these evaluated arguments, in order, and produces the returned value of the function. $f$ must be bound to a variable whose value has type $\mathsf{Fun}\;n$.
The following identifiers are pre-defined in a scope that surrounds the program. This means that none of these identifiers may be declared anywhere in a program.
π
Read-only variable whose value is the best approximate value of $\pi$.
function sqrt(x)
Returns the square root of $x$.
function sin(x)
Returns the sine of $x$ radians.
function cos(x)
Returns the cosine of $x$ radians.
function hypot(x, y)
Returns the hypotenuse of a right triangle with sides $|x|$ and $|y|$.
The source of a Astro program is a Unicode string. Here is the syntax given as an Ohm grammar:
Astro {
Program = Statement+
Statement = id "=" Exp ";" --assignment
| print Exp ";" --print
Exp = Exp ("+" | "-") Term --binary
| Term
Term = Term ("*" | "/" | "%") Factor --binary
| Factor
Factor = Primary "**" Factor --binary
| "-" Primary --negation
| Primary
Primary = id "(" ListOf<Exp, ","> ")" --call
| numeral --num
| id --id
| "(" Exp ")" --parens
numeral = digit+ ("." digit+)? (("E" | "e") ("+" | "-")? digit+)?
print = "print" ~idchar
idchar = letter | digit | "_"
id = ~print letter idchar*
space += "//" (~"\n" any)* --comment
}
The meaning of an Astro program is defined in this section via transition rules in the style of Natural Semantics. It is defined from the following abstract syntax:
Constructs are statically analyzed relative to a context that keeps track of the type and mutability status of each identifier.
Statically analyzing an expression computes its type; statically analyzing a statement or block computes the resulting context; Statically analyzing a program simply determines whether it satisfies all contextual rules.
As Astro has no nested scopes and no shadowing, we can never have a scenario in which two variables share the same name. Therefore, dynamic evaluation requires only a global memory mapping identifiers directly to the values stored in the variable bound to the identifier, as well as output tracking.