feat: complete language tour
This commit is contained in:
parent
0823b78bf8
commit
34c8a58391
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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
|
||||
}
|
||||
```
|
|
@ -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
|
||||
```
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
```
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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 🌒"
|
||||
```
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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)
|
||||
```
|
|
@ -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)
|
||||
```
|
||||
|
|
|
@ -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
|
||||
}
|
||||
```
|
||||
```
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")),
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue