141 | <{ "Hello " }>
142 |
143 | }
144 | }
145 | ```
146 |
147 | ## Nesting
148 |
149 | Sub rules and media queries can be nested in each other infinitely:
150 |
151 | ```text
152 | style base {
153 | color: red;
154 |
155 | div {
156 | color: blue;
157 |
158 | span {
159 | color: yellow;
160 |
161 | @media (max-width: 500px) {
162 | font-weight: bold;
163 | }
164 | }
165 | }
166 | }
167 | ```
168 |
169 | ## If and Case
170 |
171 | A special version of `if` and `case` expressions can be used inside `style` blocks:
172 |
173 | ```text
174 | style base {
175 | if (loading) {
176 | pointer-events: none;
177 | opacity: 0.5;
178 | }
179 |
180 | case (status) {
181 | Status::Ok => color: blue;
182 | Status::Err =>
183 | border: 1px solid red;
184 | color: red;
185 | }
186 | }
187 | ```
188 |
189 | In this case the some different rules:
190 |
191 | * one expression rule does not apply
192 | * you can have one or more CSS definitions in each branch
193 | * the `else` and empty case branch can be omitted
194 |
195 | ## Inline Styles
196 |
197 | You can use inline styles just like in HTML in two different ways:
198 |
199 | ```text
200 | component Main {
201 | fun render : Html {
202 |
203 | /* As a String */
204 |
205 | "I am red!"
206 |
207 |
208 | /* As a Map(String, String) */
209 | try {
210 | style =
211 | Map.empty()
212 | |> Map.set("color", "red")
213 |
214 |
215 | "I am red!"
216 |
217 | }
218 |
219 | }
220 | }
221 | ```
222 |
223 | ## Behind the scenes
224 |
225 | The compiler separates properties that have interpolations from the ones that don't. The properties with interpolations are converted to use CSS variables \(`--test-base-color` \).
226 |
227 | During compiling the dynamic properties are converted to use a `style` object that contains the appropriate variables with their expressions.
228 |
229 | The defined styles are static and added to the `head` of the document at runtime.
230 |
231 |
--------------------------------------------------------------------------------
/reference/components/using-providers.md:
--------------------------------------------------------------------------------
1 | # Using Providers
2 |
3 | A Provider represents a source of asynchronous events. To subscribe to a Provider, you `use` it and pass it a block that will be called whenever there is an event to process.
4 |
5 | ```text
6 | component Main {
7 | state counter : Number = 0
8 |
9 | use Provider.Tick {
10 | ticks = () : Promise(Never, Void) {
11 | next { counter = counter + 1 }
12 | }
13 | }
14 |
15 | fun render : Html {
16 |
17 | <{ Number.toString(counter) }>
18 |
19 | }
20 | }
21 | ```
22 |
23 | In the above example we will update `counter` every second using the [Tick Provider](https://github.com/mint-lang/mint/blob/master/core/source/Provider/Tick.mint).
24 |
25 | Other available Providers: [AnimationFrame](https://github.com/mint-lang/mint-core/blob/master/source/Provider/AnimationFrame.mint), [Mouse](https://github.com/mint-lang/mint/blob/master/core/source/Provider/Mouse.mint) and [Scroll](https://github.com/mint-lang/mint-core/blob/master/source/Provider/Scroll.mint)
26 |
27 |
--------------------------------------------------------------------------------
/reference/control-expressions/README.md:
--------------------------------------------------------------------------------
1 | # Control Expressions
2 |
3 | In Mint, control structures are expressions that return a value, so they are called **control expressions**.
4 |
5 | Here are the control expressions:
6 |
7 | * **if...else** - basic, 2 way conditional expression
8 | * **case** - extended, N-way conditional expression
9 | * **for** - iteration over `Array(a)` `Set(a)` and `Map(a,b)`
10 | * **try** - executes a sequence of **synchronous** expressions, including failure handling
11 | * **sequence** - executes **asynchronous** expressions in sequence, including failure handling
12 | * **parallel** - executes **asynchronous** expressions in parallel, including failure handling
13 |
14 |
--------------------------------------------------------------------------------
/reference/control-expressions/case.md:
--------------------------------------------------------------------------------
1 | # case
2 |
3 | A case expression looks like this:
4 |
5 | ```text
6 | case (condition) {
7 | match1 => value1
8 | match2 => value2
9 | match3 => value3
10 | => defaultValue
11 | }
12 | ```
13 |
14 | It returns the value of the first branch that matches the condition, or the default if none matched.
15 |
16 | There are some rules that will be enforced by either the parser or the compiler:
17 |
18 | * the `condition` can be any type
19 | * the matches of branches must be the same type as the condition.
20 | * the values of the branches must be the same type
21 |
22 | {% hint style="info" %}
23 | `case` expressions are compiled to `if...else` statements.
24 | {% endhint %}
25 |
26 | ## Matching an enum
27 |
28 | [Enums](../enums.md) can be matched using a case expression to destructure it's variants values like so:
29 |
30 | ```text
31 | case (result) {
32 | Result::Err error => /* do something with the error */
33 | Result::Ok value => /* do something with the value */
34 | }
35 | ```
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/reference/control-expressions/for.md:
--------------------------------------------------------------------------------
1 | # for
2 |
3 | `for` is useful for iterating over either an `Array(a)` or a `Set(a)` or a `Map(a,b)`. The result if the iteration is always an array where the type of it's items is from the type of the expression.
4 |
5 | In the next example we iterate over an array of Strings and making them uppercase:
6 |
7 | ```text
8 | for (item of ["bob", "joe"]) {
9 | String.toUpperCase(item)
10 | }
11 | /* The result will be ["BOB", "JOE"]
12 | ```
13 |
14 | The return type of this `for` expression is `Array(String)`
15 |
16 | #### Selecting items
17 |
18 | You can limit the items for which the iteration should take place with an optional `when` block:
19 |
20 | ```text
21 | for (item of ["bob", "joe"]) {
22 | String.toUpperCase(item)
23 | } when {
24 | item == "bob"
25 | }
26 | /* The result will be ["BOB"]
27 | ```
28 |
29 | In the example we specified that the expression should only run \(and return\) for items which equals "bob".
30 |
31 |
--------------------------------------------------------------------------------
/reference/control-expressions/if...else.md:
--------------------------------------------------------------------------------
1 | # if...else
2 |
3 | The `if...else` conditional expression returns one of two values based on a condition. It looks like this:
4 |
5 | ```text
6 | if (condition) {
7 | value1
8 | } else {
9 | value2
10 | }
11 | ```
12 |
13 | There are some rules that will be enforced:
14 |
15 | * The `else` branch must be present, if it's missing you will get a syntax error. This ensures you handle all possibilities.
16 | * The `condition` must evaluate to type `Bool`.
17 | * The values of both branches **must evaluate to the same type**
18 |
19 | {% hint style="info" %}
20 | Currently there is no shorthand for a conditional expression.
21 | {% endhint %}
22 |
23 | #### else if ...
24 |
25 | Multiple `if..else` statements can be written in sequence:
26 |
27 | ```text
28 | if (number > 5) {
29 | true
30 | } else if (number > 2 {
31 | true
32 | } else {
33 | false
34 | }
35 | ```
36 |
37 |
--------------------------------------------------------------------------------
/reference/control-expressions/parallel.md:
--------------------------------------------------------------------------------
1 | # parallel
2 |
3 | `parallel` expressions allows you to do things in parallel, and then do things with their results.
4 |
5 | A `parallel` expression is built up from multiple parts:
6 |
7 | * expressions or statements that return a `Promise` or a `Result`
8 | * an optional `then` branch
9 | * `catch` branches
10 | * an optional `finally` branch
11 |
12 | An example:
13 |
14 | ```text
15 | parallel {
16 | articles =
17 | loadImages()
18 |
19 | users =
20 | loadUsers()
21 | } then {
22 | next {
23 | articles = articles,
24 | users = users,
25 | }
26 | } catch {
27 | /* errors are catched here. */
28 | } finally {
29 | /* Clean things up here. */
30 | }
31 | ```
32 |
33 | Keep in mind that **you need to handle all possible errors** that can be returned from a statement, although the compiler has your back here and will show an error if you forget one.
34 |
35 | The return value of a `parallel` expression is always a `Promise(error, result)` :
36 |
37 | * If the value of the `then` branch is a `Promise` then it will be returned as is
38 | * If the value of the `then` branch is not a Promise then it will return a promise which never fails `Promise(Never, result)` where the `result` is the value of the `then` branch
39 | * If there is no `then` branch then the return value is `Promise(Never, Void)`
40 |
41 | A few notable things to keep in mind:
42 |
43 | * All of the catches must match the return value of the last statement
44 | * Results and unwrapped into variables
45 | * Promises are `await` -ed and unwrapped into variables
46 |
47 |
--------------------------------------------------------------------------------
/reference/control-expressions/sequence.md:
--------------------------------------------------------------------------------
1 | # sequence
2 |
3 | `sequence` expressions allows you to do things in sequence.
4 |
5 | A `sequence` expression is built up from multiple parts:
6 |
7 | * expressions or statements that return a `Promise` or a `Result`
8 | * `catch` branches
9 | * an optional `finally` branch
10 |
11 | An example of handling loading something with a request:
12 |
13 | ```text
14 | sequence {
15 | /* Started to load the users */
16 | next { loading = true }
17 |
18 | /*
19 | Make the request and wait for it to complete
20 | and store in the "response" variable
21 | */
22 | response =
23 | Http.get('/users.json')
24 | |> Http.send()
25 |
26 | /* Parse the body of the response as JSON */
27 | body =
28 | Json.parse(response.body)
29 | |> Maybe.toResult("Json Parse Error")
30 |
31 | /*
32 | Try to decode the list of users and convert the
33 | result into a Promise so we can handle it here then
34 | store it in the "users" variable
35 | */
36 | users =
37 | decode response.body as Array(User)
38 |
39 | /* If everything went well store the users */
40 | next { users = users }
41 |
42 | /* If the request fails handle it here */
43 | } catch Http.Error => error {
44 | next { error = "Something went wrong loading the request." }
45 |
46 | /* If the decoding fails handle it here */
47 | } catch Object.Error => error {
48 | next { error = Object.Error.toString(error) }
49 |
50 | /* Catch everything else */
51 | } catch {
52 | next { error = "Could not decode the response." }
53 |
54 | /*
55 | After everything is handled or finished
56 | it's not loading anymore
57 | */
58 | } finally {
59 | next { loading = false }
60 | }
61 | ```
62 |
63 | Keep in mind that **you need to handle all possible errors** that can be returned from a statement, although the compiler has your back here and will show an error if you forget one.
64 |
65 | The return value of a `sequence` expression is always a `Promise(error, result)` :
66 |
67 | * If the last statement is a `Promise` then it will be returned as is
68 | * If the last statement is not a Promise then it will return a promise which never fails `Promise(Never, result)` where the `result` is the value of the last statement
69 |
70 | A few notable things to keep in mind:
71 |
72 | * All of the catches must match the return value of the last statement
73 | * Results and unwrapped into variables
74 | * Promises are `await` -ed and unwrapped into variables
75 |
76 |
--------------------------------------------------------------------------------
/reference/control-expressions/try.md:
--------------------------------------------------------------------------------
1 | # try
2 |
3 | `try` is a control expression for **handling synchronous computations that might fail**.
4 |
5 | A try expression has 3 parts:
6 |
7 | * statements that returns a `Result`
8 | * catch expressions
9 | * a return expression
10 |
11 | Let's see an example where we are using this expression to decode an object:
12 |
13 | ```text
14 | record User {
15 | email : String,
16 | name : String
17 | }
18 |
19 | module Example {
20 | fun decodeUser (object : Object) : Result(Object.Error, User) {
21 | try {
22 | /*
23 | Try to decode the email from the object,
24 | if succeeds the value is assigned to the
25 | "email" variable.
26 | */
27 | email =
28 | object
29 | |> Object.Decode.field("email", Object.Decode.string)
30 |
31 | /*
32 | Same for the name.
33 | */
34 | name =
35 | object
36 | |> Object.Decode.field("name", Object.Decode.string)
37 |
38 | /*
39 | At this point we have the fields so we can return
40 | a result with the user.
41 | */
42 | Result.ok({
43 | email = email
44 | name = name
45 | })
46 |
47 | /*
48 | If any of the decoders fail we handle it here and
49 | return an result with the error.
50 | */
51 | } catch Object.Error => error {
52 | Result.error(error)
53 | }
54 | }
55 | }
56 | ```
57 |
58 | Keep in mind that **you need to handle all possible errors** that can be returned from a statement, although the compiler has your back here and will show an error if you forgot one.
59 |
60 | In contrast to the `do` expressions, the value of a `try` expression is the value of **its last expression**. So every `catch` expression must also return the same type.
61 |
62 |
--------------------------------------------------------------------------------
/reference/enums.md:
--------------------------------------------------------------------------------
1 | # Enums
2 |
3 | In Mint **enums** represents [Algebraic Data Types](https://en.wikipedia.org/wiki/Algebraic_data_type).
4 |
5 | With **enums** it's possible to describe data which contains **different type of values** \(called variants\). For example a type for a logged in state can be written as two variants:
6 |
7 | ```text
8 | enum UserState {
9 | LoggedIn(User)
10 | Visitor
11 | }
12 | ```
13 |
14 | since this is a type it can be used in type signatures:
15 |
16 | ```text
17 | fun isLoggedIn (userState : UserState) : Bool {
18 | case (userState) {
19 | UserState::LoggedIn user => true
20 | UserState::Visitor => false
21 | }
22 | }
23 |
24 | isLoggedIn(UserState::LoggedIn(user)) /* true */
25 | isLoggedIn(UserState::Visitor) /* false */
26 | ```
27 |
28 | as you can see from the code above you can create instances of the type by using it's name then a double colon then it's variant and then any arguments it takes `UserState::LoggedIn(user)` also you can match the variants in a [case expression](control-expressions/case.md).
29 |
30 | ## Type variables
31 |
32 | You can define **type variables** for an **enum** so it can become generic meaning that a type of a value of a variant can be any other type.
33 |
34 | The best example for this is the `Result(error, value)` type:
35 |
36 | ```text
37 | enum Result(error, value) {
38 | Err(error)
39 | Ok(value)
40 | }
41 | ```
42 |
43 | which can be used with any types for error and value:
44 |
45 | ```text
46 | /* A result where the error and value is both string */
47 | Result(String, String)
48 |
49 | /* An example result type for HTTP requests. */
50 | Result(Http.ErrorResponse, Response)
51 | ```
52 |
53 |
--------------------------------------------------------------------------------
/reference/environment-variables.md:
--------------------------------------------------------------------------------
1 | # Environment Variables
2 |
3 | When building web applications usually the same application needs to working in different environments \(development, staging, production\). There are some [variables](https://en.wikipedia.org/wiki/Environment_variable) like API endpoints that are different between these environments.
4 |
5 | Mint offers a simple feature for managing these variables. You can create different files `.env` `.env.production` which have contents like this:
6 |
7 | ```text
8 | ENDPOINT=http://localhost:3001
9 | WSENDPOINT=ws://localhost:3001
10 | ```
11 |
12 | Then in Mint code you can inline them:
13 |
14 | ```text
15 | component Main {
16 | fun render : Html {
17 |
18 | <{ @ENDPOINT }>
19 |
20 | }
21 | }
22 | ```
23 |
24 | ### Specifying .env file
25 |
26 | [The Mint CLI](../getting-started/using-the-cli.md) has a global flab `-e` or `--env` which takes the path to the `.env` file:
27 |
28 | ```text
29 | mint start --env .env.production
30 | ```
31 |
32 |
--------------------------------------------------------------------------------
/reference/equality.md:
--------------------------------------------------------------------------------
1 | # Equality
2 |
3 | In Mint, 2 objects are considered equal if they have the same type and all values are equal. The equality operator is `==`.
4 |
5 | These examples all evaluate to true:
6 |
7 | ```text
8 | { name = "Jon Doe", age=27 } == { age=27, name = "Jon Doe" }
9 | Maybe.just("A") == Maybe.just("A")
10 | ["A"] == ["A"]
11 | ```
12 |
13 | In JavaScript, the same `==` comparison would return false. We say Mint uses "logical" equality.
14 |
15 | In addition to **records** and **enums**, the following types use logical equality:
16 |
17 | * `String`
18 | * `Number`
19 | * `Boolean`
20 | * `Array`
21 | * `FormData`
22 | * `Date`
23 | * `Maybe`
24 | * `Result`
25 | * `Map`
26 | * `Set`
27 | * `SearchParams`
28 |
29 | Types that have not implemented the logical equality operation fall back to using the JavaScript **strict equality operator** `===`
30 |
31 |
--------------------------------------------------------------------------------
/reference/functions.md:
--------------------------------------------------------------------------------
1 | # Functions
2 |
3 | Functions are callable pieces of code which:
4 |
5 | * can take 0 or more parameters
6 | * must have only one expression as the body
7 | * can be defined in **components, modules, stores,** and **providers**
8 |
9 | A function is defined by the `fun` keyword followed by its **name, arguments** and **return type:**
10 |
11 | ```text
12 | module Greeter {
13 | fun greet (name : String) : Html {
14 |
15 | <{ "Hello " + name + "!" }>
16 |
17 | }
18 | }
19 | ```
20 |
21 | Things to keep in mind:
22 |
23 | * the name of the function must:
24 | * start with a lowercase letter
25 | * contain only letters and numbers
26 | * **the body of the function is always a single expression**
27 | * type annotations are mandatory
28 | * the parentheses for the arguments can be left off if the function does not take any arguments.
29 |
30 | ## Where block
31 |
32 | You can assign variables for use in the function body in a where block:
33 |
34 | ```text
35 | fun greet (id : String, users : Array(User)) : String {
36 | "Hello " + user.name + "!"
37 | } where {
38 | user =
39 | getUserFromId(id, users)
40 | }
41 | ```
42 |
43 | ## Calling a function
44 |
45 | You call a function with its name, providing zero or more arguments separated by commas in parentheses.
46 |
47 | ```text
48 | module Greeter {
49 | fun greet (name : String) : Html {
50 |
51 | <{ "Hello " + name + "!" }>
52 |
53 | }
54 |
55 | fun main : Html {
56 | greet("Bob")
57 | }
58 | }
59 | ```
60 |
61 | If the function belongs to a **store** or a **module** you can call it like this:
62 |
63 | ```text
64 | module Greeter {
65 | fun greet (name : String) : Html {
66 |
67 | <{ "Hello " + name + "!" }>
68 |
69 | }
70 | }
71 |
72 | component Main {
73 | fun render : Html {
74 | Greeter.greet("Bob")
75 | }
76 | }
77 | ```
78 |
79 | ## Functions as arguments
80 |
81 | You can define a function which takes a function as an argument. The type of this argument must be defined \(see [below](functions.md#type-of-a-function)\) and must match the type of the actual function passed at runtime. The function can be an **anonymous** or **named** function.
82 |
83 | ```text
84 | module Greeter {
85 | fun greet (name : String) : Html {
86 |
87 | <{ "Hello " + name + "!" }>
88 |
89 | }
90 | }
91 |
92 | component Main {
93 | fun renderGreeting (name : String, greeter : Function(String, Html)) : Html {
94 | greeter(name)
95 | }
96 |
97 | fun render : Html {
98 | renderGreeting("Bob", Greeter.greet)
99 | }
100 | }
101 | ```
102 |
103 | Here we passed the `Greeter.greet` function as an argument.
104 |
105 | ## Type of a function
106 |
107 | Functions have a specific type signature, like everything else in Mint. The type for the function includes the types of its arguments \(in parenthesis\) and the return value \(last in list\).
108 |
109 | For a function like:
110 |
111 | ```text
112 | fun greet (name : String) : Html {
113 |
114 | <{ "Hello " + name + "!" }>
115 |
116 | }
117 | ```
118 |
119 | the type is:
120 |
121 | ```text
122 | Function(String, Html)
123 | ```
124 |
125 | This can be read as:
126 |
127 | > A function which takes a `String` and returns `Html`
128 |
129 | ## Anonymous functions
130 |
131 | Anonymous functions look like this:
132 |
133 | ```text
134 | (event : Number) : Void { handleClick(event) }
135 |
136 | (suffix : String, match : Regex.Match) : String { match.match + suffix }
137 |
138 | () : Void { 42 }
139 | ```
140 |
141 | The anonymous function starts with one or more argument definitions enclosed by parentheses followed by the type definition after a colon `:` , then a single expression that determines the return value enclosed by brackets.
142 |
143 | This can be used as an expression anywhere you would use a value:
144 |
145 | ```text
146 | component Greeter {
147 | fun render : Html {
148 |
149 | "Click Me!"
150 |
151 | }
152 | }
153 | ```
154 |
155 | ## Partial Application
156 |
157 | Functions can be[ partially applied](https://en.wikipedia.org/wiki/Partial_application) which means that if you call a function with less arguments than the number of arguments defined, it will not call the function but return an other function with takes the arguments that were left out:
158 |
159 | ```text
160 | fun concat(head : String, tail : String) : String {
161 | head + " " + tail
162 | }
163 |
164 | try {
165 | partialFunction =
166 | concat("Head") /* Function(String) */
167 |
168 | partialFunction("Tail") /* Head Tail */
169 | }
170 | ```
171 |
172 | ## Recursive Functions
173 |
174 | All functions can be called recursively:
175 |
176 | ```text
177 | component Main {
178 | fun fibonacci(num : Number) : Number {
179 | if (num <= 1) {
180 | 1
181 | } else {
182 | fibonacci(num - 1) + fibonacci(num - 2)
183 | }
184 | }
185 |
186 | fun render : Html {
187 |
188 | <{ fibonacci(10) }>
189 |
190 | }
191 | }
192 | ```
193 |
194 | {% hint style="danger" %}
195 | Be careful when using recursive functions, the type-checker does not check if there is an exit condition, if there is not it will cause an infinite loop.
196 | {% endhint %}
197 |
198 |
--------------------------------------------------------------------------------
/reference/javascript-interop/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Interop
2 |
3 | Mint provides 2 way interoperability with \(JavaScript\):
4 |
5 | * You can invoke arbitrary JavaScript by [inlining](javascript-interop.md)
6 | * You can receive data back from JavaScript using [decode expressions](decoding-objects.md)
7 |
8 |
--------------------------------------------------------------------------------
/reference/javascript-interop/decoding-objects.md:
--------------------------------------------------------------------------------
1 | # Decoding Objects
2 |
3 | In order to process data from JavaScript in Mint, you must convert it from JavaScript:
4 |
5 | * object `{ name: "Joe" }`
6 | * array `[1, 2, 3]`
7 | * number `0`
8 | * string `"Joe"`
9 | * `null`
10 | * `undefined`
11 |
12 | ...to a Mint typed value.
13 |
14 | The `decode` expression:
15 |
16 | ```text
17 | decode object as String
18 | ```
19 |
20 | ...will try to convert the untyped value to a typed value. it returns a `Result(Object.Error, ...)`, which can be used in a [try expression](../control-expressions/try.md).
21 |
22 | You can decode JavaScript values into Mint **primitive values** \(`String`, `Bool`, `Number`, `Time`\) **simple structures** \(`Maybe(a)`,`Set(a)`,`Map(a,b)`, `Array(a)`\) and **records** which only have **decodable values**.
23 |
24 | {% hint style="info" %}
25 | If you try to decode a Type which is not supported or try to decode something that is not an `Object`, you will get a nice error message.
26 | {% endhint %}
27 |
28 | An example of decoding a simple record:
29 |
30 | ```text
31 | record User {
32 | name : String,
33 | age : Number
34 | }
35 |
36 | component Main {
37 | fun render : Html {
38 | try {
39 | object =
40 | Json.parse("{\"name\": \"John\", \"age\": 30 }")
41 | |> Maybe.toResult("Decode Error")
42 |
43 | user =
44 | decode object as User
45 |
46 | (
47 | <{ user.name }>
48 |
)
49 | } catch Object.Error => error {
50 | <{ "Could not decode!" }>
51 | } catch String => error {
52 | <{ "Invalid JSON!" }>
53 | }
54 | }
55 | }
56 | ```
57 |
58 | In this case, `user` is a `Result(Object.Error, User)`.
59 |
60 | ## Decoding not supported names
61 |
62 | There are times when we want to decode a key from an object into a record whats name is not supported as a record key for example `tag_list`. In this case we can use the `using` keyword to specify the mapping between the object and the record.
63 |
64 | Here is an example:
65 |
66 | ```text
67 | record Post {
68 | tagList: Array(String) using "tag_list"
69 | }
70 | ```
71 |
72 | When decoding an object as a `Post` it will look for the `tag_list` field instead of the `tagList` field in the object, so this JavaScript Object:
73 |
74 | ```javascript
75 | {
76 | tag_list: ["a", "b"]
77 | }
78 | ```
79 |
80 | will decode into this record:
81 |
82 | ```text
83 | {
84 | tagList = ["a", "b"]
85 | }
86 | ```
87 |
88 | ...it will use this key for the encoding as well.
89 |
90 |
--------------------------------------------------------------------------------
/reference/javascript-interop/encoding-objects.md:
--------------------------------------------------------------------------------
1 | # Encoding Objects
2 |
3 | Since we can decode objects we need to have a way to encode them into JavaScript objects as well. The `encode` expression is the way to do that:
4 |
5 | ```text
6 | encode { name = "Bob" } /* Object */
7 | encode variable /* Object */
8 | ```
9 |
10 | The `encode` expression tries to encode a typed object into a JavaScript object \(`Object` type in Mint\), in case that it's impossible to do that you will get a nice error message.
11 |
12 | The general rule is that you can `encode` everything which can be `decode`ed.
13 |
14 | Encoding data is useful when you want to convert it to Json:
15 |
16 | ```text
17 | encode { name = "Bob" }
18 | |> Json.stringify()
19 | /* { "name": "Bob"} */
20 | ```
21 |
22 |
--------------------------------------------------------------------------------
/reference/javascript-interop/javascript-interop.md:
--------------------------------------------------------------------------------
1 | # Inlining JavaScript
2 |
3 | Since Mint itself compiles to [JavaScript](https://en.wikipedia.org/wiki/JavaScript), including additional JavaScript is pretty straightforward. Simply wrap the JavaScript in **back-ticks** anywhere you would write an expression. Mint assumes that the type of the value returned by this expressions matches whatever is needed by the surrounding Mint code \(but see [decoding](decoding-objects.md) for a way to safely convert it\).
4 |
5 | {% hint style="danger" %}
6 | Inlining allows you to invoke arbitrary JavaScript code. This can cause unexpected runtime errors. You can bypass the Mint type system, storing invalid data in Mint variables and cause Mint itself to be the source of the runtime error. Use it with care!
7 | {% endhint %}
8 |
9 | Here is an example inlining a call to the JavaScript function [Window.alert](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert):
10 |
11 | ```text
12 | component Main {
13 | fun handleClick (event : Html.Event) : Void {
14 | `alert("Hello")`
15 | }
16 |
17 | fun render : Html {
18 |
19 | <{ "Click to alert!" }>
20 |
21 | }
22 | }
23 | ```
24 |
25 | ### Inlined JavaScript Statements
26 |
27 | Since Mint is an expression based language, the inlined JavaScript code must also be an expression. If you need to execute multiple JavaScript statements, wrap the code in a [self executing anonymous function](http://markdalgleish.com/2011/03/self-executing-anonymous-functions/):
28 |
29 | ```text
30 | module Greeter {
31 | fun greet (name : String) : String {
32 | `
33 | (() => {
34 | Math.SquareCircle( 1.0);
35 | Math.TrisectAngle( Math.Pi / 4)
36 | return "Hello " + #{name} + "!"
37 | })()
38 | `
39 | }
40 | }
41 | ```
42 |
43 | Your code need not return a value if you know that Mint does not expect one in that context \(such as at the end of a `sequence` expression or a function of type `Void`\).
44 |
45 | {% hint style="warning" %}
46 | You should expect your code to be used in a `return` statement.
47 | {% endhint %}
48 |
49 | ### Interpolation in inlined JavaScript Statements
50 |
51 | In certain cases you might want access to the Mint scope \(so to speak\), you can do that by using the interpolation syntax `#{...}` in inlined JavaScript. The code inside is evaluated in Mint in the current scope.
52 |
53 | ```text
54 | module Greeter {
55 | get name : String {
56 | "Me"
57 | }
58 |
59 | fun greet : String {
60 | `
61 | (() => {
62 | Math.SquareCircle( 1.0);
63 | Math.TrisectAngle( Math.Pi / 4);
64 | return "Hello " + #{name} + "!"
65 | })()
66 | `
67 | }
68 | }
69 | ```
70 |
71 |
--------------------------------------------------------------------------------
/reference/literals.md:
--------------------------------------------------------------------------------
1 | # Literals
2 |
3 | ## Boolean
4 |
5 | Represents the [Boolean type](https://en.wikipedia.org/wiki/Boolean_data_type). It has two possible values `true` and `false`.
6 |
7 | ```text
8 | true
9 | false
10 | ```
11 |
12 | ## Number
13 |
14 | Represents the [Number type](https://developer.mozilla.org/en-US/docs/Glossary/Number) from JavaScript.
15 |
16 | ```text
17 | 3.14
18 | 42
19 | -10
20 | ```
21 |
22 | {% hint style="info" %}
23 | Currently there are no support for other representations such as hex or binary.
24 | {% endhint %}
25 |
26 | ## String
27 |
28 | Represents the [String type](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) from JavaScript.
29 |
30 | ```text
31 | "hello world"
32 | ```
33 |
34 | Escaping works as in JavaScript:
35 |
36 | ```text
37 | "hello \"world\""
38 | ```
39 |
40 | Strings can span multiple lines
41 |
42 | ```text
43 | "hello
44 | world"
45 | ```
46 |
47 | or can be split into smaller consecutive parts
48 |
49 | ```text
50 | "hello " \
51 | "world" == "hello world"
52 | ```
53 |
54 | you interpolate expressions in a string with the `#{...}` syntax.
55 |
56 | ```text
57 | try {
58 | name = "Joe"
59 |
60 | "Hello #{name}" /* Hello Joe */
61 | }
62 | ```
63 |
64 | ## Array
65 |
66 | An Array is a generic type containing elements of any other type.
67 |
68 | It is typically created with an array literal:
69 |
70 | ```text
71 | [1, 2, 3]
72 | ```
73 |
74 |
--------------------------------------------------------------------------------
/reference/packages.md:
--------------------------------------------------------------------------------
1 | # Packages
2 |
3 | Mint uses **decentralized package management**, which means that instead of a global repository all information lives in Git repositories alongside the source code. Versions of packages are just Git tags in Semver format \(major.minor.patch\).
4 |
5 |
--------------------------------------------------------------------------------
/reference/records.md:
--------------------------------------------------------------------------------
1 | # Records
2 |
3 | Records are data structures that have a fixed set of keys.
4 |
5 | You can define a **record type** with the `record` keyword:
6 |
7 | ```text
8 | record User {
9 | email : String,
10 | name : String,
11 | id : Number
12 | }
13 | ```
14 |
15 | {% hint style="info" %}
16 | Records cannot have types which have type variables.
17 | {% endhint %}
18 |
19 | {% hint style="warning" %}
20 | Record definitions are globally unique, so defining a record with the same structure but a different name will raise an error.
21 | {% endhint %}
22 |
23 | ## Nested records
24 |
25 | Records can be nested in each other, using **nested type definitions.**
26 |
27 | ```text
28 | record Position {
29 | x : Number,
30 | y : Number
31 | }
32 |
33 | record Entity {
34 | position : Position,
35 | id : String
36 | }
37 | ```
38 |
39 | Creating a nested record is straightforward:
40 |
41 | ```text
42 | entity =
43 | {
44 | position = {
45 | x = 0,
46 | y = 0
47 | },
48 | id = "0"
49 | }
50 | ```
51 |
52 | ## Working with records
53 |
54 | Records can be created like this:
55 |
56 | ```text
57 | {
58 | email = "john.doe@gmail.com",
59 | name = "John Doe",
60 | id = 0
61 | }
62 | ```
63 |
64 | You can create a new record by copying from an existing one and changing only some of the fields, like this:
65 |
66 | ```text
67 | user =
68 | {
69 | email = "john.doe@gmail.com",
70 | name = "John Doe",
71 | id = 0
72 | }
73 |
74 | updatedUser =
75 | { user | name = "Stuart" }
76 |
77 | {
78 | email = "john.doe@gmail.com",
79 | name = "Stuart",
80 | id = 0
81 | }
82 | ```
83 |
84 | {% hint style="warning" %}
85 | Trying to add fields to a record which doesn't have it in it's definition will raise an error.
86 | {% endhint %}
87 |
88 | {% hint style="info" %}
89 | **\[PLANNED\]** You can update a nested record with a dot notation.
90 | {% endhint %}
91 |
92 | ```text
93 | user =
94 | {
95 | name = "Stuart",
96 | address = {
97 | location = "Freedonia"
98 | }
99 | }
100 |
101 | { user |
102 | name = "Bob",
103 | address.location = "Super Silly Fun Land"
104 | }
105 | ```
106 |
107 |
--------------------------------------------------------------------------------
/reference/routing.md:
--------------------------------------------------------------------------------
1 | # Routing
2 |
3 | In Mint, routes of an application are defined at the top level with the **routes** block.
4 |
5 | Here is an example of an application where you can list users on one route and show a single user on another route:
6 |
7 | ```text
8 | routes {
9 | / {
10 | Application.setPage("index")
11 | }
12 |
13 | /users/:id (id: Number) {
14 | do {
15 | Application.setPage("show")
16 | Application.loadUser(id)
17 | }
18 | }
19 | }
20 | ```
21 |
22 | Keep in mind the following things:
23 |
24 | * Routes are matched in the order they are defined from **top to bottom**
25 | * Routes can only have one routes block per application
26 | * Routes are used to set the state, not to render things
27 |
28 |
--------------------------------------------------------------------------------
/reference/store.md:
--------------------------------------------------------------------------------
1 | # Store
2 |
3 | Stores are global containers of application specific data. They are defined with the `store` keyword:
4 |
5 | ```text
6 | store Counter.Store {
7 | state counter : Number = 0
8 |
9 | fun increment : Promise(Never, Void) {
10 | next { counter = counter + 1 }
11 | }
12 |
13 | fun decrement : Promise(Never, Void) {
14 | next { counter = counter - 1 }
15 | }
16 | }
17 | ```
18 |
19 | In the example above, we defined a store for a global counter with a function to increment it and one to decrement it.
20 |
21 | Stores are:
22 |
23 | * **global** - which means they are accessible from anywhere \(for example: `Counter.Store.counter`\). This also means all accesses update the same, shared data.
24 | * **mutable** - their data can be changed using a `next` call \(but only inside the store\)
25 | * they can only contain **functions** and **properties**
26 |
27 | ## States
28 |
29 | States on a store define keys which correspond to specific type of values. They can be accessed by their name.
30 |
31 | ## Connecting to components
32 |
33 | Stores can be connected to components. To learn more, check out [components](components/connecting-stores.md).
34 |
35 |
--------------------------------------------------------------------------------