Asteroid Reference Guide¶
Language Syntax¶
Note: In the following descriptions <something>?
denotes an optional
something in a piece of syntax. We also use the notation <something>*
which means that something can appear zero or more times in a program.
Capitalized
words are keywords where FOR
represents the keyword for
and END
represents end
.
Statements¶
Assert¶
Syntax: ASSERT exp '.'?
If the expression of the assert statement evaluates to a
value equivalent to the Boolean value
false
an exception is thrown otherwise the statement is ignored.
For example, the statement,
assert (1+1 == 3).
will generate a runtime error but the statement,
assert (1+1 == 2).
will be ignored once the expression has been evaluated.
Break¶
Syntax: BREAK '.'?
The break statement immediately breaks out of the closest surrounding looping structure. Execution will continue at the statement right after the loop. Issuing a break statement outside of a looping structure will lead to a runtime error.
As an example we break out of the indefinite loop below when i
is equal to 10,
let i = 0.
loop
let i = i+1.
if i==10 do
break.
end
end
assert (i==10).
Expressions at the Statement Level¶
Expressions at the statement level are supported. However, they do not have any effect on the computation unless they contain side effects with one exception: In the absence of an explicit return statement, the value of the last expression evaluated in a function body is considered the return value of the function.
An example,
function inc
with i do
i+1.
end
Notice that because the expression i+1
is the last statement evaluated in the
function body its value becomes the return value of the function.
For-Loop¶
Syntax: FOR pattern IN exp DO stmt_list END
In a for-loop the expression must evaluate to a list. The pattern is then matched to each element of the list sequentially starting with the first element of the list. The loop body is executed for each successful match.
In the following program the body of the loop is executed exactly once when
the pattern matches the tuple (1,"chicken")
,
let tuple_list = [
(0,"duck"),
(1,"chicken"),
(2,"turkey")
].
for (1,bird) in tuple_list do
assert(bird is "chicken").
end
Function-Definition¶
Syntax: FUNCTION function_name WITH pattern DO stmt_list (WITH pattern DO stmt_list)* END
Function definitions in Asteroid can have one or more function bodies associated with single function name. A function body is associated with a particular pattern that is matched against the actual argument of the function call. If the match is successful then the associated function body is executed. If the match is not successful then other pattern/body pairs are tried if present. If none of the patterns match the actual argument then this constitutes a runtime error. Patterns are tried in the order they appear in the function definition.
The following is a definition of the sign
function,
function sign
with x if x >= 0 do
return 1.
with x if x < 0 do
return -1.
end
Here the first function body returns 1
if the actual argument is greater or equal to zero.
The second function body return -1
if the actual argument is less than zero.
Global¶
Syntax: GLOBAL variable_name (',' variable_name)* '.'?
The global
statement allows the developer to declare a variable as global
within a function scope and this allows the developer to set the value of a global variable
from within functions.
Consider the following code snippet,
let x = 0.
function foo
with none do
global x.
let x = 1.
end
assert(x==0).
foo().
assert(x==1).
The global
statement within the function foo
indicates that the let
statement
on the following line should assign a value to the global variable x
.
If-Then-Else¶
Syntax: IF exp DO stmt_list (ELIF exp DO stmt_list)* (ELSE DO? stmt_list)? END
If the first expression evaluates to the equivalent of a Boolean true
value
then the associated statements will be executed and the execution
continues after the end
keyword. If the expression evaluates to the equivalent
of a Boolean false
then the expressions of the optional elif
clauses
are evaluated if present. If one of them evaluates to the equivalent of a Boolean
value true
then the associated statements are executed and execution continues
after the end
keyword. Otherwise
the statements of the optional else
clause are executed if present and again
flow of control is transferred to the statements following the if-statement.
As an example consider the following if
statement that determines
what kind of integer value the user supplied,
load system io.
load system type.
let x = type @tointeger (io @input "Please enter an integer: ").
if x < 0 do
io @println "Negative".
elif x == 0 do
io @println "Zero".
elif x == 1 do
io @println "One".
else do
io @println "Positive".
end
Let¶
Syntax: LET pattern = exp '.'?
The let
statement is Asteroid’s version of the assignment statement with a twist though: the left side of the =
sign is not just a variable
but is considered a pattern. For simple assignments there is no discernible difference between assignments in Asteroid and assignments in other
languages,
let x = val.
Here, the variable x
will match the value stored in val
. However, because the left side of the =
sign is a pattern we
can write something like this,
load system math.
let x:%[(k:%integer) if math @mod(k,2)==0]% = val.
where x
will only match the value of val
if that value is an even integer value. The fact that the left side of the =
is a pattern allows
us to write things like this,
let 1 = 1.
which simply states that the value 1
on the right can be matched by the pattern 1
on the left. Having the ability to pattern match
on literals is convenient for statements like these,
let (1,x) = p.
This let
statement is only successful for values of p
which are pairs where the first component of the pair is the value 1
.
Loop¶
Syntax: LOOP DO? stmt_list END
The loop
statement executes the statements in the loop body indefinitely
unless a break
statement is encountered.
Repeat-Until¶
Syntax: REPEAT DO? stmt_list UNTIL exp '.'?
Repeatedly execute the statements in the loop body until the
expression evaluates to the equivalent of a Boolean true
value.
Here is an example of a program that prints out the elements of a list,
load system io.
let l = ["bmw", "volkswagen", "mercedes"].
repeat
let [element|l] = l.
io @println element.
until l is [].
Return¶
Syntax; RETURN exp? '.'?
Explicitly return from a function with an optional return value.
Structure¶
Syntax: STRUCTURE type_name WITH data_or_function_stmts END
The structure
statement introduces a composite data type that defines a physically grouped list of variables under one name. The variables within a structure can be declared as data members or as function members.
Unless a member function was declared as a constructor (an __init__
function) structures are
instantiated using a default constructor. The default constructor copies the arguments given to it into the data member fields in the order that the data members appear in the structure definition and as they appear in the parameter list of the constructor. We often refer to instantiated structures as objects. Member values of objects
are accessed using the access operator @
. Here is a simple example,
-- define a structure of type A
structure A with
data a.
data b.
end
let obj = A(1,2). -- call default constructor
assert( obj @a == 1 ). -- access first data member
assert( obj @b == 2 ). -- access second data member
We can use custom constructors to enforce that only certain types of values can be copied into an object,
-- define a structure of type Person
structure Person with
data name.
data age.
function __init__ with (name:%string,age:%integer) do -- constructor
let this @name = name.
let this @age = age.
end
end
let betty = Person("Betty",21). -- call constructor
assert( betty @name == "Betty" ).
assert( betty @age == 21 ).
Also note that object identity is expressed using the this
keyword.
Try-Catch¶
Syntax: TRY DO? stmt_list (CATCH pattern DO stmt_list)+ END
This statement allows the programmer to set up exception handlers for
exceptions thrown in the code of the try
part of the statement.
Notice that you can set up one or more handlers within the catch
part of
the statement. If there are more than one handlers then they are searched in
order starting with the first. Handlers are selected via pattern matching
on the exception object. The handler code of the first catch
clause whose
pattern matches the exception object is executed.
Below is an example of a try-catch
statement where the code
in the try
part generates a division-by-zero exception. The
exception object is pattern-matched in the catch
clause and processed
by the associated handler,
load system io.
try
let x = 1/0.
catch Exception("ArithmeticError", s) do
io @println s.
end
For more details on exceptions please see the User Guide.
Throw¶
Syntax: THROW exp '.'?
Allows the developer to throw an exception. Any object can serve as an exception object. However, Asteroid provides some predefined exception objects. For more details on exceptions please see the User Guide.
While-Loop¶
Syntax: WHILE exp DO stmt_list END
While the expression evaluates to the equivalent of a Boolean true
value
execute the statements in the body of the loop. The loop expression is reevaluated
after each loop iteration.
Here is an example that prints out a sequence of integer values in reverse order,
load system io.
let i = 10.
while i do
io @println i.
let i = i-1.
end
The loop terminates once i
becomes zero which is the equivalent to a Boolean
value false
.
Expressions¶
All the usual arithmetic, relational, and logic operators,
+, -, *, /, ==, =/=, <=, <, >=, >, and, or, not
are supported in
Asteroid. For extended mathematical operations such as mod
(modulus) or
sin
(sine) see the math
module. Here we discuss expression constructions
that are particular to Asteroid.
Substructure Access¶
Syntax: structure_exp @ index_exp
Asteroid provides the uniform substructure access operator @
for all structures
which includes lists, tuples, and objects. For example, accessing the first
element of a list is accomplished by the expression,
[1,2,3] @0
Similarly, given an object constructed from structure A
, member values
are accessed by name via the @
operator,
structure A with
data a.
data b.
end
let obj = A(1,2).
assert( obj @a == 1 ). -- access member a
Head-Tail Operator¶
Syntax: element_exp | list_exp
This operator works in one of two ways. In the first way it allows you to pre-append an element to a list,
let [1,2,3] = 1 | [2,3].
It can also be nested,
let [1,2,3] = 1 | 2 | 3 | [].
In the second way it works as a pattern to deconstruct a list into its first element and the remainder of the list, the list with its first element removed,
let h | t = [1,2,3].
assert(h == 1).
assert(t == [2,3]).
You can put optional brackets around the operator to highlight the fact that we are dealing with a list,
let [h | t] = [1,2,3].
The is
Predicate¶
Syntax: exp IS pattern
This operator matches the structure computed by the expression on the left
side against the pattern on the right side of the operator. If the match is
successful it returns the Boolean value true
and if not successful then
it returns the Boolean value false
. All regular rules of pattern matching
apply such as instantiating appropriate variable bindings in the current scope.
Example,
if v is (x,y) do
io @println "success".
assert(isdefined x).
assert(isdefined y).
else
io @println "not matched".
end
The in
Predicate¶
Syntax: exp IN list_exp
This predicate returns true
if the value computed by the expression on the
left in contained in the list computed by the list expression on the right.
It is an error if the expression on the right does not compute a list.
Example,
let true = 1 in [1,2,3].
List Comprehensions¶
Syntax: start_exp TO end_exp (STEP exp)?
This expression constructs a list starting with an element given by the start expression up to the value of the end expression with a given step. If the step expression is not given then a step value of 1 is assumed. The comprehension can be placed between optional square brackets.
Examples,
let [0,1,2,3,4] = 0 to 4.
let [0,-2,-4,-6] = [0 to -6 step -2].
Function Calls¶
Syntax: exp exp
Function calls are defined by function application, more specifically by
juxtaposition of expressions. Here, the first expression has to evaluated to
a function expression and the second expression has to evaluate to an appropriate
actual function parameter. Notice that function calls are defined in terms of a
single function parameter. If you would like to pass more than one value to a
function then you have to create a tuple. For example, if the function foo
needs two values to be passed to it then you need to create a tuple, e.g. foo (1,2)
.
In that respect function calls differ drastically from function calls in languages
like C/C++ or Python.
Examples,
let val = (lambda with i do i+1) 1.
assert(val == 2).
function foo with (q,p) do q+p end
let val = foo (1,2).
assert(val == 3).
If-Else Expressions¶
Syntax: then_exp IF bool_exp ELSE else_exp
If the boolean expression evaluates to true then this expression returns the value of the first expression. Otherwise it will return the value of the last expression.
Example,
let val = "yup" if b else "nope".
If b
evaluates to true then this expression returns the string "yup"
otherwise it returns the string "nope"
.
First-Class Patterns¶
PATTERN exp
* exp
This construction allows the user to construct a pattern as a value using
the pattern
keyword. The advantage of patterns as values is that they
can be stored in variables or passed to or from functions. As an example
we construct a pattern which is a pair where the first component is the constant
1
and the second component is the variable x
and we store this pattern
in the variable p
for later use,
let p = pattern (1,x).
The pattern derefence operator *
allows us to retrieve patterns from
variables, e.g.
let *p = (1,2).
Here the pair (1,2)
is matched against the pattern stored in the variable p
such that x
is bound to the value 2
.
Type Patterns¶
Syntax: '%'type_name
Type patterns match all the values of a particular type. Type patterns exist
for all the Asteroid builtin types and are also available for user defined
types introduced via a structure
command.
Example,
let true = 1 is %integer.
Named Patterns¶
Syntax: name_exp ':' pattern
Named patterns allow you to bind the term matched by the pattern to a variable. Here the name expression has to evaluate to something that looks like a variable, object member variable, or array location.
Example,
let x:%integer = val.
The variable x
will be bound to the value of val
if that value matches the
type pattern %integer
.
Conditional Patterns¶
Syntax: pattern IF cond_exp
In conditional patterns the pattern only matches if the condition expression evaluates to true.
Example,
load system math.
let k if (math @mod(k,2) == 0) = val.
Here k
only matches the value of val
if that value is an even number.
Pure Constraint Patterns¶
Syntax: %[ pattern ]%
A pure constraint pattern is a pattern that does not create any bindings
in the current scope. Any pattern can be turned into a pure constraint pattern
by placing it between the %[
and ]%
operators.
Example,
let pos_int = pattern %[(x:%integer) if x > 0]%
let i:*pos_int = val.
The first line defines a pure constraint pattern for the positive integers.
Notice that the pattern internally uses the variable x
in order to evaluate
the conditional pattern but because it has been declared as a pure constraint
pattern this value binding is not exported to the current scope during pattern matching.
On the second line we constrain the pattern i
to only the positive integer values using
the pure constraint pattern stored in p
. This pattern match will only succeed if val
evaluates to a postive integer.
Asteroid Grammar¶
The following is the complete grammar for the Asteroid language. Capitalized
words are either keywords such as FOR
and END
or tokens such as STRING
and ID
. Non-terminals
are written in all lowercase letters. The grammar utilizes an extended BNF notation
where <syntactic unit>*
means zero or more occurrences of the syntactic unit and
<syntactic unit>+
means one or more occurrences of the syntactic unit. Furthermore,
<syntactic unit>?
means that the syntactic unit is optional. Simple terminals
are written in quotes.
////////////////////////////////////////////////////////////////////////////////////////
// statements
prog
: stmt_list
stmt_list
: stmt*
stmt
: '.' // NOOP
| LOAD SYSTEM? (STRING | ID) '.'?
| GLOBAL id_list '.'?
| ASSERT exp '.'?
| STRUCTURE ID WITH struct_stmts END
| LET pattern '=' exp '.'?
| LOOP DO? stmt_list END
| FOR pattern IN exp DO stmt_list END
| WHILE exp DO stmt_list END
| REPEAT DO? stmt_list UNTIL exp '.'?
| IF exp DO stmt_list (ELIF exp DO stmt_list)* (ELSE DO? stmt_list)? END
| TRY DO? stmt_list (CATCH pattern DO stmt_list)+ END
| THROW exp '.'?
| BREAK '.'?
| RETURN exp? '.'?
| function_def
| exp '.'?
function_def
: FUNCTION ID body_defs END
body_defs
: WITH pattern DO stmt_list (WITH pattern DO stmt_list)*
data_stmt
: DATA ID
struct_stmt
: data_stmt '.'?
| function_def '.'?
| '.'
struct_stmts
: struct_stmt*
id_list
: ID (',' ID)*
////////////////////////////////////////////////////////////////////////////////////////
// expressions/patterns
exp
: pattern
pattern
: PATTERN WITH? exp
| '%[' exp ']%'
| head_tail
head_tail
: conditional ('|' exp)?
conditional
: compound (IF exp (ELSE exp)?)?
compound
: logic_exp0
(
(IS pattern) |
(IN exp) |
(TO exp (STEP exp)?) |
)?
logic_exp0
: logic_exp1 (OR logic_exp1)*
logic_exp1
: rel_exp0 (AND rel_exp0)*
rel_exp0
: rel_exp1 (('==' | '=/=' ) rel_exp1)*
rel_exp1
: arith_exp0 (('<=' | '<' | '>=' | '>') arith_exp0)*
arith_exp0
: arith_exp1 (('+' | '-') arith_exp1)*
arith_exp1
: call_or_index (('*' | '/') call_or_index)*
call_or_index
: primary (primary | '@' primary)* (':' pattern)?
////////////////////////////////////////////////////////////////////////////////////////
// primary expressions/patterns
primary
: INTEGER
| REAL
| STRING
| TRUE
| FALSE
| NONE
| ID
| '*' call_or_index
| NOT call_or_index
| MINUS call_or_index
| PLUS call_or_index
| ESCAPE STRING
| EVAL primary
| '(' tuple_stuff ')'
| '[' list_stuff ']'
| function_const
| TYPEMATCH // TYPEMATCH == '%'<typename>
tuple_stuff
: exp (',' exp?)*
| empty
list_stuff
: exp (',' exp)*
| empty
function_const
: LAMBDA body_defs
Builtin Functions¶
Function
len
, when given an input value, returns the length of that input. The function can only be applied to lists, strings, tuples, or structures.Function
hd
, when given a list as input returns the first element of that list. It is an error to apply this function to an empty list.Function
tl
, when given a list as input returns the rest of the list without the first element. It is an error to apply this function to an empty list.Function
range
will compute a list of values depending on the input values:(start:%integer,stop:%integer)
returns list[start to stop-1]
.(start:%integer,stop:%integer,inc:%integer)
returns list[start to stop-1 step inc]
.(stop:%integer)
returns list[0 to stop-1]
.
Function
getid
returns the id (physical memory address) of any Asteroid object as an Asteroid integer.Function
isdefined
returns true if a variable or type name is defined in the current environment otherwise it returns false. The variable or type name must be given as a string.
List and String Objects¶
In Asteroid, both lists
and strings,
are treated like objects. Due to this, they have member functions that can manipulate the contents of those objects.
Lists¶
A list is a structured data type that consists of square brackets enclosing comma-separated values. Member functions on lists can be called on the data structure directly, e.g.:
[1,2,3] @length()
Function
length
returns the number of elements within that list.Function
append
, given(item)
, adds that item to the end of a list.Function
extend
, given(item)
, will extend the list by adding all the items from the item whereitem
is either a list, a string or a tuple.Function
insert
, given(ix:%integer,item)
, will insert an item at a given position. The first argument is the index of the element before which to insert, soa@insert(0, x)
inserts at the front of the list, anda@insert(a@length(), x)
is equivalent toa@append(x)
.Function
remove
, given(item)
, removes the first element from the list whose value is equal to(item)
. It raises a ValueError if there is no such item.Function
pop
, given(ix:%integer)
, removes the item at the given position in the list and returns it. If no index is specified,``a@pop()`` removes and returns the last item in the list.Function
clear
, given(none)
, removes all items from the list.Function
index
returns a zero-based index in the list of the first element whose value is equal to(item)
. It raises a ValueError exception if there is no such item. The optional argumentloc
allows you to specify(startix)
and(endix)
and are used to limit the search to a particular subsequence of the list. The returned index is computed relative to the beginning of the full sequence rather than the(startix)
argument. This function can be called with several input configurations:(item,loc(startix:%integer,endix:%integer))
(item,loc(startix:%integer))
item
Function
count
, given(item)
, returns the number of times(item)
appears in the list.Function
sort
sorts the items of the list in place. It can be called with several different inputs:(reverse:%boolean)
if the boolean is set to true then the sorted list is reversed.none
returns the reverse list.
Function
reverse
, reverses the elements of the list in place.Function
copy
, makes a shallow copy of the list.Function
shuffle
, returns a random permutation of a given list - in place!Function
map
, given(f:%function)
, appliesf
to each element of the list in place. The modified list is returned.Function
reduce
reduces the value of elements in a list. This function can be called with several different inputs:Input
(f:%function)
returnsvalue
, such thatvalue = f(value,this@i)
.Input
(f:%function,init)
returns the same format but usesinit
as an initial value.
The first argument to
f
is the accumulator.Function
filter
, given(f:%function)
, constructs an output list from those elements of the list for whichf
returns true. Iff
is none, the identity function is assumed, that is, all elements of the input list that are false are removed.Function
member
, given(item)
, returns true only ifitem
exists on the list.Function
join
, given(join:%string)
, turns the list into a string usingjoin
between the elements. The string is returned as the return value from this function.
See the Prologue module for more on all the functions above.
Strings¶
A string is a sequence of characters that can be used as a variable or a literal constant. Similar to lists the member functions of strings can be called directly on the data structure itself, e.g.:
"Hello there" @length()
Function
length
returns the number of characters within that string.Function
explode
, turns a string into a list of characters.Function
trim
, given the input(what:%string)
, returns a copy of the string with the leading and trailing characters removed. Thewhat
argument is a string specifying the set of characters to be removed. If omitted or none, thewhat
argument defaults to removing whitespace. Thewhat
argument is not a prefix or suffix; rather, all combinations of its values are stripped.Function
replace
will return a copy of the string with all occurrences of regular expression patternold
replaced by the stringnew
. If the optional argument count is given, only the first count occurrences are replaced. It can be called with several different inputs:(old:%string,new:%string,count:%integer)
(old:%string,new:%string)
Function
split
will return a list of the words in a given string, usingsep
as the delimiter string. Ifmaxsplit
is given: at most maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not specified or -1, then there is no limit on the number of splits (all possible splits are made). Ifsep
is given, consecutive delimiters are not grouped together and are deemed to delimit empty strings (for example,"1,,2"@split(",")
returns["1", "", "2"]
). Thesep
argument may consist of multiple characters (for example,"1<>2<>3"@split("<>")
returns["1", "2", "3"]
). Splitting an empty string with a specified separator returns[""]
. Ifsep
is not specified or is None, a different splitting algorithm is applied: runs of consecutive whitespace are regarded as a single separator, and the result will contain no empty strings at the start or end if the string has leading or trailing whitespace. Consequently, splitting an empty string or a string consisting of just whitespace with a None separator returns[]
. Functionsplit
can be called with several different inputs:Input
(sep:%string,count:%integer)
Input
(sep:%string)
Input
(none)
Function
toupper
, converts all the lowercase letters in a string to uppercase.Function
tolower
, converts all the uppercase letters in a string to lowercase.Function
index
allows the user to search for a givenitem
in a list. It returns an integer index into the string ornone
ifitem
was not found. The optional argumentloc
allows you to specify(startix)
and(endix)
and are used to limit the search to a particular substring of the string. The returned index is computed relative to the beginning of the full string rather than the(startix)
argument.The function can be called with several different inputs:Input
(item:%string,loc(startix:%integer,endix:%integer))
Input
(item:%string,loc(startix:%integer))
Input
(item:%string)
Function
flip
reverses a string.
See the Prologue module for more on all the functions above.
Asteroid Modules¶
There are a number of system modules that can be loaded into an Asteroid program using load system <module name>
.
The modules are implemented as objects where all the functions of that module are
member functions of that module object. For example, in the case of the io
module
we have println
as one of the member functions. To call that function:
load system io.
io @println "Hello there!". -- println is a member function of the io module
Bitwise¶
The bitwise module defines Bitwise operations. It supports the following functions,
Function
band
can be called with the input(x:%integer, y:%integer)
, and performs the Bitwise AND operation.Function
bor
can be called with the input(x:%integer, y:%integer)
, and performs the Bitwise OR operation.Function
bnot
can be called with the input(x:%integer)
, and performs the Bitwise NOT operation.Function
bxor
can be called with the input(x:%integer, y:%integer)
, and performs the Bitwise XOR operation.Function
blshift
can be called with the input(x:%integer, y:%integer)
, and performs the Bitwise left shift operation.Function
brshift
can be called with the input(x:%integer, y:%integer)
, and performs the Bitwise right shift operation.Function
blrotate
can be called with the input(x:%integer, i:%integer)
, and performs the Bitwise left rotate operation.Function
brrotate
can be called with the input(x:%integer, i:%integer)
, and performs the Bitwise right rotate operation.Function
bsetbit
can be called with the input(x:%integer, i:%integer)
, and sets the ith bit.Function
bclearbit
can be called with the input(x:%integer, i:%integer)
, and clears the ith bit.Function
bsize``can be called with the input ``(x:%integer)
, and returns the bit size.
Hash¶
The hash module implements a hash for name-values pairs. It supports the following functions,
Function
insert
, given the input(name,value)
, will insert a given name-value pair into the table.Function
get
, givenname
, will return thevalue
associated with the givenname
as long as it can be found otherwise an exception will be thrown.Function
aslist
returns the hash as a list of name-value pairs.
IO¶
The io module implements Asteroid’s I/O system. The module defines three default streams,
__STDIN__
- the standard input stream.__STDOUT__
- the standard output stream.__STDERR__
- the standard error stream.
Furthermore, the module supports the following functions,
Function
println
can be called withitem
, and prints a given argument to the terminal (__STDOUT__
) with an implicit newline character.Function
print
can be called withitem
, and prints a given argument. No implicit newline is appended to the output.Function
input
can be called with a stringprompt
. Ifprompt
is given it is printed and then input is read from the terminal (__STDIN__
) and returned as a string.Function
open
opens a file. Given(name:%string, mode:%string)
, it returns a file descriptor of typeFILE
. Themode
string can be"r"
when the file will only be read,"w"
for only writing (an existing file with the same name will be erased), and"a"
opens the file for appending; any data written to the file is automatically added to the end. The"r+"
opens the file for both reading and writing.Function
close
, givenfile:%FILE
, closes that file.Function
read
, givenfile:%FILE
, reads a file. If no file is given the__STDIN__
stream is read.Function
readln
, givenfile:%FILE
, reads a given line of input from the file. If no file is given the__STDIN__
stream is read.Function
write
, given(file:%FILE, what:%string)
, will writewhat
to the givenfile
. Iffile
is not given then it writes to the__STDOUT__
stream.Function
writeln
, works the same way aswrite
except that it appends a newline character to the output.
Math¶
The math module implements mathematical constants and functions. It supports the following functions,
Power and logarithmic functions
Function
exp
, givenx:%integer
, returns e raised to the powerx
, where e = 2.718281… is the base of natural logarithms.Function
log
can be called with two different argument setups,If only one argument,
(x)
, is input, this returns the natural logarithm of x (to base e).If two arguments,
(x,base)
, are input, this returns the logarithm of x to the given base, calculated as log(x)/log(base).
Function
pow
, given(b,p:%integer)
, returns “b <sup>p</sup>” as long as b is eitherreal
orinteger
.Function
sqrt
, givena
, returns its square root as long asa
is eitherreal
orinteger
.
Number-theoretic and representation functions
Function
abs
, givenx
, returns its absolute value.Function
ceil
, givenx:%real
, returns the ceiling of x: the smallest integer greater than or equal to x.Function
floor
, givenx:%real
, returns the floor of x: the largest integer less than or equal to x.Function
gcd
, given(a:%integer,b:%integer)
, returns the greatest common denominator that both integers share.Function
isclose
can be called with two different argument setups,With input values
(a,b)
, it returns returnstrue
if the two values are close to each other andFalse
otherwise. Default tolerance 1e-09.With input values
(a,b,t)
, it comparesa
andb
with tolerancet
.
Function
mod
, given(v,d)
, will return the remainder of the operationv/d
, as long asv
andd
are eitherreal
orinteger
values.
Trigonometric functions
Function
acos
, givenx
, returns the arc cosine of x in radians. The result is between 0 and pi.Function
asin
, givenx
, returns the arc sine of x in radians. The result is between -pi/2 and pi/2.Function
atan
, ,givenx
, returns the arc tangent of x in radians. The result is between -pi/2 and pi/2.Function
cos
, givenx
, returns the cosine of x radians.Function
sin
, givenx
, returns the sine of x radians.Function
tan
, givenx
, returns the tangent of x radians.Function
acosh
, givenx
, returns the inverse hyperbolic cosine of x.Function
asinh
, givenx
, returns the inverse hyperbolic sine of x.Function
atanh
, givenx
, returns the inverse hyperbolic tangent of x.Function
cosh
, givenx
, returns the hyperbolic cosine of x.Function
sinh
, givenx
, returns the hyperbolic sine of x.Function
tanh
, givenx
, returns the hyperbolic tangent of x.Function
degrees
, givenx
, converts anglex
from radians to degrees.Function
radians
, givenx
, converts anglex
from degrees to radians.
An example,
load system io.
load system math.
let x = math @sin( math @pi / 2 ).
io @println("The sine of pi / 2 is " + x + ".").
Pick¶
The pick module implements
pick objects that allow a user to randomly pick items from a list using the pick
function.
The pick
function can be called with n:%integer
and returns a list of n
randomly picked objects from the object list.
Here is a simple use case
load system io.
load system pick.
let po = pick @pick([1 to 10]).
let objects = po @pick(3).
io @println objects.
Random¶
The random module implements the random
numbers. Using the functions included in this module will return a random value within a given range or interval. It supports the following functions,
Function
random
, given the inputnone
, returns a random floating point number in the range[0.0, 1.0)
.Function
randint
returns a random value N in the interval lo <= N <= hi. The exact random value output depends on the types of the values specifying the interval. It can be called with two different number interval inputs:(lo:%integer,hi:%integer)
(lo:%real,hi:%real)
Note: any other interval specification will instead output an error message for “unsupported interval specification in randint.”
Function
seed
, given(sd:%integer)
, provides a seed value for the random number generator.
Set¶
The set module implements Asteroid sets as lists. Unlike lists, sets do not have repeated members. It supports the following functions,
Function
toset
, given(lst:%list)
, converts the input list into a set.Function
diff
, given(a:%list,b:%list)
, computes the difference set between the two seta
andb
.Function
intersection
, given(a:%list,b:%list)
, finds the intersection between setsa
andb
.Function
union
, given(a:%list,b:%list)
, computes the union of setsa
andb
.Function
xunion
, given(a:%list,b:%list)
, returns all elements ina
orb
, but not in both.
Sort¶
The sort module
defines a parameterized sort
function over a list.
The sort
function makes use of a user-defined order predicate on the list’s elements to
perform the sort. The Quicksort
is the underlying sort algorithm.
The following is a simple example,
load system io.
load system sort.
let sl = sort @sort((lambda with (x,y) do return true if x<y else false),
[10,5,110,50]).
io @println sl.
prints the sorted list:
[5,10,50,110]
Stream¶
The stream module implements streams that allow
the developer to turn any list into a stream supporting interface functions like peeking
ahead or rewinding
the stream.
The following stream interface functions are available,
Function
eof
returnstrue
if the stream does not contain any further elements for processing. Otherwise it returnsfalse
.Function
peek
returns the current element available on the stream otherwise it returnsnone
.Function
get
returns the current element and moves the stream pointer one ahead.Function
rewind
resets the stream pointer to the first element of the stream.Function
map
applies a given function to each element in the stream.Function
append
, givenitem
, adds item to the end of the stream.Function
__string__
maps a the stream to a string representation.
A simple use case.
load system io.
load system stream.
let s = stream @stream([1 to 10]).
while not s @ eof() do
io @ print (s @get()+" ").
end
io @println ("").
which outputs:
1 2 3 4 5 6 7 8 9 10
Type¶
The type module defines type related functions and structures.
Type Conversion
Function
tointeger
converts a given input to an integer. It can be called with two different arguments,(item:%string,base:%integer)
wherebase
is a valid base for integer conversionitem
whereitem
is converted to a base 10 integer.
Function
toreal
, givenitem
, returns the input as a real number data type.Function
toboolean
, givenitem
, returns the input as a Boolean value of either true or false.Function
tostring
converts an Asteroid object to a string. If format values are given, it applies the formatting to the object. It can be called with several different inputs where*TP
indicates a``boolean``,integer
, orstring
type andw
is the width specification,p
is the precision specification ands
is the scientific notation flag. When no formatting information is provided a default string conversion occurs,(v:*TP,type @stringformat(w:%integer))
(v:%real,type @stringformat(w:%integer))
(v:%real,type @stringformat(w:%integer,p:%integer))
(v:%real,type @stringformat(w:%integer,p:%integer,s:%boolean))
item
- default conversion
Function
tobase
represents the given integerx
(specifically within the given input(x:%integer,base:%integer)
) as a string in the given base.
Here is a program that exercises some of the string formatting options,
load system io.
load system type.
load system math.
-- if the width specifier is larger than the length of the value
-- then the value will be right justified
let b = type @tostring(true,type @stringformat(10)).
io @println b.
let i = type @tostring(5,type @stringformat(5)).
io @println i.
-- we can format a string by applying tostring to the string
let s = type @tostring("hello there!",type @stringformat(30)).
io @println s.
-- for floating point values: first value is width, second value precision.
-- if precision is missing then value is left justified and zero padded on right.
let r = type @tostring(math @pi,type @stringformat(6,3)).
io @println r.
The output of the program is,
true
5
hello there!
3.142
Notice the right justification of the various values within the given string length.
Type Query Functions
Function
islist
returnstrue
if givenitem
is a list otherwise it will returnfalse
.Function
isscalar
returnstrue
if givenitem
is either an integer or a real value.Function
isnone
returnstrue
if givenitem
is equal to the valuenone
.Function
gettype
returns the type of a givenitem
as an Asteroid string.
A simple example program using the gettype
function,
load system type.
let i = 1.
assert(type @gettype(i) == "integer").
Util¶
The util module defines utility functions and structures that don’t really fit into any omodules. It supports the following functions,
Function
exit
exits the program. It can be called with two inputs,none
msg:%string
Function
copy
, given Asteroid objectobj
, makes a deep copy of it.Function
cls
clears the terminal screen.Function
sleep
, programs sleep forsecs
seconds where the argumentsecs
is either an integer or real value.Function
zip
, given(list1:%list,list2:%list)
, will return a list where elementi
of the list is the tuple(list1@i,list2@i)
.Function
unzip
, given a list of pairs will return a pair of lists where the first component of the pair is the list of all the first components of the pairs of the input list and the second component of the return list is a list of all the second components of the input list.Function
ascii
, given a characteritem:%string
, returns the corresponding ASCII code of the first character of the input string.Function
achar
, given a decimal ASCII codeitem:%integer
, returns the corresponding character symbol.
Vector¶
The vector defines functions useful for vector arithmetic. It supports the following functions. Here a
and b
are vectors implemented as lists,
Function
add
, given the input(a,b)
, returns a vector that contains the element by element sum of the input vectors.Function
sub
, given the input(a,b)
, returns the element by element difference vector.Function
mult
, given the input(a,b)
, returns the element by element vector multiplication.Function
dot
, given(a,b)
, computes the dot product of the two vectors.Function
op
allows the developer to vectorize any function. It can be called with three different inputs:(f:%function,a:%list,b:%list)
(f:%function,a:%list,b if type @isscalar(b))
(f:%function,a if type @isscalar(a),b:%list)
Here is a simple example program for the vector
module,
load system io.
load system vector.
let a = [1,0].
let b = [0,1].
io @println (vector @dot (a,b)).
which prints the value 0
.
Interfacing Asteroid with Python¶
Asteroid allows integration with Python in one of two ways. First, we can call the Asteroid interpreter from within a Python program and second, we can embed Python code directly within an Asteroid program. We start with looking at calling the Asteroid interpreter from Python.
Calling Asteroid from Python¶
Calling Asteroid from within a Python program is nothing more than calling Asteroid’s interp
function with a string representing an Asteroid program as its argument. In order to make this work you
will have to make sure that the Python interpreter can find the Asteroid modules.
Here we assume that you have installed Asteroid with the pip
installer.
Once you have installed Asteroid you will have to point the PYTHONPATH
environment variable to the directory where pip
installed the Asteroid modules.
You can easily find out where the modules are installed by issuing the show
command,
ubuntu$ pip3 show asteroid-lang
Name: asteroid-lang
Version: 1.1.1
Summary: A pattern-matching oriented programming language.
Home-page: https://asteroid-lang.org
Author: University of Rhode Island
Author-email: lutzhamel@uri.edu
License: None
Location: /home/ubuntu/.local/lib/python3.8/site-packages
Requires: numpy, pandas, matplotlib
Required-by:
ubuntu$
The Location
field tells us where the Asteroid modules have been installed.
Under Ubuntu we can now create an environment variable that points to that directory as follows,
ubuntu$ export PYTHONPATH=/home/ubuntu/.local/lib/python3.8/site-packages
ubuntu$
Now that Python knows how to find the Asteroid modules we can import the Asteroid interpreter into any Python program using,
from asteroid.interp import interp
where the interp
function takes a string representing of an Asteroid program
as an argument. Let’s test drive this in the Python interactive shell,
ubuntu$ python3
Python 3.8.10 (default, Nov 26 2021, 20:14:08)
[GCC 9.3.0] on 1
Type "help", "copyright", "credits" or "license" for more information.
>>> from asteroid.interp import interp
>>> interp('load system io. io @println "Hello, World!".')
Hello, World!
>>>
For more detailed information on the interp
function do a help(interp)
at the interactive Python prompt.
Even though we have shown this example under Linux, analogous approaches
should work on both Windows and macOS.
Not only can we execute the Asteroid interpreter from Python but we can also access its state to look up the results of a computation for example. Here is a slight variation of the program above where the Asteroid program computes the string value containing the greeting but we are actually printing the value from Python,
# import Asteroid modules
from asteroid.interp import interp
from asteroid.state import state
# run the interpreter to compute the greeting string
interp('let s = "Hello World!".')
# retrieve the greeting string from the interpreter state
# notice the pair of values a symbol table lookup produces:
# one for the type of the value and one for the actual value
(type,val) = state.symbol_table.lookup_sym('s')
print(type)
print(val)
The program prints out,
string
Hello World!
Embedding Python into an Asteroid Program¶
Using Asteroid’s escape
expression allows us to embed arbitray Python
code into an Asteroid program,
-- Printing hello once from each environment
-- print hello from Asteroid
load system io.
io @println "Hello World from Asteroid!".
-- print hello from Python
escape
"
print('Hello World from Python!')
".
Please note that the format of the Python code in the escaped string should follow the same guidelines as the Python code embedded in strings handed to the Python exec function.
Not only does the escape
expression give you access to the Python environment but
it also gives you access to the current Asteroid interpreter state including its
symbol table. That means we can access any variable defined in the Asteroid
environment from Python,
let s = "Hello World!".
escape
"
(type, val) = state.symbol_table.lookup_sym('s')
print(type)
print(val)
".
Notice that a symbol table lookup produces a pair of values where the first value represents the type of the value stored in the symbol table and the second value is the actual value stored. In this case our program prints out,
string
Hello World!
That is the type of the value is a string and the value is the actual string Hello World!
.
Since escape
represents an expression we can also return values from the
Python code using a special __retval__
variable. The only trick is that
we have to remember that values in Asteroid are pairs consisting of type information
and values. Here is a very simple program that exercises that part of the Python API,
load system io.
let i = escape
"
global __retval__ # access the return value register
__retval__ = ('integer', 101)
".
io @println i.
This program will print out the value 101
from Asteroid even though that value
was created within the Python environment. Notice that we have to access the
return value register __retval__
with the global
statement in the Python code.
We can pull all of this together and write an Asteroid function that performs its computations in Python,
function inc with i do return escape
"
# access return value register
global __retval__
# lookup the value of the formal argument
(type, val) = state.symbol_table.lookup_sym('i')
# only perform the increment if the value is an integer
if type != 'integer':
raise ValueError('not an integer')
else:
__retval__ = (type, val+1)
".
end
-- call inc and make sure the result is correct
let k = inc(1)
assert(k == 2).
Of course the function is just an illustration of how to use the Python API. This type of computation is much easier to express in Asteroid directly,
function inc
with i:%integer do
i+1
end
let k = inc(1)
assert(k == 2).
The Foreign Type Tag¶
When working in the hybrid Asteroid-Python environment it is sometimes useful to be able to embed values
in an Asteroid program that have no direct representation in Asteroid. This is where the foreign
type tage comes into play. Consider the following program that uses Pandas dataframes within an
Asteroid program,
------------------------------------------------------------------------
function pack
------------------------------------------------------------------------
-- this function packs four real values into a Pandas dataframe
with (a:%real,b:%real,c:%real,d:%real) do return escape
"
global __retval__
# we can ignore type info here because we checked it above
(_, aval) = state.symbol_table.lookup_sym('a')
(_, bval) = state.symbol_table.lookup_sym('b')
(_, cval) = state.symbol_table.lookup_sym('c')
(_, dval) = state.symbol_table.lookup_sym('d')
import pandas as pd
df = pd.DataFrame({'x':[aval,bval], 'y':[cval,dval]})
__retval__ = ('foreign', df)
"
end
------------------------------------------------------------------------
function dump
------------------------------------------------------------------------
-- dump the Pandas dataframe to stdout
with df do escape
"
(dftype, dfval) = state.symbol_table.lookup_sym('df')
if dftype != 'foreign':
raise ValueError('expected data frame')
print(dfval)
"
end
------------------------------------------------------------------------
function access
------------------------------------------------------------------------
-- access an element of the Pandas dataframe at row r and column c
with (df,r:%integer,c:%integer) do return escape
"
global __retval__
(dftype, dfval) = state.symbol_table.lookup_sym('df')
if dftype != 'foreign':
raise ValueError('expected data frame')
# we can ignore type info here because we checked it above
(_, rval) = state.symbol_table.lookup_sym('r')
(_, cval) = state.symbol_table.lookup_sym('c')
# make sure the ret value conforms to the Asteroid value structure
__retval__ = ('real', dfval.iloc[rval,cval])
"
end
------------------------------------------------------------------------
function sum
------------------------------------------------------------------------
-- sum down the columns of the dataframe and return a pair of values,
-- one component for each column
with (df) do return escape
"
global __retval__
(dftype, dfval) = state.symbol_table.lookup_sym('df')
if dftype != 'foreign':
raise ValueError('expected data frame')
# sum the value down the columns
sum = list(dfval.sum(axis=0))
# construct our tuple, note the type information
__retval__ = ('tuple', [('real',sum[0]),('real',sum[1])])
"
end
------------------------------------------------------------------------
-- exercise our machinery
let df = pack(1.0,2.0,3.0,4.0).
dump(df).
assert(access(df,1,1) == 4).
assert(sum(df) == (3.0,7.0)).
The dump
function generates the following output,
x y
0 1.0 3.0
1 2.0 4.0
Pandas dataframes are not directly usable in Asteroid but by writing thin Python
wrappers and taking advantage of the escape
expression the foreign
type
tag we can embed Pandas functionality into Asteroid. As an additional step we could
wrap these individual functions into a structure
with the dataframe as
a data member and the functions as member functions of that structure. As an
example of this approach see the dataframe.ast system module.