feat: complete language tour

This commit is contained in:
rvcas 2022-11-30 15:24:37 -05:00 committed by Lucas
parent 0823b78bf8
commit 34c8a58391
27 changed files with 1130 additions and 252 deletions

View File

@ -7,20 +7,22 @@
- [String](./language-tour/string.md)
- [Bool](./language-tour/bool.md)
- [Int](./language-tour/int.md)
- [ByteArray](./language-tour/bytearray.md)
- [Data](./language-tour/data.md)
- [Option](./language-tour/option.md)
- [Variables](./language-tour/variables.md)
- [Blocks](./language-tour/blocks.md)
- [List](./language-tour/list.md)
- [Tuple](./language-tour/tuple.md)
- [Custom types](./language-tour/custom-types.md)
- [Type aliases](./language-tour/type-aliases.md)
- [Matching](./language-tour/matching.md)
- [Functions](./language-tour/functions.md)
- [Custom types](./language-tour/custom-types.md)
- [Option](./language-tour/option.md)
- [Assert](./language-tour/assert.md)
- [Check](./language-tour/check.md)
- [Todo](./language-tour/todo.md)
- [Modules](./language-tour/modules.md)
- [Constants](./language-tour/constants.md)
- [Type aliases](./language-tour/type-aliases.md)
- [Untyped Plutus Core](./uplc.md)
- [Syntax](./uplc/syntax.md)
- [Command-line utilities](./uplc/cli.md)

View File

