Introduction to Aurora
Aurora is a powerful yet remarkably simple programming language designed for scripting and automation. With its Lua/Ruby-like syntax and dynamic typing, Aurora offers an easy learning curve for users while enabling them to tackle complex tasks efficiently. It aims to replace the long-standing (mis)use of shell scripts for intricate operations.
Features
- Lua/Ruby-like syntax: Aurora adopts a syntax reminiscent of popular scripting languages Lua and Ruby, making it familiar and approachable to developers.
- Dynamic typing: The language supports dynamic typing, allowing variables to hold values of different types throughout their lifetime.
- Extraordinarily simple: Aurora prioritizes simplicity, emphasizing straightforwardness and ease of use.
Example
# This is a comment
# This is a subroutine
sub hello name
print fmt"Hello, {name}!"
end
# This is a function
fn add a, b
return a + b
end
# This is a variable
name = "world"
# This is a top-level call (subroutine or function)
hello name # Hello, world!
print add(1, 2) # Cannot call a subroutine like this; only functions
Functions vs. Subroutines
In Aurora, there are two types of top-level calls: functions and subroutines.
- Functions: Functions are called using the syntax
function_name(arguments, ...)
. They can be invoked from anywhere in the program, including within expressions. Functions can return values and are defined using thefn
keyword. - Subroutines: Subroutines are called using the syntax
subroutine_name arguments, ...
. They can only be called from the top level of the program and cannot be invoked within expressions. Subroutines do not return values and are defined using thesub
keyword.
Variables
Aurora employs scoping rules similar to those found in most C-like languages.
# This is a global variable
global = "global"
# This is a 'do' block; an anonymous subroutine
do
# This is a local variable
local = "local"
end
# The 'do' block is called here, as it's at the top level
print global # Output: global
print local # Error: undefined variable 'local'
Nil
In Aurora, the absence of a nil value means that functions must always return a value. Consequently, when a function does not have a specific value to return, it is declared as a subroutine. By designating such functions as subroutines, Aurora ensures clarity and avoids confusion. Subroutines are meant to execute actions or operations without returning a value, highlighting their purpose of performing tasks rather than producing specific outputs. This distinction allows developers to write more precise and understandable code, enhancing code organization and reducing potential errors related to missing or unexpected return values.
Types
Aurora includes the following types:
str
: Represents a string of characters.num
: Represents a numeric value.bool
: Represents a boolean value (true
orfalse
).list
: Represents a collection of values.fn
: Represents a function.sub
: Represents a subroutine.map
: Represents a mapping of keys to values.
Aurora's concise syntax and simplicity make it an appealing choice for scripting and automating tasks efficiently.
Aurora vs. Shell
Aurora outshines traditional shell scripting with its focus on readability and simplicity. Its intuitive syntax, plain English function names, and clean structure enhance code comprehension. Aurora's built-in functions and higher-level abstractions simplify complex tasks, reducing convoluted command chains. This results in concise, straightforward code that is easier to understand and maintain. By prioritizing readability and simplicity, Aurora offers an approachable and error-resistant scripting experience, boosting productivity.
#!/bin/bash
directory="/path/to/directory"
echo "Listing files in $directory:"
for file in "$directory"/*; do
if [ -f "$file" ]; then
filename=$(basename "$file")
size=$(du -sh "$file" | awk '{print $1}')
echo "File: $filename | Size: $size"
fi
done
#!/bin/aurora
directory = "/path/to/directory"
print fmt"Listing files in {directory}:"
for file, dir(directory)
if not isDir(file)
print fmt"File: {basename(file)} | Size: {size(file, 'kb)}"
end
end
Control Flow
Aurora has the control flow you'd expect from a modern language. It also has a few features that are (somewhat) unique to Aurora.
If Statements
If statements are pretty standard.
if condition
# do something
end
If statements can also have an else
clause.
if condition
# do something
else
# do something else
end
Unless Statements
Unless statements are the opposite of if statements.
unless condition
# do something
end
Unless statements can also have an else
clause.
unless condition
# do something
else
# do something else
end
While Loops
While loops are pretty standard.
while condition
# do something
end
Until Loops
Until loops are the opposite of while loops.
until condition
# do something
end
For Loops
For loops iterate over a list.
For doing something a certain number of times, see times
.
for i, upTo(10)
# do something
end
Break and Continue
Break and continue are pretty standard.
while condition
if condition
break
end
if condition
continue
end
end
Switch Statements
Switch statements are pretty standard.
switch value
case 1
# do something
case 2
# do something else
else
# do something else
end
Select Statements
Select statements are like a chain of if statements.
select
case condition1
# do something
case condition2
# do something else
else
# do something else
end
Functions and Subroutines
In Aurora, functions and subroutines are defined using the fn
and sub
keywords, respectively.
Functions
Functions are defined using the fn
keyword, followed by the function's name, its parameters, and its body.
It must always return a value.
fn add a, b
return a + b
end
# or, using the shorthand syntax:
fn add a, b -> a + b
Subroutines
Subroutines are defined using the sub
keyword, followed by the subroutine's name, its parameters, and its body.
Subroutines cannot return a value.
They can return early using the return
keyword, but it must not be followed by any value.
sub say_hi name
print "Hello, ", name
end
# or, using the shorthand syntax:
sub say_hi name -> print "Hello, ", name
Anonymous Functions and Subroutines
Functions and subroutines can also be anonymous.
They are do
and lambda
blocks, respectively.
# Anonymous function:
add = lambda a, b -> a + b
# or:
add = lambda a, b
return a + b
end
# Anonymous subroutine:
say_hi = do name
print "Hello, ", name
end
Calling Functions and Subroutines
There are two places where functions can be called: in expressions and in statements. At the top level, functions can be called, just discarding the result. However, subroutines can only be called in statements, as they produce no result.
# Calling a function in an expression:
print add(1, 2) # 3
# Calling a function at the top level:
add 1, 2 # Discards the result, but still executes the function
# Calling a subroutine in a statement:
print "Hello, World!"
Top-Level Calls
Top-level calls don't have to be to a named function or subroutine.
They can also be calls to do
blocks or lambda
block expressions, or any other expression (list indexing, for example).
If an expression is at the top level, it is immediately evaluated, and the result must be a callable value.
# Calling a do block:
do
print "Hello, World!"
end # It is immediately executed, as it's at the top level
foo = [print]
foo:0 "Hello, World!" # Calling a subroutine in a list
Literal Values
Aurora has a few literal values that can be used in expressions.
String Literals
String literals are surrounded by double quotes ("
), or backticks (`
).
There is also a shorthand syntax for string literals, which is the '
character followed by a single word. It does not have an end delimiter.
"Hello, world!" # A string literal
`Hello, world!` # Also a string literal
'hello # Shorthand syntax for string literals, known as a symbol in other languages
You can do string interpolation with the fmt
operator.
It takes a string, with expressions inside curly braces ({}
).
name = "world"
print fmt"Hello, {name}!"
Number Literals
Number literals are pretty standard. They can be written in decimal, hexadecimal, octal, or binary. They can also be written in scientific notation.
123 # Decimal
0x123 # Hexadecimal
0o123 # Octal
0b101 # Binary
1.23e2 # Scientific notation
Boolean Literals
Boolean literals are either true
or false
.
true
false
List Literals
List literals are surrounded by square brackets ([]
).
They can contain any number of values, separated by commas.
[1, 2, 3]
Map Literals
Map literals are surrounded by curly braces ({}
).
They can contain any number of key-value pairs, separated by commas, or newlines.
{
foo -> 2, bar -> 4
baz -> 6
}
Variables
Variables in Aurora are defined using no keywords, but just the variable's name and its value.
a = 1
Variables can be reassigned.
a = 1
a = 2
Variables can be reassigned to a different type.
a = 1
a = "Hello, world!"
You can add, subtract, multiply, and divide variables.
a = 1
a += 1 # 2
a -= 1 # 1
a *= 2 # 2
a /= 2 # 1
Variables are scoped to the block they are defined in, or globally if defined at the top level.
a = 1
if true
a = 2
end
print a # 2
do
b = 1
end
print b # Error: b is not defined
Constants
Constants are denoted by an uppercase first letter.
A = 1
Constants cannot be reassigned.
A = 1
A = 2 # Error: A is a constant
Lists
Lists are declared as comma-seperated values between two square brackets.
[1, 2, 3]
Lists can be indexed using the :
operator.
a = [1, 2, 3]
print a:0 # 1
print a:1 # 2
print a:2 # 3
You can assign to a list index.
a = [1, 2, 3]
a:0 = 4
print a # [4, 2, 3]
See List Functions for functions that operate on lists.
Maps
Maps are a type of collection that store key-value pairs. They are also known as dictionaries or associative arrays in other languages.
In Aurora, maps are surrounded by curly braces ({}
), and contain key-value pairs separated by commas or newlines.
{
foo -> 2, bar -> 4
baz -> 6
}
Maps can be accessed using either the :
operator, or the ::
operator.
m = {
foo -> 2, bar -> 4
baz -> 6
}
print m:"foo" # 2
print m::foo # 2
You can assign to a map index.
m = {
foo -> 2, bar -> 4
baz -> 6
}
m::foo = 3 # or m:"foo" = 3
print m # {foo -> 3, bar -> 4, baz -> 6}
See Map Functions for functions that operate on maps.
Operators
Precedence Table
Precedence | Operator | Description |
---|---|---|
1 | - , fmt | Unary minus, string formatting |
2 | () , : , :: | Function call, index, map index |
3 | not | Logical not |
4 | * , / , % , . | Multiplication, division, mod, apply operator |
5 | + , - | Addition, subtraction |
6 | == , != , < , <= , > , >= | Comparison |
7 | and | Logical and |
8 | or | Logical or |
Arithmetic Operators
1 + 1 # 2
1 - 1 # 0
1 * 1 # 1
1 / 1 # 1
1 % 1 # 0
Comparison Operators
1 == 1 # true
1 != 1 # false
1 < 1 # false
1 <= 1 # true
1 > 1 # false
1 >= 1 # true
Logical Operators
true and false # false
true or false # true
not true # false
String Formatting Operator
The string formatting operator (fmt
) is used to format a string.
It takes a string, with expressions inside curly braces ({}
).
name = "world"
print fmt"Hello, {name}!"
Apply Operator
The apply operator (.
) is used to call a function on a value.
It basically re-orders it's operands, so f.g(x)
is equivalent to g(f, x)
.
The parentheses are optional if the function takes only one argument.
fn plus_one num -> num + 1
print 1.plus_one # 2
fn add num1, num2 -> num1 + num2
print 1.add(2) # 3
Function Call Operator
The function call operator (()
) is used to call a function.
It has a comma-separated list of arguments inside the parentheses.
fn plus_one num -> num + 1
print plus_one(1) # 2
Index Operator
The index operator (:
) is used to index a list or map.
a = [1, 2, 3]
print a:0 # 1
print a:1 # 2
print a:2 # 3
m = {
foo -> 2, bar -> 4
baz -> 6
}
print m:"foo" # 2
Map Index Operator
The map index operator (::
) is used to index a map without quotes.
m = {
foo -> 2, bar -> 4
baz -> 6
}
print m::foo # 2
The Standard Library
The standard library is a collection of functions and variables that are distributed with the Aurora programming language. These items are imported by default in every Aurora program.
Conventions
Functions which do a "destructive" operation (i.e. they modify their arguments, manipulate the filesystem in a possibly destructive way, etc.) are suffixed with an exclamation mark (!
).
Functions which "ask" a question (i.e. they return a boolean value) are suffixed with a question mark (?
).
Type Signatures
The type signatures of functions and variables are written in a pseudo-Aurora syntax.
...
means that the function or variable takes a variable number of arguments.
Categories
The standard library is divided into these categories:
- I/O: Input and output functions.
- File: File manipulation functions.
- String: String manipulation functions.
- Math: Mathematical functions and constants.
- Time: Time and date functions.
- Type: Type checking and conversion functions.
- List: List manipulation functions.
- Misc: Miscellaneous functions.
Standard Library - I/O Functions
print
sub print any...
Description
Prints the given arguments to the standard output stream, with no separator between them.
Example
print "Hello, ", "world!"
ask
fn ask str -> str
Description
Asks the user for input and returns it.
Example
name = ask("What is your name? ")
print "Hello, ", name
read
fn read str -> str
Description
Reads the contents of a file and returns it as a string.
Example
contents = read("file.txt")
print contents
Standard Library - File Functions
copy!
sub copy! str str
Description
Copies a file from one path to another. Will overwrite the destination file if it already exists.
Example
copy! `file.txt`, `file2.txt`
copy
fn copy str str -> bool
Description
Copies a file from one path to another.
Will not overwrite the destination file if it already exists.
Returns true
if the file was copied, false
otherwise.
Example
copy `file.txt`, `file2.txt`
# error-checking
unless copy(`file.txt`, `file2.txt`)
print "Failed to copy file!"
end
move!
sub move! str str
Description
Moves a file from one path to another. Will overwrite the destination file if it already exists.
Example
move! `file.txt`, `file2.txt`
move
fn move str str -> bool
Description
Moves a file from one path to another.
Will not overwrite the destination file if it already exists.
Returns true
if the file was moved, false
otherwise.
Example
move `file.txt`, `file2.txt`
# error-checking
unless move(`file.txt`, `file2.txt`)
print "Failed to move file!"
end
delete!
sub delete! str
Description
Deletes a file.
Example
delete! `file.txt`
new!
sub new! str
Description
Creates a new file. Will overwrite the file if it already exists.
Example
new! `file.txt`
new
fn new str -> bool
Description
Creates a new file.
Will not overwrite the file if it already exists.
Returns true
if the file was created, false
otherwise.
Example
new `file.txt`
# error-checking
unless new(`file.txt`)
print "Failed to create file!"
end
exists?
fn exists? str -> bool
Description
Returns true
if the file exists, false
otherwise.
Example
if exists?(`file.txt`)
print "File exists!"
end
write!
sub write! str str
Description
Writes a string to a file. Will overwrite the file if it already exists.
Example
write! `file.txt`, "Hello, world!"
write
fn write str str -> bool
Description
Writes a string to a file.
Will not overwrite the file if it already exists.
Returns true
if the file was written to, false
otherwise.
Example
write `file.txt`, "Hello, world!"
# error-checking
unless write(`file.txt`, "Hello, world!")
print "Failed to write to file!"
end
Standard Library - String Functions
len
fn len str -> num
Description
Returns the length of the given string.
Example
print len("abc") # 3
split
fn split str, str -> [str]
Description
Splits the given string into substrings at each occurrence of the given separator and returns them as an array.
Example
print split("a,b,c", ",") # ["a", "b", "c"]
replace
fn replace str, str, str -> str
Description
Replaces all occurrences of the given substring in the given string with the given replacement.
Example
print replace("abcabc", "a", "x") # "xbcxbc"
substring
fn substring str num num -> str
Description
Returns the substring of the given string starting at the given index and ending at the given index.
Example
print substring("abc", 1, 2) # "bc"
Standard Library - Math Functions
Note
All of these functions are declared under the global math
object.
sin
fn sin num -> num
Description
Returns the sine of the given angle in radians.
Example
print math::sin(0) # 0
print math::sin(1) # 0.8414709848078965
cos
fn cos num -> num
Description
Returns the cosine of the given angle in radians.
Example
print math::cos(0) # 1
print math::cos(1) # 0.5403023058681398
tan
fn tan num -> num
Description
Returns the tangent of the given angle in radians.
Example
print math::tan(0) # 0
print math::tan(1) # 1.5574077246549023
asin
fn asin num -> num
Description
Returns the arc-sine of the given angle in radians.
Example
print math::asin(0) # 0
print math::asin(1) # 1.5707963267948966
acos
fn acos num -> num
Description
Returns the arc cosine of the given angle in radians.
Example
print math::acos(0) # 1.5707963267948966
print math::acos(1) # 0
atan
fn atan num -> num
Description
Returns the arc tangent of the given angle in radians.
Example
print math::atan(0) # 0
print math::atan(1) # 0.7853981633974483
sinh
fn sinh num -> num
Description
Returns the hyperbolic sine of the given angle in radians.
Example
print math::sinh(0) # 0
print math::sinh(1) # 1.1752011936438014
cosh
fn cosh num -> num
Description
Returns the hyperbolic cosine of the given angle in radians.
Example
print math::cosh(0) # 1
print math::cosh(1) # 1.5430806348152437
tanh
fn tanh num -> num
Description
Returns the hyperbolic tangent of the given angle in radians.
Example
print math::tanh(0) # 0
print math::tanh(1) # 0.7615941559557649
asinh
fn asinh num -> num
Description
Returns the inverse hyperbolic sine of the given angle in radians.
Example
print math::asinh(0) # 0
print math::asinh(1) # 0.881373587019543
acosh
fn acosh num -> num
Description
Returns the inverse hyperbolic cosine of the given angle in radians.
Example
print math::acosh(0) # NaN
print math::acosh(1) # 0
atanh
fn atanh num -> num
Description
Returns the inverse hyperbolic tangent of the given angle in radians.
Example
print math::atanh(0) # 0
print math::atanh(1) # NaN
exp
fn exp num -> num
Description
Returns the exponential of the given number.
The exponential function is defined as e^x
, where e
is Euler's number (approximately 2.71828
).
Example
print math::exp(0) # 1
print math::exp(1) # 2.718281828459045
expm1
fn expm1 num -> num
Description
Returns the exponential of the given number minus one.
The exponential function is defined as e^x
, where e
is Euler's number (approximately 2.71828
).
Example
print math::expm1(0) # 0
print math::expm1(1) # 1.718281828459045
ln
fn ln num -> num
Description
Returns the natural logarithm of the given number. The natural logarithm is the inverse of the exponential function.
Example
print math::ln(1) # 0
print math::ln(2.718281828459045) # 1
log
fn log num -> num
Description
Returns the base-10 logarithm of the given number.
Example
print math::log(1) # 0
print math::log(10) # 1
log10
fn log10 num -> num
Description
Returns the base-10 logarithm of the given number.
Example
print math::log10(1) # 0
print math::log10(10) # 1
log2
fn log2 num -> num
Description
Returns the base-2 logarithm of the given number.
Example
print math::log2(1) # 0
print math::log2(2) # 1
sqrt
fn sqrt num -> num
Description
Returns the square root of the given number.
Example
print math::sqrt(4) # 2
print math::sqrt(2) # 1.4142135623730951
cbrt
fn cbrt num -> num
Description
Returns the cube root of the given number.
Example
print math::cbrt(8) # 2
print math::cbrt(2) # 1.2599210498948732
hypot
fn hypot num, num -> num
Description
Returns the square root of the sum of the squares of the given numbers.
Example
print math::hypot(3, 4) # 5
print math::hypot(2, 2) # 2.8284271247461903
pow
fn pow num, num -> num
Description
Returns the first number raised to the power of the second number.
Example
print math::pow(2, 3) # 8
print math::pow(2, 0.5) # 1.4142135623730951
ceil
fn ceil num -> num
Description
Returns the smallest integer greater than or equal to the given number.
Example
print math::ceil(1.5) # 2
print math::ceil(-1.5) # -1
floor
fn floor num -> num
Description
Returns the largest integer less than or equal to the given number.
Example
print math::floor(1.5) # 1
print math::floor(-1.5) # -2
round
fn round num -> num
Description
Returns the nearest integer to the given number.
Example
print math::round(1.5) # 2
print math::round(-1.5) # -2
abs
fn abs num -> num
Description
Returns the absolute value of the given number.
Example
print math::abs(1) # 1
print math::abs(-1) # 1
NaN
NaN: num
Description
A special value representing "not a number".
Example
print math::NaN # NaN
Standard Library - Time Functions
now
fn now -> {
year -> num,
month -> num,
day -> hour,
hour -> num,
minute -> num,
second -> num
}
Description
Returns the current date and time as an object with the following fields:
year
- The current year.month
- The current month.day
- The current day.hour
- The current hour.minute
- The current minute.second
- The current second.
Example
print now() # {year -> 2021, month: 9, day -> 1, hour -> 0, minute -> 0, second -> 0}
clock
fn clock -> num
Description
Returns the number of milliseconds since the Unix epoch.
Example
print clock() # 1686614986960
Standard Library - Type Functions
num
fn num any -> num
Description
Converts the given value to a number.
Otherwise, returns math::NaN
.
Example
print num("123") # 123
print num("abc") # NaN
str
fn str any -> str
Description
Converts the given value to a string.
Example
print str(123.4) # 123.4
type_of
fn type_of any -> str
Description
Returns the type of the given value as a string.
Example
print type_of(123) # "num"
print type_of("abc") # "str"
is?
fn is? any, str -> bool
Description
Returns true
if the given value is of the given type, false
otherwise.
Example
print is?(123, 'num) # true
print is?(123, 'str) # false
Standard Library - List Functions
len
fn len [any] -> num
Description
Returns the length of the given list.
Example
print len([1, 2, 3]) # 3
push
sub push [any] any
Description
Adds the given value to the end of the given list.
Example
list = [1, 2, 3]
push list, 4
print list # [1, 2, 3, 4]
pop
fn pop [any] -> any
Description
Removes and returns the last element of the given list.
Example
list = [1, 2, 3]
print pop(list) # 3
print list # [1, 2]
shift
fn shift [any] -> any
Description
Removes and returns the first element of the given list.
Example
list = [1, 2, 3]
print shift(list) # 1
print list # [2, 3]
unshift
sub unshift [any] any
Description
Adds the given value to the beginning of the given list.
Example
list = [1, 2, 3]
unshift list, 0
print list # [0, 1, 2, 3]
join
fn join [any], str -> str
Description
Returns a string containing the elements of the given list joined by the given separator.
Example
print join([1, 2, 3], ", ") # "1, 2, 3"
map
fn map [any], (fn any -> any) -> [any]
Description
Returns a new list containing the results of applying the given function to each element of the given list.
Example
print map([1, 2, 3], lambda x -> x * 2) # [2, 4, 6]
map!
sub map! [any], (fn any -> any)
Description
Applies the given function to each element of the given list.
Example
list = [1, 2, 3]
map! list, lambda x -> x * 2
print list # [2, 4, 6]
filter
fn filter [any], (fn any -> bool) -> [any]
Description
Returns a new list containing the elements of the given list for which the given function returns true.
Example
print filter([1, 2, 3], lambda x -> x % 2 == 0) # [2]
filter!
sub filter! [any], (fn any -> bool)
Description
Removes all elements from the given list for which the given function returns false.
Example
list = [1, 2, 3]
filter! list, lambda x -> x % 2 == 0
print list # [2]
reduce
fn reduce [any], any, (fn any any -> any) -> any
Description
Applies the given function to each element of the given list, accumulating the result.
Example
print reduce([1, 2, 3], 0, lambda x y -> x + y) # 6
upTo
fn upTo num -> [num]
Description
Returns a list of numbers from 0 up to the given number.
Example
print upTo(3) # [0, 1, 2, 3]
downTo
fn downTo num -> [num]
Description
Returns a list of numbers from the given number down to 0.
Example
print downTo(3) # [3, 2, 1, 0]
Standard Library - Map Functions
TODO
This page is a stub. It will be filled in later.
Standard Library - Misc Functions
execute
fn execute str... -> {
wait_for -> (fn -> num)
}
Description
Executes the given command in a new process.
Returns an object with a wait_for
method that waits for the process to finish and returns its exit code.
Example
print execute("echo", "Hello, World!")::wait_for() # 0
times
sub times num, fn
Description
Calls the given function the given number of times.
Example
3.times do i
print i
end
set_dir
sub set_dir str
Description
Sets the current working directory to the given path.
Example
set_dir "/home/user"
get_dir
fn get_dir -> str
Description
Returns the current working directory.
Example
print get_dir() # /home/user
in_dir
sub in_dir str, callable
Description
Sets the current working directory to the given path, calls the given callable, and then restores the previous working directory.
Example
set_dir "/foo/bar"
print get_dir() # /foo/bar
in_dir "/home/user" do
print get_dir() # /home/user
end
print get_dir() # /foo/bar