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
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 either a list or a tuple. The pattern is then matched to each component of the expression value sequentially starting with the first component. 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.
let x = 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
.
Load
Syntax: LOAD SYSTEM? (STRING | ID) (AS ID)? '.'?
The load
statement allows you to load Asteroid modules either by filename or by module name.
The system
flag tells the interpreter only to search in the system modules for the desired
module. Probably the most often loaded module is the system IO module,
load system io.
io @println "Hello World!".
The as
modifier allows you to rename a module in the current context in order to avoid name clashes.
Consider for example that you had loaded your own IO module but also would like to load the
system IO module. In order to avoid a name clash you can use the as
modifier to rename one
of the modules,
load io. -- load my IO module
load system io as systemio. -- load the system IO module and rename it to systemio
io @output "Foobar".
systemio @println "Hello World!".
When loading a module with an explicit filename the basename of the filename becomes the module name.
The ASTEROIDPATH
environment variable allows you to specify additional directories the load
command
will search for modules. The contents of ASTEROIDPATH
is a colon separated list of directories.
Loop
Syntax: LOOP DO? stmt_list END
The loop
statement executes the statements in the loop body indefinitely
unless a break
statement is encountered.
Match
Syntax: MATCH expression (WITH pattern DO stmt_list)* END
The match
statement matches a value given by expression against a list of patterns
in the with
clauses. If a pattern matches the associated statements will be executed,
match (1,2)
with (x,y) if x > y do
let x = "GT".
with (x,y) if x < y do
let x = "LT".
with _ do
throw Error("not a valid tuple").
end
assert(x == "LT").
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
function __str__ with none do
return this @name+" is "+ tostring(this@age) +" years old".
end
end
let betty = Person("Betty",21). -- call constructor
assert( betty@name == "Betty" ).
assert( betty@age == 21 ).
assert(tostring betty is "Betty is 21 years old").
Note that object identity is expressed using the this
keyword.
Here we also supplied an instantiation of the __str__
function that allows
us to customize the stringification of the object. See the last line
where we cast the object betty
to a string. Without the __str__
function
Asteroid uses a default representation of the object as a string.
The __str__
function does not accept any arguments and has to return a string.
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 >= 0 do
io @println i.
let i = i-1.
end
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,
load system io.
if (1,2) is (x,y) do
io @println "success".
assert(isdefined "x").
assert(isdefined "y").
else
io @println "not matched".
assert(not isdefined "x").
assert(not isdefined "y").
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 (BIND '[' ID (AS ID)? (',' ID (AS ID)?)*']')?
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
.
The optional bind
term together with an appropriate list of variable names
allows the user to selectively project variable bindings from patterns that have been
constructed using the %[...]%
scope operator.
into the current scope. The as
keyword allows you to rename those bindings.
Consider the following program,
let Pair = pattern %[(x,y)]%.
let *Pair bind [x as a, y] = (1,2).
assert( a == 1).
assert(y == 2).
At the second let
statement we bind the x
as a
and y
from the scope
of the pattern into our current scope.
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.
Conditional Patterns (1)
Syntax: exp ':' pattern
These patterns allow you to express constraints on exp based on the pattern.
Example,
let x:%integer = val.
The variable x
will be bound to the value of val
if that value matches the
type pattern %integer
.
These patterns are a syntactic short hand for the equivalent conditional pattern,
exp if exp is pattern
That means the following two let
statements are equivalent,
let x:(q,p) = (1,2).
let x if x is (q,p) = (1,2).
Conditional Patterns (2)
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.
Patterns with Scope
Syntax: %[ pattern ]% (BIND '[' ID (AS ID)? (',' ID (AS ID)?)*']')?
A pattern with scope is a pattern that does not create any bindings
in the current scope. Any pattern can be turned into a scoped 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 scoped 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 scoped pattern stored in p
. This pattern match will only succeed if val
is 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) (AS ID)? '.'?
| GLOBAL id_list '.'?
| 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 '.'?
| MATCH exp (WITH pattern DO stmt_list)* END
| 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 ']%' binding_list?
| 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 binding_list?
| NOT call_or_index
| MINUS call_or_index
| PLUS call_or_index
| '(' tuple_stuff ')'
| '[' list_stuff ']'
| function_const
| TYPEMATCH // TYPEMATCH == '%'<typename>
binding_list
: BIND binding_list_suffix
binding_list_suffix
: binding_term
| '[' binding_term (',' binding_term)* ']'
binding_term
: ID (AS ID)?
tuple_stuff
: exp (',' exp?)*
| empty
list_stuff
: exp (',' exp)*
| empty
function_const
: LAMBDA body_defs
Notes on Function Argument Notation
Functions in Asteroid are multi-dispatch functions and therefore can be called with a variety
of input configurations. This is reflected in the documentation of built-in functions and
functions belonging to modules: when a function can be called with different input argument
configurations then the documentation reflects this by providing different argument configuration
separated by a ‘|
’ symbol. E.g.,
list @pop () | ix:%integer
indicating that the list member function pop
can be called either with the empty argument ()
or with a
single integer value.
Builtin Functions
- assert x
Throws an exception if x evaluates to false; otherwise it returns a none value.
- eval x:%string
Evaluate x as a piece of Asteroid code and return the computed value. The following is a simple example,
let a = eval "1+1". assert(a == 2).
- escape x:%string
Evaluate x as a piece of Python code and return the computed value. For more details please see the section on embedding Python code in this reference guide.
- getid x
Returns a unique id of any Asteroid object as an integer.
- gettype x
Returns the type of x as a string.
- hd x:%list
Returns the first element of a list. It is an error to apply this function to an empty list.
- isdefined x:%string
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.
- islist x
Returns true if x is a list otherwise it will return false.
- isnone x
Returns true if x is equal to the value none.
- isscalar x
Returns true if x is either an integer or a real value.
- len x
Returns the length of x. The function can only be applied to lists, strings, tuples, or structures.
- range stop:%integer | (start:%integer, stop:%integer) | (start:%integer, stop:%integer, inc:%integer)
Compute a list of values depending on the input values:
If only the stop value is given then the list [0 to stop-1] is returned.
If the start and stop values are given then the list [start to stop-1] is returned.
If in addition to the start and stop values the inc values is given then the list [start to stop-1 step inc] is returned.
- tl x:%list
Returns the rest of the list without the first element. It is an error to apply this function to an empty list.
- tobase (x:%integer,base:%integer)
Represents the given integer x as a numeral string in different bases.
- tointeger (x:%string,base:%integer) | x
Converts a given input to an integer. If a base value is specified then the resulting integer is in the corresponding base.
- toplevel ()
Returns true if flow of control is in the “toplevel” module, that is, the module with which the the interpreter was called; otherwise it will return false.
- toreal x
Returns the input as a real number.
- tostring x | (x,stringformat(width:%integer,precision:%integer,scientific:%boolean))
Converts an Asteroid object to a string. If format values are given, it applies the formatting to the string object.
List and String Objects
In Asteroid, both lists
and strings,
are treated like objects in the OO sense. 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 ()
Member Functions
- list @append item
Adds the item to the end of the list.
- list @clear ()
Removes all items from the list.
- list @copy ()
Returns a shallow copy of the list.
- list @count item
Returns the number of times item appears in the list.
- list @extend item
Extend the list by adding all the elements from the item to the list where the item is either a list or a tuple.
- list @filter f:%function
Returns a list constructed from those elements for which function f returns true.
- list @index item | (item, loc(startix:%integer) | (item, loc(startix:%integer, endix:%integer))
Returns a zero-based index of the first element whose value is equal to item. It throws an exception if there is no such item. The argument loc 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 list rather than the startix argument.
- list @insert (ix:%integer, item)
Insert the item into the list at the position i. This means that
a@insert(0, x)
inserts x at the front of the list, anda@insert(a@length(), x)
is equivalent toa@append(x)
.- list @join join_str:%string
Turns the list into a string using join_str between the elements. The string is returned as the return value from this function.
- list @length ()
Returns the number of elements within the list.
- list @map f:%function
Applies the function f to each element of the list in place. The modified list is returned.
- list @member item
Returns true only if item exists on the list.
- list @pop () | ix:%integer
Removes the item at the given position in the list and returns it. If no index is specified removes and returns the last item in the list.
- list @reduce f:%function | (f:%function, init)
Reduce the list to a value by applying the function f to all the members of the list. The function f has to be a function with two arguments where the first argument is the accumulator. If no initial value is given then the first element of the list is assumed to be the first accumulator value. In order to illustrate, we have:
let value = [1,2] @reduce (lambda with (x,y) do x+y, 0). assert(value == 3).
is equivalent to
let l = [1,2]. let value = 0. for i in range(l@length()) do let value = (lambda with (x,y) do x+y) (value,l@i). end assert(value == 3).
- list @remove item
Removes the first element from the list whose value is equal to item. It throws an exception if there is no such item.
- list @reverse ()
Reverses the elements of the list in place and returns the reversed list.
- list @shuffle ()
Creates a random permutation of the list in place and returns the randomized list.
- list @sort () | reverse:%boolean
Sorts the items of the list in place and returns the sorted list. If the boolean reverse is set to true then the sorted list is reversed.
Strings
A string is a sequence of characters surrounded by double quotes. In Asteroid, single characters are represented as single character strings. Similar to lists the member functions of strings can be called directly on the data structure itself, e.g.:
"Hello there" @length ()
Member Functions
- string @explode ()
Returns the string as a list of characters.
- string @flip ()
Returns a copy of the string with its characters in the reverse order.
- string @index item:%string | (item:%string, loc(startix:%integer)) | (item:%string, loc(startix:%integer, endix:%integer))
Returns an integer index of the item in the string or -1 if item was not found. The argument loc 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.
- string @length ()
Returns the number of characters within the string.
- string @replace (old:%string, new:%string) | (old:%string, new:%string, count:%integer)
Return a copy of the string with all occurrences of regular expression old replaced by the string new. If the argument count is given, only the first count occurrences are replaced.
- string @split () | sep:%string | (sep:%string, count:%integer)
Return a list of the words in the string, using sep as the delimiter. If count is given then at most count splits are done (thus, the list will have at most count+1 elements). If count is not specified or -1, then there is no limit on the number of splits (all possible splits are made). Consecutive delimiters are not grouped together and are deemed to delimit empty strings. For example:
let s = "1,,2" @split ",". assert (s == ["1", "", "2"]).
The sep argument may consist of multiple characters. For example:
let s = "1<>2<>3" @split "<>". assert (s == ["1", "2", "3"]).
Splitting an empty string with a specified separator returns
[""]
. If sep is not specified or is None, a different splitting algorithm is applied: consecutive whitespace is 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[]
.- string @tolower ()
Returns a copy of the string in all lower case letters.
- string @toupper ()
Returns a copy of the string in all upper case letters.
- string @trim () | what:%string
Returns a copy of the string with the leading and trailing characters removed. The what argument specifies the set of characters to be removed. If omitted trim defaults to removing whitespace. The what argument is not a prefix or suffix; rather, all combinations of its characters are stripped.
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
This module defines bitwise operations on integers. It supports the following functions,
- bitwise @band (x:%integer, y:%integer)
Performs the bitwise AND operation and returns the result as an integer.
- bitwise @bclearbit (x:%integer, i:%integer)
Clear the ith bit in x and returns the result as an integer.
- bitwise @blrotate (x:%integer, i:%integer)
Performs the bitwise left rotate operation by i bits and returns the result as an integer.
- bitwise @blshift (x:%integer, y:%integer)
Performs the bitwise left shift operation where x is shifted by y bits and returns the result as an integer.
- bitwise @bnot x:%integer
Performs the bitwise NOT operation and returns the result as an integer.
- bitwise @bor (x:%integer, y:%integer)
Performs the bitwise OR operation and returns the result as an integer.
- bitwise @brrotate (x:%integer, i:%integer)
Performs the bitwise right rotate operation by i bits and returns the result as an integer.
- bitwise @brshift (x:%integer, y:%integer)
Performs the bitwise right shift operation where x is shifted by y bits and returns the result as an integer.
- bitwise @bsetbit (x:%integer, i:%integer)
Sets the ith bit in x and returns the result as an integer.
- bitwise @bsize x:%integer
Returns the bit size of x.
- bitwise @bxor (x:%integer, y:%integer)
Performs the bitwise XOR operation and returns the result as an integer.
hash
This module implements a hash for key-value pairs. It supports the following functions,
- hash @hash ()
Returns a new hash object of type __HASH__.
- __HASH__ @aslist ()
Returns the hash as a list of key-value pairs.
- __HASH__ @get key
Return the value associated with the given key as long as it can be found otherwise an exception will be thrown.
- __HASH__ @insert (key, value) | pairs:%list
Given a pair of the format (key, value) insert it into the table. Given a list of the format:
[(key1, val1), (key2, val2), ...]
insert all the key-value pairs on the list into the hash.
io
This module implements Asteroid’s I/O system. The module defines three I/O streams,
__STDIN__ - the standard input stream.
__STDOUT__ - the standard output stream.
__STDERR__ - the standard error stream.
Furthermore, the module supports the following functions,
- io @close file:%__FILE__
Closes the file where file is a file descriptor of type __FILE__.
- io @input () | prompt:%string
Ask the user for input from __STDIN__. The input is returned as a string. If prompt is given it is printed and then input is read from terminal.
- io @open (name:%string, mode:%string)
Returns a file descriptor of type __FILE__. The mode 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. Finally, “r+” opens the file for both reading and writing.
- io @print item
Prints item to the terminal (__STDOUT__). No implicit newline is appended to the output.
- io @println item
Prints item to the terminal (__STDOUT__) with an implicit newline character.
- io @read () | file:%__FILE__
Read a file and return the contents as a string. If no file is given the __STDIN__ stream is read.
- io @readln () | file:%__FILE__
Reads a line of input from a file and returns it as a string. If no file is given the __STDIN__ stream is read.
- io @write what:%string | (file:%__FILE__, what:%string)
Write what to a file. If file is not given then it writes to the __STDOUT__ stream.
- io @writeln what:%string | (file:%__FILE__, what:%string)
Write what to a file and append a newline charater. If file is not given then it writes to __STDOUT__.
math
The math module implements mathematical constants and functions. An example:
load system io.
load system math.
let x = math @sin( math @pi / 2.0 ).
io @println("The sine of pi / 2 is " + tostring x + ".").
Constants
- math @pi
The mathematical constant π = 3.141592…, to available precision.
- math @e
The mathematical constant e = 2.718281…, to available precision.
- math @tau
The mathematical constant τ = 6.283185…, to available precision.
Power and logarithmic functions
- math @exp x:%integer
Returns e raised to the power x, where e = 2.718281… is the base of the natural logarithm.
- math @expm1 x
Returns e raised to the power x minus 1. This function maintains a higher level of precision then the standard operation.
- math @isqrt x:%real
Returns the floor of the square root of x as a integer.
- math @ldexp (x,i)
Returns x * (2^i).
- math @log x | (x, base:%integer)
If only argument x is the input, return the natural logarithm of x (to base e). If two arguments, (x, base:%integer), are given as input, return the logarithm of x to the given base, calculated as log(x)/log(base).
- math @log1p x
Returns the natural logarithm of 1 + x.
- math @log2 x
Returns the base 2 logarithm of x.
- math @log10 x
Returns the base 10 logarithm of x.
- math @pow (b, p:%integer)
Returns b raised to the power p. The return type depends on the type of the base.
- math @sqrt x
Returns the square root of x as a real.
Number-theoretic and representation functions
- math @abs x
Returns that absolute value of x. The return type depends on the type of x.
- math @ceil x:%real
Returns the ceiling of x: the smallest integer greater than or equal to x.
- math @comb (n:%integer,k:%integer)
Returns the numbers of ways to choose k items from n total items without repetition and without order. This is equal to n! / k!(n - k)!.
- math @copysign (x,y)
Returns a real with the absolute value of x and the sign of y.
- math @dist (x,y)
Return the Euclidean distance as a float between two points x and y, each given as a tuple or list of coordinates.
- math @factorial (n:%integer)
Returns the factorial of the integer n.
- math @floor x:%real
Returns the floor of x: the largest integer less than or equal to x.
- math @fmod (v,d)
Implements the modulus operation as defined by the platform C library. This is equal to v - n*d for some integer n such that the result has the same sign as v and magnitude less than abs(d).
- math @fsum x:%list | x:%tuple
Calculate the sum of all the elements of a list or tuple. This function carries a higher floating point precison level than the standard sum() function by tracking multiple intermediate partial sums.
- math @gcd (a:%integer, b:%integer)
Returns the greatest common denominator that both integers share.
- math @isclose (a:%real, b:%real) | (a:%real, b:%real, t:%real)
Return true if the values a and b are close to each other and false otherwise. Default tolerance is 1e-09. An alternative tolerance can be specified with the t argument.
- math @lcm (a:%integer,b:%integer)
Returns the least common multiple of the integers a and b.
- math @mod (v,d)
Implements the modulus operation. Returns the remainder of the quotient v/d.
- math @perm (n:%integer, k:%integer)
Returns the numbers of ways to choose k items from n total items without repetition and with order. This is equal to n! / (n - k)!.
- math @prod x:%list | x:%tuple
Calculate and return the product of all the elements of a list or tuple x.
- math @remainder (x,y)
Returns the IEEE 754-style remainder of x with respect to y.
- math @round x:%real
Returns x rounded to the nearest integer. If two integers are equally close, x is rounded to the nearest even integer.
- math @sum x:%list | x:%tuple
Calculate and return the sum of all the elements of a list or tuple x.
- math @trunc x:%real
Returns x with its fractional component set to 0.
Trigonometric functions
- math @acos x
Returns the arc cosine of x in radians. The result is between 0 and pi.
- math @asin x
Returns the arc sine of x in radians. The result is between -pi/2 and pi/2.
- math @atan x
Returns the arc tangent of x in radians. The result is between -pi/2 and pi/2.
- math @atan2 (x,y)
Returns the arc tangent of y / x in radians. The result is between -pi and pi.
- math @cos x
Returns the cosine of x radians.
- math @hypot (x,y)
Returns the Euclidean norm as a real of x and y. This is equivalent to sqrt(x^2 + y^2).
- math @sin x
Returns the sine of x radians.
- math @tan x
Returns the tangent of x radians.
Hyperbolic functions
- math @acosh x
Returns the inverse hyperbolic cosine of x.
- math @asinh x
Returns the inverse hyperbolic sine of x.
- math @atanh x
Returns the inverse hyperbolic tangent of x.
- math @cosh x
Returns the hyperbolic cosine of x.
- math @sinh x
Returns the hyperbolic sine of x.
- math @tanh x
Returns the hyperbolic tangent of x.
Angular conversion
- math @degrees x
Converts angle x from radians to degrees.
- math @radians x
Converts angle x from degrees to radians.
Special functions
- math @erf x
Returns the error function (also called the Gauss error function) at x.
- math @erfc x
Returns the complement of the error function at x. The is defined as 1 - erf(x)
- math @gamma x
Returns the Gamma function at x.
- math @lgamma x
Returns the natural logarithm of the absolute value of the Gamma function at x.
os
This module provides a portable way of using operating system dependent functionality.
Process Parameters
- os @argv
The list of command line arguments passed to an Asteroid script. argv[0] is the name of the Asteroid script (it is operating system dependent whether this is a full pathname or not). In interactive mode argv[0] will be the empty string.
- os @env
A hash table where keys and values are strings that represent the process environment. For example,
os @env @get “HOME”
is the pathname of your home directory (on some platforms), and is equivalent to getenv(“HOME”) in C.
- os @platform
This string contains a platform identifier.
Functions
- os @basename path:%string
Return the base name of pathname path. This is the second element of the pair returned by passing path to the function split. Note that the result of this function is different from the Unix basename program; where basename for ‘/foo/bar/’ returns ‘bar’, the basename function returns an empty string (“”).
- os @chdir path:%string
Change the current working directory to path.
- os @dirname path:%string
Return the directory name of pathname path. This is the first element of the pair returned by passing path to the function split.
- os @exists path:%string
Return true if path refers to an existing path or an open file descriptor. Returns false for broken symbolic links. On some platforms, this function may return False if permission is not granted to execute stat on the requested file, even if the path physically exists.
- os @exit () | v:%integer | msg:%string
Signaling an intention to exit the interpreter. When an argument value other than none is provided it is considered a status value. If it is an integer, zero is considered “successful termination” and any nonzero value is considered “abnormal termination” by shells and the like. Most systems require it to be in the range 0–127, and produce undefined results otherwise. Some systems have a convention for assigning specific meanings to specific exit codes, but these are generally underdeveloped; Unix programs generally use 2 for command line syntax errors and 1 for all other kind of errors. If none is given as an argument value then is it is considered to be a successful exit equivalent to passing a zero. If a string is passed then it is printed printed to __STDERR__ and results in an exit code of 1. In particular, sys.exit(“some error message”) is a quick way to exit a program when an error occurs.
- os @getdir ()
Return a string representing the current working directory.
- os @getpathtime path:%string | (path:%string,flag:%boolean)
Returns a triple with (creation, access, modification) times. By default the return value is a triple of real numbers giving the number of seconds since 1/1/1970. If the flag is set to true then a triple of strings is returned where each string represents the respective local time. Throws an exception if the file does not exist or is inaccessible.
- os @getsize path:%string
Return the size, in bytes, of path. Throws exception if the file does not exist or is inaccessible.
- os @isfile path:%string
Return true if path is an existing regular file. This follows symbolic links.
- os @isdir path:%string
Return true if path is an existing directory. This follows symbolic links.
- os @join (path1:%string,path2:%string)
Join path1 and path2 components intelligently. The return value is the concatenation of path and any members of *paths with exactly one directory separator following each non-empty part except the last, meaning that the result will only end in a separator if the last part is empty. If the second component is an absolute path, the first component is thrown away.
On Windows, the drive letter is not reset when an absolute path component (e.g., r’foo’) is encountered. If a component contains a drive letter, all previous components are thrown away and the drive letter is reset. Note that since there is a current directory for each drive, os.path.join(“c:”, “foo”) represents a path relative to the current directory on drive C: (c:foo), not c:foo.
- os @split path:%string
Split the pathname path into a pair, (head, tail) where tail is the last pathname component and head is everything leading up to that. The tail part will never contain a slash; if path ends in a slash, tail will be empty. If there is no slash in path, head will be empty. If path is empty, both head and tail are empty. Trailing slashes are stripped from head unless it is the root (one or more slashes only). Also see the functions dirname and basename.
- os @splitdrive path:%string
Split the pathname path into a pair (drive, tail) where drive is either a mount point or the empty string. On systems which do not use drive specifications, drive will always be the empty string. In all cases, drive + tail will be the same as path.
On Windows, splits a pathname into drive/UNC sharepoint and relative path.
If the path contains a drive letter, drive will contain everything up to and including the colon.
- os @splitext path:%string
Split the pathname path into a pair (root, ext) such that root + ext == path, and the extension, ext, is empty or begins with a period and contains at most one period. If the path contains no extension, ext will be the empty string.
- os @syscmd cmd:%string
Execute a command in a subshell. This is implemented by calling the Standard C function system, and has the same limitations. If command generates any output, it will be sent to the interpreter standard output stream. The C standard does not specify the meaning of the return value of the C function, so the return value of this function is system-dependent.
patterns
The patterns module implements common patterns. An example:
load system patterns.
let evens = [].
for num in 1 to 10 do
if num is *patterns@even do
let evens = evens@append(num).
end
end
assert(evens is [2,4,6,8,10]).
If a pattern only applies to a certain datatype then a constraint expression of the form :%<datatype>
appears
right after the pattern in the documentation. If the pattern applies to multiple datatypes then the
different datatypes are separated by or-bars, e.g. :%<datatype1>|%<datatype2>
.
Common number sets
- patterns @digit : %integer
Matches single digit integers.
- patterns @even : %integer
Matches integers which are even numbers.
- patterns @nat : %integer
Matches integers which are natural numbers.
- patterns @negative : %integer | %real
Matches negative reals and integers.
- patterns @neg_int : %integer
Matches negative integers.
- patterns @neg_int : %integer
Matches negative integers.
- patterns @odd : %integer
Matches integers which are odd numbers.
- patterns @positive : %integer | %real
Matches positive reals and integers.
- patterns @pos_int : %integer
Matches positive integers.
- patterns @pos_real : %real
Matches postive reals.
- patterns @prime : %integer
Matches prime numbers.
- patterns @zero : %integer | %real
Matches the value 0, either as an integer or a real.
Containers
- patterns @bool_list : %list
Matches lists which only contain booleans.
- patterns @func_list : %list
Matches lists which only contain functions.
- patterns @int_list : %list
Matches lists which only contain integers.
- patterns @list_list : %list
Matches lists which only contain lists.
- patterns @real_list : %list
Matches lists which only contain reals.
- patterns @str_list : %list
Matches lists which only contain strings.
- patterns @tuple_list : %list
Matches lists which only contain tuples.
Strings
- patterns @alphabetic : %string
Matches strings which only contain alphabetic characters.
- patterns @alphanumeric : %string
Matches strings which only contain alphanumeric characters.
- patterns @lowercase : %string
Matches strings which only contain lowercase alphabetic characters.
- patterns @numeric : %string
Matches strings which only contain numeric characters.
- patterns @uppercase : %string
Matches strings which only contain uppercase alphabetic characters.
pick
The pick module implements pick objects that allow a user to randomly pick items from a list of items using the pickitems function. An example:
load system io.
load system pick.
let po = pick @pick [1 to 10].
let objects = po @pickitems 3.
io @println objects.
- pick @pick l:%list
Construct a pick object of type __PICK__.
- __PICK__ @pickitems () | n:%integer
Return items randomly picked from the list l. If no input is provided then pickitems will return a single, randomly picked item from the list. If an integer value n is given then a list of n randomly picked items from the list l is returned. The picked item list is constructed by sampling the list l with replacement.
random
The random module implements random number generation.
- random @randint (lo:%integer,hi:%integer) | (lo:%real,hi:%real)
Return a random value N in the interval lo <= N <= hi. The type of the random value depends on the types of the values specifying the interval. If the interval is specified with integers then a random integer value is returned. If the interval is specified with real numbers then a real value is is returned, and for everything else an exception is thrown.
- random @random ()
Return a random real number in the range [0.0, 1.0).
- random @seed x:%integer
Provide the seed value x for the random number generator.
set
The set module implements Asteroid sets as lists. Unlike lists, sets do not have repeated elements. Use the set member function toset to turn any list into a list that represents a set (remove repeated items).
- set @diff (a:%list,b:%list)
Return the difference set between sets a and b.
- set @intersection (a:%list,b:%list)
Return the intersection of sets a and b.
- set @toset l:%list
Return list l as a set by removing repeated elements.
- set @union (a:%list,b:%list)
Return the union of sets a and b.
- set @xunion (a:%list,b:%list)
Return the elements in a or b but not 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 true if x<y else false),
[10,5,110,50]).
io @println sl.
prints the sorted list:
[5,10,50,110]
- sort @sort (p:%function,l:%list)
Returns the sorted list l using the predicate p.
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. A simple use case:
load system io.
load system stream.
let s = stream @stream [1 to 10].
while not s @eof() do
io @print (tostring (s @get()) + " ").
end
io @println "".
which outputs:
1 2 3 4 5 6 7 8 9 10
- stream @stream l:%list
Returns a stream object of type __STREAM__.
- __STREAM__ @append x
Adds x to the end of the stream.
- __STREAM__ @eof ()
Returns true if the stream does not contain any further elements for processing. Otherwise it returns false.
- __STREAM__ @get ()
Returns the current element and moves the stream pointer one ahead. Returns none if no elements left in stream.
- __STREAM__ @map f:%function
Applies function f to each element in the stream.
- __STREAM__ @peek ()
Returns the current element available on the stream otherwise it returns none.
- __STREAM__ @rewind ()
Resets the stream pointer to the first element of the stream.
util
The util module defines utility functions and structures that don’t really fit into any other modules.
- util @achar x
Given a decimal ASCII code x, return the corresponding character symbol.
- util @ascii x:%string
Given a character x, return the corresponding ASCII code of the first character of the input.
- util @cls ()
Clears the terminal screen.
- util @copy x
Given the object x, make a deep copy of it.
- util @ctime x:%real
Given a real value representing seconds since 1/1/1970 this function converts it to a suitable string representation of the date.
- type @sleep x
Sleep for x seconds where the x is either an integer or real value.
- type @time ()
Returns the local time as a real value in secs since 1/1/1970.
- type @unzip x:%list
Given a list of pairs x this function 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.
- type @zip (list1:%list,list2:%list)
Returns a list where element i of the list is the tuple (list1@i,list2@i).
vector
The vector defines functions useful for vector arithmetic. Vectors are implemented as lists.
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
.
- vector @add (a:%list,b:%list)
Returns a vector that contains the element by element sum of the input vectors a and b.
- vector @dot (a:%list,b:%list)
Computes the dot product of the two vectors a and b.
- vector @mult (a:%list,b:%list)
Returns the element by element vector multiplication of vectors a and b.
- vector @op (f:%function,a:%list,b:%list) | (f:%function,a:%list,b if type @isscalar(b)) | (f:%function,a if type @isscalar(a),b:%list)
Allows the developer to vectorize any function f. Applying scalar values to vectors is also supported by this function.
- vector @sub (a:%list,b:%list)
Returns the element by element difference vector.
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.3
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 linux
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
function 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
function 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
is a function 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.