@ -5,7 +5,7 @@ smart contracts on the [Cardano](https://cardano.org) blockchain.
## Philosophy
Our main goal is to improve the smart contract development experience for the Cardano blockchain. Aiken takes inspiration from many modern languages such as gleam, rust, and elm which are known for friendly error messages and an overall excellent developer experience. We believe Cardano deserves a dedicated language with these kinds of features, developed in the open with the community.
Our main goal is to improve the smart contract development experience for the Cardano blockchain. Aiken takes inspiration from many modern languages such as aiken, rust, and elm which are known for friendly error messages and an overall excellent developer experience. We believe Cardano deserves a dedicated language with these kinds of features, developed in the open with the community.
## Goals

View File

@ -1,16 +1,25 @@
# Assert
```gleam
assert rawdata = SomeType
```aiken
type Datum {
n: Int
}
fn do_something(datum: Data) -> Bool {
assert d: Datum = datum
d.n == 0
}
```
Causes the script to fail if the rawdata doesn't match the structure of datumtype
Otherwise, returns a value of SomeType
Causes the script to fail if the raw `Data` doesn't match the structure of `Datum`.
Primarily for validating input datums / redeemers.
You can unpack (1-match) data in the assertion
You can also assert patterns.
```gleam
assert Some(x) = Option(Int)
```
```aiken
let optional_int = Some(1)
assert Some(x) = optional_int
```

View File

@ -1,28 +1,18 @@
# Blocks
Let bindings with blocks
```gleam
let x = 3
Every block in Aiken is an expression. All expressions in the
block are executed, and the result of the last expression is returned.
let z = {
let y = 2
x + y
}
```aiken
let value: Bool = {
"Hello"
42 + 12
False
} // => False
```
A block can be thought of as calling an anonymous function with no arguments. They can be used anywhere a value is.
Since everything is secretly a function, the last statement in any block is implicitly its return.
Expression blocks can be used instead of parenthesis to change the precedence of operations.
Blocks within a where-if clause
```gleam
let name: Option(String) = someFunction()
let suffix = ""
when name is {
Some(s)->{
let combined = s + suffix
Some(combined)
}
None->None
}
```
```
let celsius = { fahrenheit - 32 } * 5 / 9
```

View File

@ -1,39 +1,36 @@
# Bool
Bools (short for booleans) are True or False. They correspond to the plutus bool primitive type.
There are logical disjunctions (True || False) or conjunctions (True && True).
```gleam
False || False -- -> False
True || False -- -> True
False || True -- -> True
True || True -- -> True
A Bool can be either True or False.
False && False -- -> False
True && False -- -> False
False && True -- -> False
True && True -- -> True
Aiken defines a handful of operators that work with Bools.
```aiken
False && False // => False
False && True // => False
True && False // => False
True && True // => True
False || False // => False
False || True // => True
True || False // => True
True || True // => True
```
These are implemented using the plutus ifThenElse primitive.
```gleam
a || b -- if a {True} else {b} -- ifThenElse(a, True, b)
a && b -- if a {b} else {False} -- ifThenElse(a, b, False)
These are implemented using the plutus `ifThenElse` builtin.
```aiken
a || b // => if a {True} else {b} -- ifThenElse(a, True, b)
a && b // => if a {b} else {False} -- ifThenElse(a, b, False)
```
An if statement decides on a boolean value.
```gleam
```aiken
fn negate(b: Bool) -> Bool {
if b {
False
}else{
} else {
True
}
}
```
The && operator in a function
```gleam
fn and(b: Bool, c: Bool, d: Bool) -> Bool{
b && c && d
}
```

View File

@ -0,0 +1,20 @@
# ByteArray
ByteArrays are exactly what they seem, an array of bytes. In plutus
they are called ByteStrings but we decided ByteArray is a more accurate name.
Aiken supports ByteArray literals.
```aiken
#[10, 255]
#[1, 256] // => results in a parse error because 256 is bigger than 1 byte
```
It's important to mention that variables and patterns are not supported in
ByteArray literals. This is due to how ByteString literals work under the
hood in [UPLC](../uplc.md).
```aiken
let x = 10
#[x, 243] // => not allowed
```

View File

@ -1,8 +1,19 @@
# Check
Check is slower than assert but has stronger guarantees.
You can unpack (1-match) data in a check.
`check` uses more budget than assert but has stronger guarantees.
```gleam
check Some(x) = Option(Int)
```
# Assert
```aiken
type Datum {
n: Int
}
fn do_something(datum: Data) -> Bool {
check d: Datum = datum
d.n == 0
}
```
Causes the script to fail if the raw `Data` doesn't match the structure of `Datum`.

View File

@ -1,9 +1,9 @@
# Comments
```gleam
```aiken
// Line comments begin with a double-slash
/// Doc comments begin with a triple-slash
//// Module comments begin with a quadruple-slash
```
```

View File

@ -1,8 +1,45 @@
# Constants
Constants (not implemented yet) will be set in the global scope like so:
```gleam
const x = 1
Aikens's module constants provide a way to use a
certain fixed value in multiple places in a Aiken project.
```aiken
pub const start_year = 2101
pub const end_year = 2111
pub fn is_before(year: Int) -> Bool {
year < start_year
}
pub fn is_during(year: Int) -> Bool {
start_year <= year && year <= end_year
}
```
Similar to a let binding, but with a const keyword instead.
Like all values in Aiken constants are immutable. They cannot be used as global mutable state.
When a constant is referenced the value is inlined by the compiler, so they can be used in case expression guards.
```aiken
pub const start_year = 2101
pub const end_year = 2111
pub describe(year: Int) -> String {
when year is {
year if year < start_year -> "Before"
year if year > end_year -> "After"
- -> "During"
}
}
```
## Type annotations
Constants can also be given type annotations.
```aiken
pub const name: String = "Aiken"
pub const size: Int = 100
```
These annotations serve as documentation or can be used to provide a more specific type than the compiler would otherwise infer.

View File

@ -1,46 +1,233 @@
# Custom types
Aiken's custom types are named collections of keys and/or values. They are
similar to objects in object oriented languages, though they don't have
methods.
### Enum Types
Custom types are defined with the `type` keyword.
Instantiation
```gleam
type Color {
Red
Blue
Green
}
```
Enum types can be thought of as the unit of product types.
Usage
```gleam
fn myFavouriteColor() -> Color {
Red
```aiken
pub type Datum {
Datum { signer: ByteArray, count: Int }
}
```
### Product Types
Here we have defined a custom type called `Datum`. Its constructor is called
`Datum` and it has two fields: A `signer` field which is a `ByteArray`, and a
`count` field which is an `Int`.
```gleam
type MyValue {
Name(String)
Age(Int)
The `pub` keyword makes this type usable from other [modules](./modules.md).
Once defined the custom type can be used in functions:
```aiken
fn datums() {
// Fields can be given in any order
let datum1 = Datum { signer: #[0xAA, 0xBB], count: 2001 }
let datum2 = Datum { count: 1805, name: #[0xAA, 0xCC] }
[cat1, cat2]
}
```
```gleam
fn eitherNameOrAge(b: Bool) -> MyValue {
if b {
Name("James")
} else {
Age(20)
}
## Multiple constructors
Custom types in Aiken can be defined with multiple constructors, making them a
way of modeling data that can be one of a few different variants.
We've already seen a custom type with multiple constructors in the Language
Tour - [`Bool`](./bool.md).
Aiken's built-in `Bool` type is defined like this:
```aiken
// A Bool is a value that is either `True` or `False`
pub type Bool {
True
False
}
```
This example is a bit nonsensical...
It's a simple custom type with constructors that takes no arguments at all!
Use it to answer yes/no questions and to indicate whether something is `True`
or `False`.
The records created by different constructors for a custom type can contain
different values. For example a `User` custom type could have a `LoggedIn`
constructor that creates records with a name, and a `Guest` constructor which
creates records without any contained values.
```aiken
type User {
LoggedIn { count: Int } // A logged in user
Guest // A guest user with no details
}
```
```aiken
let user1 = LoggedIn { count: 4 }
let user2 = LoggedIn { count: 2 }
let visitor = Guest
```
## Destructuring
When given a custom type record we can pattern match on it to determine which
record constructor matches, and to assign names to any contained values.
```aiken
fn get_name(user) {
when user is {
LoggedIn { count } -> count
Guest -> "Guest user"
}
}
```
Custom types can also be destructured with a `let` binding.
```aiken
type Score {
Points(Int)
}
```
```aiken
let score = Points(50)
let Points(p) = score
p // => 50
```
During destructuring you may also use discards (`_`) or spreads (`..`).
```aiken
pub type Dog {
Dog { name: ByteArray, cuteness: Int, age: Int }
}
let dog = Dog { name: #[67, 97, 115, 104, 101, 119], cuteness: 9001, age: 3 }
```
You will need to specify all args for a pattern match, or alternatively use the
spread operator.
```aiken
// All fields present
let Dog { name: name, cuteness: _, age: _ } = dog
builtin.decode_utf8(name) // "Cashew"
// Other fields ignored by spreading. Field punning is supported.
let Dog { age, .. } = dog
age // 3
```
## Named accessors
If a custom type has only one variant and named fields they can be accessed
using `.field_name`.
For example using the `Cat` type defined earlier.
```aiken
let dog = Dog { name: #[82, 105, 110], cuteness: 2001 }
builtin.decode_utf8(dog.name) // This returns "Rin"
dog.cuteness // This returns 2001
```
This is actually so common that Aiken has some sugar for defining these
kinds of types.
```aiken
pub type Dog {
name: ByteArray,
cuteness: Int,
age: Int,
}
```
## Generics
Custom types can be be parameterised with other types, making their contents
variable.
For example, this `Box` type is a simple record that holds a single value.
```aiken
pub type Box(inner_type) {
Box(inner: inner_type)
}
```
The type of the field `inner` is `inner_type`, which is a parameter of the `Box`
type. If it holds an int the box's type is `Box(Int)`, if it holds a string the
box's type is `Box(String)`.
```aiken
pub fn main() {
let a = Box(420) // type is Box(Int)
let b = Box("That's my ninja way!") // type is Box(String)
}
```
## Opaque types
At times it may be useful to create a type and make the constructors and
fields private so that users of this type can only use the type through
publically exported functions.
For example we can create a `Counter` type which holds an int which can be
incremented. We don't want the user to alter the int value other than by
incrementing it, so we can make the type opaque to prevent them from being
able to do this.
```aiken
// The type is defined with the opaque keyword
pub opaque type Counter {
Counter(value: Int)
}
pub fn new() {
Counter(0)
}
pub fn increment(counter: Counter) {
Counter(counter.value + 1)
}
```
Because the `Counter` type has been marked as `opaque` it is not possible for
code in other modules to construct or pattern match on counter values or
access the `value` field. Instead other modules have to manipulate the opaque
type using the exported functions from the module, in this case `new` and
`increment`.
## Record updates
Aiken provides a dedicated syntax for updating some of the fields of a custom
type record.
```aiken
pub type Person {
name: ByteArray,
shoe_size: Int,
age: Int,
is_happy: Bool,
}
pub fn have_birthday(person) {
// It's this person's birthday, so increment their age and
// make them happy
Person { ..person, age: person.age + 1, is_happy: True }
}
```
Update syntax created a new record with the values of the initial record
with the new values added.
## Plutus
At runtime custom types become instances of `PlutusData`.
In Aiken's type system `Data` matches with any user-defined type
except for most of the types included in the prelude module.

View File

@ -1 +1,29 @@
# Data
`Data` is the equivalent to `BuiltinData` in plutus. In Aiken,
it's a way to generically represent [custom types](./custom-types.md). In a way, it's
almost like `any` in TypeScript but restricted to user-defined [custom types](./custom-types.md).
As a type it won't match Int, Bool, ByteArray, and String.
It's useful for interacting with builtins that operate on `PlutusData'.
```aiken
type Datum {
count: Int,
}
let datum = Datum { count: 1 }
// fn(Data) -> ByteArray
builtin.serialize_data(datum) // some bytearray
```
It's also possible to cast `Data` to a [custom type](./custom-types.md).
```aiken
pub fn do_cast(datum: Data) -> Datum {
let d: Datum = datum
d
}
```

View File

@ -1,43 +1,230 @@
# Functions
Functions can be instantiated like so:
```gleam
fn f(x) {
## Named functions
Named functions in Aiken are defined using the `pub fn` keywords.
```aiken
pub fn add(x: Int, y: Int) -> Int {
x + y
}
```
Functions in aiken are pure without side effect, so all functions also must return some value.
A function with no return value (like above) will error.
Providing arg types:
```gleam
fn f(x: Int) {
pub fn multiply(x: Int, y: Int) -> Int {
x * y
}
```
and return types:
Functions in Aiken are first class values and so can be assigned to variables,
passed to functions, or anything else you might do with any other data type.
```gleam
fn f(x: Int) -> Int {
```aiken
/// This function takes a function as an argument
pub fn twice(f: fn(t) -> t, x: t) -> t {
f(f(x))
}
pub fn add_one(x: Int) -> Int {
x + 1
}
pub fn add_two(x: Int) -> Int {
twice(add_one, x)
}
```
and the last value is implicitly returned:
## Pipe Operator
```gleam
fn f(x: Int) -> Int {
x + 1
Aiken provides syntax for passing the result of one function to the arguments of another function, the pipe operator (`|>`). This is similar in functionality to the same operator in Elixir or F#.
The pipe operator allows you to chain function calls without using a lot of parenthesis and nesting.
For a simple example, consider the following implementation of an imaginary `string.reverse` in Aiken:
```aiken
string_builder.to_string(string_builder.reverse(string_builder.from_string(string)))
```
This can be expressed more naturally using the pipe operator, eliminating the need to track parenthesis closure.
```aiken
string
|> string_builder.from_string
|> string_builder.reverse
|> string_builder.to_string
```
Each line of this expression applies the function to the result of the previous line. This works easily because each of these functions take only one argument. Syntax is available to substitute specific arguments of functions that take more than one argument; for more, look below in the section "Function capturing".
## Type annotations
Function arguments are normally annotated with their type, and the
compiler will check these annotations and ensure they are correct.
```aiken
fn identity(x: some_type) -> some_type {
x
}
fn inferred_identity(x) {
x
}
```
Functions can be made public in modules so they can be accessed by others
The Aiken compiler can infer all the types of Aiken code without annotations
and both annotated and unannotated code is equally safe. It's considered a
best practice to always write type annotations for your functions as they
provide useful documentation, and they encourage thinking about types as code
is being written.
```gleam
pub fn myFunction(a: List(a)) {
// do something useful...
## Generic functions
At times you may wish to write functions that are generic over multiple types.
For example, consider a function that consumes any value and returns a list
containing two of the value that was passed in. This can be expressed in Aiken
like this:
```aiken
fn list_of_two(my_value: a) -> List(a) {
[my_value, my_value]
}
```
A
Here the type variable `a` is used to represent any possible type.
You can use any number of different type variables in the same function. This
function declares type variables `a` and `b`.
```aiken
fn multi_result(x: a, y: b, condition: Bool) -> Result(a, b) {
case condition {
True -> Ok(x)
False -> Error(y)
}
}
```
Type variables can be named anything, but the names must be lower case and may
contain underscores. Like other type annotations, they are completely optional,
but may aid in understanding the code.
## Labeled arguments
When functions take several arguments it can be difficult for the user to
remember what the arguments are, and what order they are expected in.
To help with this Aiken supports _labeled arguments_, where function
arguments are given an external label in addition to their internal name.
Take this function that replaces sections of a string:
```aiken
pub fn replace(string: String, pattern: String, replacement: String) {
// ...
}
```
It can be given labels like so.
```aiken
pub fn replace(
in string: String,
each pattern: String,
with replacement: String,
) {
// The variables `string`, `pattern`, and `replacement` are in scope here
}
```
These labels can then be used when calling the function.
```aiken
replace(in: "A,B,C", each: ",", with: " ")
// Labeled arguments can be given in any order
replace(each: ",", with: " ", in: "A,B,C")
// Arguments can still be given in a positional fashion
replace("A,B,C", ",", " ")
```
The use of argument labels can allow a function to be called in an expressive,
sentence-like manner, while still providing a function body that is readable
and clear in intent.
## Anonymous functions
Anonymous functions can be defined with a similar syntax.
```aiken
pub fn run() {
let add = fn(x, y) { x + y }
add(1, 2)
}
```
## Function capturing
There is a shorthand syntax for creating anonymous functions that take one
argument and call another function. The `_` is used to indicate where the
argument should be passed.
```aiken
pub fn add(x, y) {
x + y
}
pub fn run() {
let add_one = add(1, _)
add_one(2)
}
```
The function capture syntax is often used with the pipe operator to create
a series of transformations on some data.
```aiken
pub fn add(x: Int , y: Int ) -> Int {
x + y
}
pub fn run() {
// This is the same as add(add(add(1, 3), 6), 9)
1
|> add(_, 3)
|> add(_, 6)
|> add(_, 9)
}
```
In fact, this usage is so common that there is a special shorthand for it.
```aiken
pub fn run() {
// This is the same as the example above
1
|> add(3)
|> add(6)
|> add(9)
}
```
The pipe operator will first check to see if the left hand value could be used
as the first argument to the call, e.g. `a |> b(1, 2)` would become `b(a, 1, 2)`.
If not it falls back to calling the result of the right hand side as a function
, e.g. `b(1, 2)(a)`.
## Documentation
You may add user facing documentation in front of function definitions with a
documentation comment `///` per line. Markdown is supported and this text
will be included with the module's entry in generated HTML documentation.
```aiken
/// Does nothing, returns `Nil`.
///
fn returns_nil(a) -> Nil {
Nil
}
```

View File

@ -1,14 +1,39 @@
# Int
Ints are plutus integers which are arbitrary size.
So, there is no underflow or overflow. Basic arithmetic can be done for O(1) between ints (+,-,*).
Aiken's only number type is an arbitrary size integer.
```gleam
This means there is no underflow or overflow.
Binary, octal, and hexadecimal integers begin with 0b, 0o, and 0x respectively.
```aiken
1
2
-3
4001
0b00001111
0o17
0xF
```
Aiken has several operators that work with Int.
```aiken
// A convenient helper function to get the number 7.
pub fn get7(){
let x = 3
let y = 2
let z = 1
x*y + z
}
```
1 + 1 // => 2
5 - 1 // => 4
5 / 2 // => 2
3 * 3 // => 9
5 % 2 // => 1
2 > 1 // => True
2 < 1 // => False
2 >= 1 // => True
2 <= 1 // => False
```
Underscores can be added to Ints for clarity.
```
1_000_000
```

View File

@ -1,25 +1,41 @@
# List
Aiken lists are plutus linked lists.
Accessing by index is O(n).
Appending or accessing head is O(1).
Grabbing tail is O(1).
Lists are ordered collections of values. They're one of the most common data structures in Aiken.
There is no builtin syntax for accessing by index as this is implemented by standard libs.
All the elements of a List must be of the same type. Attempting to make a list using multiple
different types will result in a type error.
```aiken
[1, 2, 3, 4] // List(Int)
["text", 3, 4] // Type error!
```
Inserting at the front of a list is very fast, and is the preferred way to add new values.
```aiken
[1, ..[2, 3]] // => [1, 2, 3]
```
Note that all data structures in Aiken are immutable so prepending to a list does not change the original list. Instead it efficiently creates a new list with the new additional element.
```
let x = [2, 3]
let y = [1, ..x]
x // => [2, 3]
y // => [1, 2, 3]
```
Accessing head, tail, or preceding elements can be done by pattern matching.
```gleam
```aiken
// this function checks if a list has a sequence of 1 then 2 contained within it.
fn listStuff(a: List(Int)){
fn list_stuff(a: List(Int)){
when a is {
[1,2, ..tail] -> True
[]->False
_ -> listStuff([2, ..tail])
[1, 2, ..tail] -> True
[] -> False
_ -> list_stuff([2, ..tail])
}
}
```
Helper functions for safely accessing head, tail, are provided in standard lib but are implemented using comprehensions.
It is usually best to use your own comprehensions for efficiency (until the optimiser is better).
It is best to avoid accesses by indexes if possible for efficiency.

View File

@ -1,11 +1,136 @@
# Matching
Where-if Patterns (also known as case in lamer languages)
The `when *expr* is` expression is the most common kind of flow control in Aiken code. It
allows us to say "if the data has this shape then do that", which we call
_pattern matching_.
```gleam
when color is {
Green -> "Success."
Blue -> "Warning."
Red -> "Error!"
Here we match on an `Int` and return a specific string for the values 0, 1,
and 2. The final pattern `n` matches any other value that did not match any of
the previous patterns.
```aiken
when some_number is {
0 -> "Zero"
1 -> "One"
2 -> "Two"
n -> "Some other number" // This matches anything
}
```
```
Pattern matching on a `Bool` value is discouraged and `if else`
expressions should be use instead.
```aiken
if some_bool {
"It's true!"
else {
"It's not true."
}
```
Aiken's `when *expr* is` is an expression, meaning it returns a value and can be used
anywhere we would use a value. For example, we can name the value of a case
expression with a `let` binding.
```aiken
type Answer {
Yes
No
}
let answer = Yes
let description =
when answer is {
Yes -> "It's yes!"
No -> "It's not yes."
}
description // => "It's true!"
```
## Destructuring
A `when *expr* is` expression can be used to destructure values that
contain other values, such as tuples and lists.
```aiken
when xs is {
[] -> "This list is empty"
[a] -> "This list has 1 element"
[a, b] -> "This list has 2 elements"
_other -> "This list has more than 2 elements"
}
```
It's not just the top level data structure that can be pattern matched,
contained values can also be matched. This gives `case` the ability to
concisely express flow control that might be verbose without pattern matching.
```aiken
when xs is {
[[]] -> "The only element is an empty list"
[[], ..] -> "The 1st element is an empty list"
[[4], ..] -> "The 1st element is a list of the number 4"
other -> "Something else"
}
```
Pattern matching also works in `let` bindings, though patterns that do not
match all instances of that type may result in a runtime error.
```aiken
let [a] = [1] // a is 1
let [b] = [1, 2] // Runtime error! The pattern has 1 element but the value has 2
```
## Assigning names to sub-patterns
Sometimes when pattern matching we want to assign a name to a value while
specifying its shape at the same time. We can do this using the `as` keyword.
```aiken
case xs {
[[_, ..] as inner_list] -> inner_list
other -> []
}
```
## Checking equality and ordering in patterns
The `if` keyword can be used to add a guard expression to a case clause. Both
the patterns have to match and the guard has to evaluate to `True` for the
clause to match. The guard expression can check for equality or ordering for
`Int`.
```aiken
case xs {
[a, b, c] if a == b && b != c -> "ok"
_other -> "ko"
}
```
## Alternative clause patterns
Alternative patterns can be given for a case clause using the `|` operator. If
any of the patterns match then the clause matches.
Here the first clause will match if the variable `number` holds 2, 4, 6 or 8.
```aiken
case number {
2 | 4 | 6 | 8 -> "This is an even number"
1 | 3 | 5 | 7 -> "This is an odd number"
_ -> "I'm not sure"
}
```
If the patterns declare variables then the same variables must be declared in
all patterns, and the variables must have the same type in all the patterns.
```aiken
case list {
[1, x] | x -> x // Error! Int != List(Int)
_ -> 0
}
```

View File

@ -0,0 +1,153 @@
# Modules
Aiken programs are made up of bundles of functions and types called modules.
Each module has its own namespace and can export types and values to be used
by other modules in the program.
```aiken
// inside module lib/straw_hats/sunny.ak
fn count_down() {
"3... 2... 1..."
}
fn blast_off() {
"BOOM!"
}
pub fn set_sail() {
[
count_down(),
blast_off(),
]
}
```
Here we can see a module named `straw_hats/sunny`, the name determined by the
filename `lib/straw_hats/sunny.ak`. Typically all the modules for one
project would live within a directory with the name of the project, such as
`straw_hats` in this example.
For the functions `count_down` and `blast_off` we have omitted the `pub`
keyword, so these functions are _private_ module functions. They can only be
called by other functions within the same module.
## Import
To use functions or types from another module we need to import them using the
`use` keyword.
```aiken
// inside module src/straw_hats/laugh_tale.ak
use straw_hats/sunny
pub fn find_the_one_piece() {
sunny.set_sail()
}
```
The definition `use straw_hats/sunny` creates a new variable with the name
`sunny` and the value of the `sunny` module.
In the `find_the_one_piece` function we call the imported module's public `set_sail`
function using the `.` operator. If we had attempted to call `count_down` it
would result in a compile time error as this function is private to the
`sunny` module.
## Named import
It is also possible to give a module a custom name
when importing it using the `as` keyword.
```aiken
use unix/dog
use animal/dog as kitty
```
This may be useful to differentiate between multiple modules that would have
the same default name when imported.
## Unqualified import
Values and types can also be imported in an unqualified fashion.
```aken
use animal/dog.{Dog, stroke}
pub fn main() {
let puppy = Dog { name: "Zeus" }
stroke(puppy)
}
```
This may be useful for values that are used frequently in a module, but
generally qualified imports are preferred as it makes it clearer where the
value is defined.
## The prelude module
There are two modules that are built into the language, the first is the `aiken` prelude
module. By default its types and values are automatically imported into
every module you write, but you can still chose to import it the regular way.
This may be useful if you have created a type or value with the same name as
an item from the prelude.
```aiken
use aiken
/// This definition locally overrides the `Result` type
/// and the `Some` constructor.
pub type Option {
Some
}
/// The original `Option` and `Some` can still be used
pub fn go() -> aiken.Option(Int) {
aiken.Some(1)
}
```
The prelude module contains these types:
- `ByteArray`
- `Bool`
- `Int`
- `List(element)`
- `Nil`
- `Option(value)`
- `String`
- `Data`
And these values:
- `None`
- `Some`
- `False`
- `True`
- `Nil`
## The builtin module
The second module that comes with the language is for exposing
useful builtin functions from plutus core. Most underlying
platform functions are available here using a snake case name.
Much of Aiken's syntax ends up compiling to combinations of certain bultins
but many aren't "exposed" through the syntax and need to be used directly.
The standard library wraps these in a more Aiken friendly interface so
you'll probably never need to use these directly unless you're making your
own standard library.
```aiken
use aiken/builtin
fn eq(a, b) {
builtin.equals_integer(a, b)
}
```
## Documentation
You may add user facing documentation at the head of modules with a module
documentation comment `////` per line. Markdown is supported and this text
will be included with the module's entry in generated HTML documentation.

View File

@ -12,12 +12,10 @@ pub type Option(a) {
Then, functions which fail may safely return an optional value.
```
pub fn getHead(a: List(a))->a {
pub fn get_head(a: List(a)) -> Option(a) {
when a is {
[a, .._]->Some(a)
[]->None
[a, .._] -> Some(a)
[] -> None
}
}
```
An unsafe variant of this function might instead assert that there is a head, and return it.

View File

@ -1,15 +1,21 @@
# String
There are Strings and ByteArrays (plutus bytestrings)
In Aiken Strings can be written as text surrounded by double quotes.
The default representation using double quotes is a string.
```aiken
"Hello, Aiken!"
```
let mystring = "Hello World!"
They can span multiple lines.
```aiken
"Hello
Aiken!"
```
Strings may be appended and compared.
For char operations, ByteArrays must be used.
ByteArrays have efficient indexing, slicing, and can be compared or concatenated.
ByteArrays can also be useful for non-text data (hence why we call them arrays not strings.)
Under the hood Strings are [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoded binaries
and can contain any valid unicode.
Due to their fixed byte width, you may find them not suitable for complex structures.
```aiken
"🌘 アルバイト Aiken 🌒"
```

View File

@ -1,12 +1,47 @@
# Todo
A 'todo' type errors on evaluation but casts to any type.
It can be useful to let your project typecheck while you are still working on parts of it.
Aiken's `todo` keyword is used to indicate that some code is not yet finished.
```
fn notImplementedYet(){
todo
It can be useful when designing a module, type checking functions and types
but leaving the implementation of the functions until later.
```aiken
fn not_sure_yet() -> Int {
// The type annotations says this returns an Int, but we don't need
// to implement it yet.
todo
}
pub fn idk() {
favourite_number() * 2
}
```
It is also good practice to use instead of a todo comment.
When this code is built Aiken will type check and compile the code to ensure
it is valid, and the `todo` will be replaced with code that crashes the
program if that function is run.
A message can be given as a form of documentation. The message will be traced when
the `todo` code is run.
```aiken
fn not_sure_yet() -> Int {
todo("Believe in the you that believes in yourself!")
}
```
When the compiler finds a `todo` it will print a warning, which can be useful
to avoid accidentally forgetting to remove a `todo`.
The warning also includes the expected type of the expression that needs to
replace the `todo`. This can be a useful way of asking the compiler what type
is needed if you are ever unsure.
```aiken
fn main() {
my_complicated_function(
// What type does this function take again...?
todo
)
}
```

View File

@ -1,9 +1,8 @@
# Tuple
Tuples are anonymous product types. They are useful for passing combinations of data between functions.
Aiken has tuples which can be useful for grouping values.
```aiken
#(10, "hello") // Type is #(Int, String)
#(1, 4, [0]) // Type is #(Int, Int, List(Int))
```
let x = (1, 2)
let y = (3, 4)
pairAdder(x, y) -- --> (4, 6)
```

View File

@ -1,24 +1,24 @@
# Type aliases
A type alias lets you create a name which is identical to another type, without any additional information.
We like type names (including type alias names) to be PascalCase.
A type alias lets you create a name which is identical to
another type, without any additional information.
```gleam
```aiken
type MyNumber = Integer
```
I imagine them like variables for types. You could use this to simplify your type signatures for tuples.
They are most useful for simplifying type signatures.
```gleam
type Person = (String, Integer)
```aiken
type Person = #(String, Integer)
fn createPerson(name: String, age: Integer) -> Person {
(name, age)
fn create_person(name: String, age: Integer) -> Person {
#(name, age)
}
```
If you want the type-alias to be accessible as a module, you should pub it.
If you want the type alias to be accessible from a module, you can define it as public.
```
pub type MyVector3 = (Integer, Integer, Integer)
```
pub type MyVector3 = #(Integer, Integer, Integer)
```

View File

@ -1,17 +1,31 @@
# Variables
Function variables with types
Aiken has let bindings for variables. A value can be given a name using let.
Names can be reused by later let bindings, but the values contained are immutable.
```aiken
let x = 3
let v = x
let x = 7
x // => 2
y // => 1
```
fn add(a:Int, b:Int) -> Int {
Function variables with types
```
fn add(a: Int, b: Int) -> Int {
a + b
}
```
Let bindings
```
fn something(){
fn something() {
let a = 3
let b = 5
a + b
}
```
```

View File

@ -398,6 +398,34 @@ pub fn expr_parser(
elems,
});
let bytearray = just(Token::Hash)
.ignore_then(
select! {Token::Int {value} => value}
.validate(|value, span, emit| {
let byte: u8 = match value.parse() {
Ok(b) => b,
Err(_) => {
emit(ParseError::expected_input_found(
span,
None,
Some(error::Pattern::Byte),
));
0
}
};
byte
})
.separated_by(just(Token::Comma))
.allow_trailing()
.delimited_by(just(Token::LeftSquare), just(Token::RightSquare)),
)
.map_with_span(|bytes, span| expr::UntypedExpr::ByteArray {
location: span,
bytes,
});
let list_parser = just(Token::LeftSquare)
.ignore_then(r.clone().separated_by(just(Token::Comma)))
.then(choice((
@ -505,7 +533,7 @@ pub fn expr_parser(
let assert_parser = just(Token::Assert)
.ignore_then(pattern_parser())
.then(just(Token::Colon).ignore_then(type_parser()).or_not())
.then_ignore(just(Token::Is))
.then_ignore(just(Token::Equal))
.then(r.clone())
.map_with_span(
|((pattern, annotation), value), span| expr::UntypedExpr::Assignment {
@ -520,7 +548,7 @@ pub fn expr_parser(
let check_parser = just(Token::Check)
.ignore_then(pattern_parser())
.then(just(Token::Colon).ignore_then(type_parser()).or_not())
.then_ignore(just(Token::Is))
.then_ignore(just(Token::Equal))
.then(r.clone())
.map_with_span(
|((pattern, annotation), value), span| expr::UntypedExpr::Assignment {
@ -572,6 +600,7 @@ pub fn expr_parser(
var_parser,
todo_parser,
tuple,
bytearray,
list_parser,
anon_fn_parser,
block_parser,

View File

@ -5,7 +5,7 @@ use miette::Diagnostic;
use crate::{ast::Span, parser::token::Token};
#[derive(Debug, Diagnostic, thiserror::Error)]
#[error("{}", .kind)]
#[error("{kind}")]
pub struct ParseError {
pub kind: ErrorKind,
#[label]
@ -71,7 +71,7 @@ impl<T: Into<Pattern>> chumsky::Error<T> for ParseError {
pub enum ErrorKind {
#[error("unexpected end")]
UnexpectedEnd,
#[error("unexpected {0}")]
#[error("{0}")]
#[diagnostic(help("{}", .0.help().unwrap_or_else(|| Box::new(""))))]
Unexpected(Pattern),
#[error("unclosed {start}")]
@ -87,22 +87,29 @@ pub enum ErrorKind {
#[derive(Debug, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)]
pub enum Pattern {
#[error("{0:?}")]
#[error("Unexpected {0:?}")]
#[diagnostic(help("Try removing it"))]
Char(char),
#[error("{0}")]
#[diagnostic(help("try removing it"))]
#[error("Unexpected {0}")]
#[diagnostic(help("Try removing it"))]
Token(Token),
#[error("literal")]
#[error("Unexpected literal")]
#[diagnostic(help("Try removing it"))]
Literal,
#[error("type name")]
#[error("Unexpected type name")]
#[diagnostic(help("Try removing it"))]
TypeIdent,
#[error("indentifier")]
#[error("Unexpected indentifier")]
#[diagnostic(help("Try removing it"))]
TermIdent,
#[error("end of input")]
#[error("Unexpected end of input")]
End,
#[error("pattern")]
#[diagnostic(help("list spread in match can only have a discard or var"))]
#[error("Bad list spread pattern")]
#[diagnostic(help("List spread in matches can\nuse have a discard or var"))]
Match,
#[error("Bad byte literal")]
#[diagnostic(help("Bytes must be between 0-255"))]
Byte,
}
impl From<char> for Pattern {

View File

@ -6,20 +6,18 @@ use crate::ast::{BinOp, Span, TodoKind};
use super::Type;
// use aiken/pub
// pub fn do_thing() { pub.other() }
#[derive(Debug, thiserror::Error, Diagnostic)]
pub enum Error {
#[error("duplicate argument {label}")]
#[error("Duplicate argument\n\n{label}")]
#[diagnostic(help("Try renaming it"))]
DuplicateArgument {
#[label]
location: Span,
label: String,
},
#[error("duplicate const {name}")]
#[error("Duplicate const\n\n{name}")]
#[diagnostic(help("Try renaming it"))]
DuplicateConstName {
#[label]
location: Span,
@ -28,7 +26,8 @@ pub enum Error {
name: String,
},
#[error("duplicate import {name}")]
#[error("Duplicate import\n\n{name}")]
#[diagnostic(help("Try renaming it"))]
DuplicateImport {
#[label]
location: Span,
@ -37,14 +36,16 @@ pub enum Error {
name: String,
},
#[error("duplicate name {label}")]
#[error("Duplicate field\n\n{label}")]
#[diagnostic(help("Try renaming it"))]
DuplicateField {
#[label]
location: Span,
label: String,
},
#[error("duplicate name {name}")]
#[error("Duplicate name\n\n{name}")]
#[diagnostic(help("Try renaming it"))]
DuplicateName {
#[label]
location: Span,
@ -53,7 +54,8 @@ pub enum Error {
name: String,
},
#[error("duplicate type name {name}")]
#[error("Duplicate type name\n\n{name}")]
#[diagnostic(help("Try renaming it"))]
DuplicateTypeName {
#[label]
location: Span,
@ -62,7 +64,7 @@ pub enum Error {
name: String,
},
#[error("incorrect arity expected {expected} but given {given}")]
#[error("Incorrect arity\n\nExpected\n\n{expected}\n\nGiven\n\n{given}")]
IncorrectArity {
#[label]
location: Span,
@ -71,7 +73,7 @@ pub enum Error {
labels: Vec<String>,
},
#[error("incorrect number of clause patterns expected {expected} but given {given}")]
#[error("Incorrect number of clause patterns\n\nExpected\n\n{expected}\n\nGiven\n\n{given}")]
IncorrectNumClausePatterns {
#[label]
location: Span,
@ -79,7 +81,7 @@ pub enum Error {
given: usize,
},
#[error("{name} has incorrect type arity expected {expected} but given {given}")]
#[error("Incorrect type arity for `{name}`\n\nExpected\n\n{expected}\n\nGiven\n\n{given}")]
IncorrectTypeArity {
#[label]
location: Span,
@ -88,50 +90,50 @@ pub enum Error {
given: usize,
},
#[error("non-exhaustive pattern match")]
#[error("Non-exhaustive pattern match")]
NotExhaustivePatternMatch {
#[label]
location: Span,
unmatched: Vec<String>,
},
#[error("not a function")]
#[error("Not a function")]
NotFn {
#[label]
location: Span,
tipo: Arc<Type>,
},
#[error("{name} contains keyword {keyword}")]
#[error("Module\n\n{name}\n\ncontains keyword\n\n{keyword}")]
KeywordInModuleName { name: String, keyword: String },
#[error("clause guard {name} is not local")]
#[error("Clause guard {name} is not local")]
NonLocalClauseGuardVariable {
#[label]
location: Span,
name: String,
},
#[error("positional argument after labeled")]
#[error("Positional argument after labeled")]
PositionalArgumentAfterLabeled {
#[label]
location: Span,
},
#[error("private type leaked")]
#[error("Private type leaked")]
PrivateTypeLeak {
#[label]
location: Span,
leaked: Type,
},
#[error("record access unknown type")]
#[error("Record access unknown type")]
RecordAccessUnknownType {
#[label]
location: Span,
},
#[error("record update invalid constructor")]
#[error("Record update invalid constructor")]
RecordUpdateInvalidConstructor {
#[label]
location: Span,
@ -140,27 +142,27 @@ pub enum Error {
#[error("{name} is a reserved module name")]
ReservedModuleName { name: String },
#[error("unexpected labeled argument {label}")]
#[error("Unexpected labeled argument\n\n{label}")]
UnexpectedLabeledArg {
#[label]
location: Span,
label: String,
},
#[error("unexpected type hole")]
#[error("Unexpected type hole")]
UnexpectedTypeHole {
#[label]
location: Span,
},
#[error("unknown labels")]
#[error("Unknown labels")]
UnknownLabels {
unknown: Vec<(String, Span)>,
valid: Vec<String>,
supplied: Vec<String>,
},
#[error("unknown module {name}")]
#[error("Unknown module\n\n{name}")]
UnknownModule {
#[label]
location: Span,
@ -168,7 +170,7 @@ pub enum Error {
imported_modules: Vec<String>,
},
#[error("unknown module field {name} in module {module_name}")]
#[error("Unknown module field\n\n{name}\n\nin module\n\n{module_name}")]
UnknownModuleField {
location: Span,
name: String,
@ -177,7 +179,7 @@ pub enum Error {
type_constructors: Vec<String>,
},
#[error("unknown module value {name}")]
#[error("Unknown module value\n\n{name}")]
UnknownModuleValue {
#[label]
location: Span,
@ -186,7 +188,7 @@ pub enum Error {
value_constructors: Vec<String>,
},
#[error("unknown type {name} in module {module_name}")]
#[error("Unknown type\n\n{name}\n\nin module\n\n{module_name}")]
UnknownModuleType {
#[label]
location: Span,
@ -195,7 +197,7 @@ pub enum Error {
type_constructors: Vec<String>,
},
#[error("unknown record field {label}")]
#[error("Unknown record field\n\n{label}")]
UnknownRecordField {
#[label]
location: Span,
@ -205,7 +207,7 @@ pub enum Error {
situation: Option<UnknownRecordFieldSituation>,
},
#[error("unknown type {name}")]
#[error("Unknown type\n\n{name}")]
UnknownType {
#[label]
location: Span,
@ -213,7 +215,7 @@ pub enum Error {
types: Vec<String>,
},
#[error("unknown variable {name}")]
#[error("Unknown variable\n\n{name}")]
UnknownVariable {
#[label]
location: Span,
@ -221,14 +223,14 @@ pub enum Error {
variables: Vec<String>,
},
#[error("unnecessary spread operator")]
#[error("Unnecessary spread operator")]
UnnecessarySpreadOperator {
#[label]
location: Span,
arity: usize,
},
#[error("cannot update a type with multiple constructors")]
#[error("Cannot update a type with multiple constructors")]
UpdateMultiConstructorType {
#[label]
location: Span,

View File

@ -12,30 +12,30 @@ use miette::{
#[allow(dead_code)]
#[derive(thiserror::Error)]
pub enum Error {
#[error("duplicate module {module}")]
#[error("Duplicate module\n\n{module}")]
DuplicateModule {
module: String,
first: PathBuf,
second: PathBuf,
},
#[error("file operation failed")]
#[error("File operation failed")]
FileIo { error: io::Error, path: PathBuf },
#[error("source code incorrectly formatted")]
#[error("Source code incorrectly formatted")]
Format { problem_files: Vec<Unformatted> },
#[error(transparent)]
StandardIo(#[from] io::Error),
#[error("cyclical module imports")]
#[error("Syclical module imports")]
ImportCycle { modules: Vec<String> },
/// Useful for returning many [`Error::Parse`] at once
#[error("a list of errors")]
#[error("A list of errors")]
List(Vec<Self>),
#[error("parsing")]
#[error("Parsing")]
Parse {
path: PathBuf,
@ -47,7 +47,7 @@ pub enum Error {
error: Box<ParseError>,
},
#[error("type checking")]
#[error("Checking")]
Type {
path: PathBuf,
src: String,
@ -56,7 +56,7 @@ pub enum Error {
error: tipo::error::Error,
},
#[error("validator functions must return Bool")]
#[error("Validator functions must return Bool")]
ValidatorMustReturnBool {
path: PathBuf,
src: String,
@ -64,7 +64,7 @@ pub enum Error {
location: Span,
},
#[error("{name} requires at least {at_least} arguments")]
#[error("Validator\n\n{name}\n\nrequires at least {at_least} arguments")]
WrongValidatorArity {
name: String,
at_least: u8,
@ -198,7 +198,7 @@ impl Diagnostic for Error {
Error::ImportCycle { .. } => Some(Box::new("aiken::module::cyclical")),
Error::List(_) => None,
Error::Parse { .. } => Some(Box::new("aiken::parser")),
Error::Type { .. } => Some(Box::new("aiken::typecheck")),
Error::Type { .. } => Some(Box::new("aiken::check")),
Error::StandardIo(_) => None,
Error::Format { .. } => None,
Error::ValidatorMustReturnBool { .. } => Some(Box::new("aiken::scripts")),

View File

@ -1,6 +1,7 @@
use sample
use sample/mint
use sample/spend
use aiken/builtin
pub type Redeemer {
signer: ByteArray,
@ -36,8 +37,8 @@ pub fn incrementor(counter: Int, target: Int) -> Int {
}
}
pub fn who(a: #(Int, Int)) -> #(Int, Int) {
#(1, 2)
pub fn who(a: ByteArray) -> ByteArray {
builtin.append_bytearray(a, #[12, 256])
}
pub fn spend(