├── README.md ├── SUMMARY.md ├── getting-started ├── configuration.md ├── handling-events.md ├── styling-with-css.md ├── the-main-component.md ├── tools.md └── using-the-cli.md ├── recipes └── using-third-party-css.md └── reference ├── built-in-types.md ├── components ├── README.md ├── computed-properties.md ├── connecting-stores.md ├── internal-state.md ├── properties.md ├── referencing-elements-and-components.md ├── styling.md └── using-providers.md ├── control-expressions ├── README.md ├── case.md ├── for.md ├── if...else.md ├── parallel.md ├── sequence.md └── try.md ├── enums.md ├── environment-variables.md ├── equality.md ├── functions.md ├── javascript-interop ├── README.md ├── decoding-objects.md ├── encoding-objects.md └── javascript-interop.md ├── literals.md ├── packages.md ├── records.md ├── routing.md └── store.md /README.md: -------------------------------------------------------------------------------- 1 | # Mint 2 | 3 | Mint is a refreshing, strongly typed, expression-oriented programming language for the front-end web. 4 | 5 | It tries to solve most common issues of **Single Page Applications \(SPAs\)**: 6 | 7 | * Reusable components 8 | * Styling 9 | * Routing 10 | * Global and local state handling 11 | * Synchronous and asynchronous computations that might fail 12 | 13 | While focusing on: 14 | 15 | * Developer happiness 16 | * Fast compilation 17 | * Readability 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Mint](README.md) 4 | 5 | ## Getting Started 6 | 7 | * [Installation](https://www.mint-lang.com/install) 8 | * [Tools](getting-started/tools.md) 9 | * [Using the CLI](getting-started/using-the-cli.md) 10 | * [Configuration](getting-started/configuration.md) 11 | * [The Main Component](getting-started/the-main-component.md) 12 | * [Styling with CSS](getting-started/styling-with-css.md) 13 | * [Handling Events](getting-started/handling-events.md) 14 | 15 | ## Recipes 16 | 17 | * [Using third party CSS](recipes/using-third-party-css.md) 18 | 19 | ## Reference 20 | 21 | * [Literals](reference/literals.md) 22 | * [Functions](reference/functions.md) 23 | * [Records](reference/records.md) 24 | * [Enums](reference/enums.md) 25 | * [Control Expressions](reference/control-expressions/README.md) 26 | * [if...else](reference/control-expressions/if...else.md) 27 | * [case](reference/control-expressions/case.md) 28 | * [for](reference/control-expressions/for.md) 29 | * [try](reference/control-expressions/try.md) 30 | * [sequence](reference/control-expressions/sequence.md) 31 | * [parallel](reference/control-expressions/parallel.md) 32 | * [Built in Types](reference/built-in-types.md) 33 | * [Components](reference/components/README.md) 34 | * [Properties](reference/components/properties.md) 35 | * [Computed Properties](reference/components/computed-properties.md) 36 | * [Styling](reference/components/styling.md) 37 | * [Connecting Stores](reference/components/connecting-stores.md) 38 | * [Using Providers](reference/components/using-providers.md) 39 | * [Internal State](reference/components/internal-state.md) 40 | * [Referencing elements and components](reference/components/referencing-elements-and-components.md) 41 | * [Store](reference/store.md) 42 | * [Routing](reference/routing.md) 43 | * [Equality](reference/equality.md) 44 | * [JavaScript Interop](reference/javascript-interop/README.md) 45 | * [Inlining JavaScript](reference/javascript-interop/javascript-interop.md) 46 | * [Decoding Objects](reference/javascript-interop/decoding-objects.md) 47 | * [Encoding Objects](reference/javascript-interop/encoding-objects.md) 48 | * [Packages](reference/packages.md) 49 | * [Environment Variables](reference/environment-variables.md) 50 | 51 | -------------------------------------------------------------------------------- /getting-started/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | Your application / package is configured with the `mint.json` file \(which is a [JSON](https://en.wikipedia.org/wiki/JSON) file\). 4 | 5 | The initial `mint.json` file of an application looks like this: 6 | 7 | ```text 8 | { 9 | "name": "my-app", 10 | "source-directories": [ 11 | "source" 12 | ], 13 | "dependencies": { 14 | "mint-core": { 15 | "repository": "https://github.com/mint-lang/mint-core", 16 | "constraint": "0.0.0 <= v < 1.0.0" 17 | } 18 | } 19 | } 20 | ``` 21 | 22 | {% hint style="info" %} 23 | All the fields are validated so if something is not right you will get a nice error message! 24 | {% endhint %} 25 | 26 | Here are all the possible fields \(dot notation denotes a nested object\): 27 | 28 | | Path | Purpose | 29 | | :--- | :--- | 30 | | name | The name of your application or package. | 31 | | source-directories | The directories which contains source files of your application. | 32 | | test-directories | The directories which contains tests for your application. | 33 | | dependencies | Contains the dependencies. | 34 | | application | Contains application specific information \(can be omitted in packages\) | 35 | | application.head | A path to an HTML file which can contain links to external resources. | 36 | | application.title | The initial title of the application. | 37 | | application.meta | An object which contains informations which is converted to META tags. | 38 | | application.icon | A path to an image file which is converted to different icons like [favicons](https://en.wikipedia.org/wiki/Favicon). | 39 | | application.orientation | \[PWA\] The orientation of the application, either "portrait" or "landscape". | 40 | | application.name | \[PWA\] The name of the application. | 41 | | application.theme-color | \[PWA\] The theme color of the application. | 42 | | application.display | \[PWA\] The display property of the application, one of: "fullscreen", "standalone", "minimal-ui", "browser" | 43 | | formatter-config.indent-size | Specifies how many spaces is used to intend code locks. | 44 | 45 | ## Dependencies 46 | 47 | The dependencies object contains all the packages that your application needs. Each key is the name of a package and the value is an object that has a `repository` field pointing to Git repository and `constraint` field that is the version information in one of two formats: 48 | 49 | * `0.0.0 <= v < 1.0.0` where the `v` is the resolvable version 50 | * `master:1.0.0` where the first part is the Git reference \(of a branch, commit hash or tag\) followed by a colon and the version the package should resolve as this is necessary because the version is only specified by tags. 51 | 52 | -------------------------------------------------------------------------------- /getting-started/handling-events.md: -------------------------------------------------------------------------------- 1 | # Handling Events 2 | 3 | In web applications there are a lot of interactive parts, and all of these parts are handling some input from the users. Inputs can come from a variety of sources such as keyboard, and mouse. An instance of these inputs is called an **event**. 4 | 5 | You can handle events coming from the DOM using **event attributes**. An event attribute is always starts with `on` followed by the name of the event \(capitalized\), for example: `onClick` or `onInput`. 6 | 7 | In this example we are listening on a click event: 8 | 9 | ```text 10 | component Main { 11 | fun render : Html { 12 | 15 | } 16 | } 17 | ``` 18 | 19 | Every time the users clicks on this button the `"Hello"` string is printed in the console. 20 | 21 | Event attributes should match a specific type signature `Function(Html.Event, a)` which means that only functions which take an `Html.Event` and return something can be passed to these attributes. Alternatively you can just pass a `Function(a)` if you don't care about the event. 22 | 23 | ## Html.Event 24 | 25 | An Html.Event is a [record](../reference/records.md) with the following fields: 26 | 27 | | Name | Type | 28 | | :--- | :--- | 29 | | **bubbles** | Bool | 30 | | **cancelable** | Bool | 31 | | **currentTarget** | Dom.Element | 32 | | **defaultPrevented** | Bool | 33 | | **eventPhase** | Number | 34 | | **isTrusted** | Bool | 35 | | **target** | Dom.Element | 36 | | **timeStamp** | Number | 37 | | **type** | String | 38 | | **data** | String | 39 | | **altKey** | Bool | 40 | | **charCode** | Number | 41 | | **ctrlKey** | Bool | 42 | | **key** | String | 43 | | **keyCode** | Number | 44 | | **locale** | String | 45 | | **location** | Number | 46 | | **metaKey** | Bool | 47 | | **repeat** | Bool | 48 | | **shiftKey** | Bool | 49 | | **which** | Number | 50 | | **button** | Number | 51 | | **buttons** | Number | 52 | | **clientX** | Number | 53 | | **clientY** | Number | 54 | | **pageX** | Number | 55 | | **pageY** | Number | 56 | | **screenX** | Number | 57 | | **screenY** | Number | 58 | | **detail** | Number | 59 | | **deltaMode** | Number | 60 | | **deltaX** | Number | 61 | | **deltaY** | Number | 62 | | **deltaZ** | Number | 63 | | **animationFrame** | String | 64 | | **pseudoElement** | String | 65 | | **propertyName** | String | 66 | | **elapsedTime** | Number | 67 | 68 | -------------------------------------------------------------------------------- /getting-started/styling-with-css.md: -------------------------------------------------------------------------------- 1 | # Styling with CSS 2 | 3 | After you have something on the screen you probably want to add some styles to it. This is easy to do in Mint, you can even do it with simple CSS! 4 | 5 | In components, styles can be defined with an identifier, then applied to HTML using the identifier as a CSS class. A style can contain any number of CSS definitions, sub rules, media queries, `if` and `case` statements. 6 | 7 | {% hint style="info" %} 8 | A style is scoped to the component it's defined in. 9 | {% endhint %} 10 | 11 | This is how it looks when we style the component what we just created: 12 | 13 | ```text 14 | component Main { 15 | style button { 16 | background: red; 17 | color: white; 18 | border: 0; 19 | } 20 | 21 | fun render : Html { 22 | 23 | "Click ME!" 24 | 25 | } 26 | } 27 | ``` 28 | 29 | The button is now red with white text. 30 | 31 | -------------------------------------------------------------------------------- /getting-started/the-main-component.md: -------------------------------------------------------------------------------- 1 | # The Main Component 2 | 3 | To display anything on the screen you need to define the `Main` component. Anything it returns from its `render` function will be shown. 4 | 5 | Here we render a button on the screen: 6 | 7 | ```text 8 | component Main { 9 | fun render : Html { 10 | 13 | } 14 | } 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /getting-started/tools.md: -------------------------------------------------------------------------------- 1 | # Tools 2 | 3 | This page covers general-purpose tools that support Mint language. When you’re ready to create a project, get the tools of your choice: 4 | 5 | ## Editor extensions 6 | 7 | * [VSCode extension](https://github.com/faustinoaq/vscode-mint-lang) adds mint syntax highlighting and autocomplete support 8 | * [Emacs mint-mode](https://github.com/creatorrr/emacs-mint-mode) adds mint syntax highlighting and auto formatter using `mint format` 9 | 10 | -------------------------------------------------------------------------------- /getting-started/using-the-cli.md: -------------------------------------------------------------------------------- 1 | # Using the CLI 2 | 3 | Once you install Mint you will have a `mint` binary at your disposal. 4 | 5 | This is the help section \(you can print this using the `--help` flag\): 6 | 7 | ```text 8 | mint --help 9 | Mint - Help 10 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11 | Usage: 12 | mint [flags...] [arg...] 13 | 14 | Mint 15 | 16 | Flags: 17 | --env, -e (default: "") # Loads the given .env file 18 | --help # Displays help for the current command. 19 | 20 | Subcommands: 21 | build # Builds the project for production 22 | docs # Starts the documentation server 23 | format # Formats source files 24 | init # Initializes a new project 25 | install # Installs dependencies 26 | loc # Counts Lines of Code 27 | start # Starts the development server 28 | test # Runs the tests 29 | version # Shows version 30 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 31 | ``` 32 | 33 | ## Initializing a new application 34 | 35 | To initialize a new application, you invoke the binary with the `init` command: 36 | 37 | ```text 38 | mint init my-app 39 | ``` 40 | 41 | This will scaffold a new application in the `my-app` directory: 42 | 43 | ```text 44 | my-app 45 | ├── source 46 | │ └── Main.mint 47 | ├── tests 48 | │ └── Main.mint 49 | ├── .gitignore 50 | └── mint.json 51 | ``` 52 | 53 | ## Installing dependencies 54 | 55 | You install dependencies by invoking the binary with the `install` command: 56 | 57 | ```text 58 | mint install 59 | ``` 60 | 61 | This builds a dependency tree of the packages and installs them by cloning their repositories from Git sources into the `.mint` directory. 62 | 63 | ## Starting a development server 64 | 65 | To start a development server for an application, just invoke the binary with the `start` command: 66 | 67 | ```text 68 | mint start 69 | ``` 70 | 71 | ## Running tests 72 | 73 | To run the test written for an application, just invoke the binary with the `test` command: 74 | 75 | ```text 76 | mint test 77 | ``` 78 | 79 | This will: 80 | 81 | * compile the application including the tests 82 | * open a browser in headless mode 83 | * run the tests in the browser printing the results along the way 84 | 85 | ## Building for production 86 | 87 | To build the application for production deployment, invoke the binary with the `build` command: 88 | 89 | ```text 90 | mint build 91 | ``` 92 | 93 | This will: 94 | 95 | * generate the `index.html` file 96 | * compile the application in production mode 97 | * if a base icon is provided, generate favicons in different sizes 98 | * copy all static files from the `public` directory 99 | 100 | ## Browsing documentation of installed packages 101 | 102 | To browse the documentation of the application and installed packages, invoke the binary with the \`docs\` command: 103 | 104 | ```text 105 | mint docs 106 | ``` 107 | 108 | It starts a documentation server which can be accessed at `http://localhost:3002` 109 | 110 | -------------------------------------------------------------------------------- /recipes/using-third-party-css.md: -------------------------------------------------------------------------------- 1 | # Using third party CSS 2 | 3 | There is this frequently asked question: 4 | 5 | > How can I use it with a CSS library like bootstrap with Mint? 6 | 7 | In this page I will guide you through it. 8 | 9 | ### Adding the library 10 | 11 | In this example we will be adding [Bootstrap](https://getbootstrap.com) to a Mint application. 12 | 13 | First you need to create an HTML file which will be loaded in the `head` of the compiled HTML, for now it will only contain the link to the stylesheet from the CDN: 14 | 15 | {% code-tabs %} 16 | {% code-tabs-item title="assets/head.html" %} 17 | ```markup 18 | 22 | ``` 23 | {% endcode-tabs-item %} 24 | {% endcode-tabs %} 25 | 26 | Next you will need to modify the `mint.json` file so the file we just created: 27 | 28 | {% code-tabs %} 29 | {% code-tabs-item title="mint.json" %} 30 | ```javascript 31 | { 32 | "name": "mint-bootstrap-example", 33 | "source-directories": [ 34 | "source" 35 | ], 36 | "application": { 37 | "head": "assets/head.html" 38 | } 39 | } 40 | ``` 41 | {% endcode-tabs-item %} 42 | {% endcode-tabs %} 43 | 44 | If you start the development server and load the application the CSS for Bootstrap will be loaded. 45 | 46 | ### Using the library 47 | 48 | Now you can use the library as you normally would, by adding the `class` attribute on HTML elements: 49 | 50 | {% code-tabs %} 51 | {% code-tabs-item title="source/Main.mint" %} 52 | ```text 53 | component Main { 54 | style base { 55 | background: #f5f5f5; 56 | text-align: center; 57 | height: 100vh; 58 | 59 | justify-content: center; 60 | align-items: center; 61 | display: flex; 62 | } 63 | 64 | fun render : Html { 65 | 66 | 131 | 132 | } 133 | } 134 | 135 | ``` 136 | {% endcode-tabs-item %} 137 | {% endcode-tabs %} 138 | 139 | -------------------------------------------------------------------------------- /reference/built-in-types.md: -------------------------------------------------------------------------------- 1 | # Built in Types 2 | 3 | Mint comes with several built in types. These are used in control expressions: `Promise`, `Result`, `Void`, `Never`. The `Maybe` type represents a [nillable](https://stackoverflow.com/questions/5913200/why-is-it-called-nillable) value. 4 | 5 | ## Promise 6 | 7 | The promise type represents a [JavaScript promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), an asynchronous computational task that might fail. 8 | 9 | In Mint promises have two parameters `Promise(error, result)` : 10 | 11 | * **error** - the value if the task failed 12 | * **result** - the value if the task succeeded 13 | 14 | A good example is a HTTP request which in Mint looks like this: 15 | 16 | `Promise(Http.ErrorResponse, Http.Response)` 17 | 18 | where the `Http.ErrorResponse` is a record containing information about the error that happened while `Http.Response` is a record containing the response of the request. 19 | 20 | Promises are used in a [sequence ](control-expressions/sequence.md)and [parallel](control-expressions/parallel.md) expressions. 21 | 22 | ## Result 23 | 24 | The result type, represents a **synchronous** task that might fail. It is defined as an **enum:** 25 | 26 | ```text 27 | enum Result(error, value) { 28 | Err(error) 29 | Ok(value) 30 | } 31 | ``` 32 | 33 | In Mint, results have two parameters `Result(error, value)`: 34 | 35 | * **error** - the type of the error 36 | * **value** - the type of the result 37 | 38 | For example, converting a `String` to a `Number`: 39 | 40 | * If the conversion fails we get an error: 41 | 42 | ```text 43 | "blah" 44 | |> Number.fromString() 45 | |> Result.isError() # true 46 | ``` 47 | 48 | * If the conversion succeeds we get a value 49 | 50 | ```text 51 | "10" 52 | |> Number.fromString() 53 | |> Result.isOk() # true 54 | ``` 55 | 56 | Results are used in [sequence](control-expressions/sequence.md), [parallel](control-expressions/parallel.md) and [try](control-expressions/try.md) expressions. 57 | 58 | ## Void 59 | 60 | The void type represents a side-effect: an expression which cannot have a value, either because it's not synchronous or because it causes a state change. 61 | 62 | * `Void` is used to handle events coming from the DOM or any outside source. 63 | * `Void` can be explicitly returned with the `void` keyword. 64 | 65 | ## Never 66 | 67 | The never type is used to describe tasks \(promises or results\) that can never fail. For example, here is the type of task that can never fail: `SomeTask(Never, Value)` 68 | 69 | These tasks don't need to be caught in [sequence](control-expressions/sequence.md), [parallel](control-expressions/parallel.md) and [try](control-expressions/try.md) expressions. 70 | 71 | ## Maybe 72 | 73 | The maybe type represents a value which may or may not exist. It's defined as an **enum:** 74 | 75 | ```text 76 | enum Maybe(value) { 77 | Just(value) 78 | Nothing() 79 | } 80 | ``` 81 | 82 | For example here is a user who may or may not have a car: 83 | 84 | ```text 85 | record Car { 86 | type : String, 87 | engine : String 88 | } 89 | 90 | record User { 91 | name : String, 92 | car : Maybe(Car) 93 | } 94 | ``` 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /reference/components/README.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | Components are the main building blocks of the language. 4 | 5 | Components have the following features: 6 | 7 | * they can be composed inside each other 8 | * they can be connected to **stores** 9 | * they can subscribe to **providers** 10 | * styles can be defined for HTML elements inside them 11 | * properties can be defined with types and default values 12 | 13 | -------------------------------------------------------------------------------- /reference/components/computed-properties.md: -------------------------------------------------------------------------------- 1 | # Computed Properties 2 | 3 | Computed properties are functions that work like properties, they are defined with the `get`keyword and no arguments. 4 | 5 | ```text 6 | component Greeter { 7 | property name : String = "" 8 | 9 | get text : String { 10 | "Hello " + name + "!" 11 | } 12 | 13 | fun render : Html { 14 |
15 | <{ text }> 16 |
17 | } 18 | } 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /reference/components/connecting-stores.md: -------------------------------------------------------------------------------- 1 | # Connecting Stores 2 | 3 | Components refer to [stores](../store.md) in order to **use functions and properties from the store**. 4 | 5 | The component references a store with the `connect` keyword: 6 | 7 | ```text 8 | store Counter { 9 | state count : Number = 0 10 | 11 | fun setCount (count : Number) : Promise(Never, Void) { 12 | next { count = count } 13 | } 14 | } 15 | 16 | component Main { 17 | connect Counter exposing { count, setCount } 18 | 19 | fun render : Html { 20 |
23 | 24 | <{ "Count: " + Number.toString(count) }> 25 | 26 |
27 | } 28 | } 29 | ``` 30 | 31 | When connecting a store, the component must use the `exposing` keyword to list the particular functions or properties it will use. 32 | 33 | #### Exposing with a different name 34 | 35 | You can expose a connected function or property by a different name using the `as` notation: 36 | 37 | ```text 38 | connect Counter exposing { count as myCount } 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /reference/components/internal-state.md: -------------------------------------------------------------------------------- 1 | # Internal State 2 | 3 | ```text 4 | component Main { 5 | state greeting : String = "Welcome" 6 | 7 | fun greet : Promise(Never, Void) { 8 | next { greeting = newGreeting } 9 | } where { 10 | newGreeting = 11 | if (greeting == "hello") { 12 | "bye" 13 | } else { 14 | "hello" 15 | } 16 | } 17 | 18 | fun render : Html { 19 |
20 | <{ greeting }> 21 |
22 | 23 | 26 |
27 | } 28 | } 29 | ``` 30 | 31 | The `state` keyword is used to attach **private** state variable to a Component. It cannot be passed in from another Component, but is updated with the `next` keyword. 32 | 33 | -------------------------------------------------------------------------------- /reference/components/properties.md: -------------------------------------------------------------------------------- 1 | # Properties 2 | 3 | Properties can be defined for components with the **property** keyword: 4 | 5 | ```text 6 | component Test { 7 | property size : String = "small" 8 | property color : String = "red" 9 | 10 | fun render : Html { 11 |
12 | <{ color }> 13 | <{ size }> 14 |
15 | } 16 | } 17 | ``` 18 | 19 | The name of the property must start with a lowercase letter and can only contain letters and numbers. 20 | 21 | Properties are referenced by **name** within the component \(in **styles, functions, computed properties,** etc.\). 22 | 23 | {% hint style="warning" %} 24 | Properties must be fully defined meaning types in it cannot have any type variables. 25 | {% endhint %} 26 | 27 | ## Passing properties 28 | 29 | Property values are passed to the component when it is rendered: 30 | 31 | ```text 32 | component Main { 33 | fun render : Html { 34 | 35 | } 36 | } 37 | ``` 38 | 39 | All properties are type checked, attempting to set an incompatible value will show an error. 40 | 41 | There are some examples of passing different things: 42 | 43 | ```text 44 | component Main { 45 | fun render : Html { 46 | 49 | } 50 | } 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /reference/components/referencing-elements-and-components.md: -------------------------------------------------------------------------------- 1 | # Referencing elements and components 2 | 3 | Sometimes it's necessary to access elements or components in a component for a number of reasons. To do that you can use the `as name` notation. 4 | 5 | The usual example is to focus an element after an event happens: 6 | 7 | ```text 8 | component Main { 9 | fun handleClick : Promise(Never, Void) { 10 | case (input) { 11 | Maybe::Just element => Dom.focusWhenVisible(element) 12 | Maybe::Nothing => next {} 13 | } 14 | } 15 | 16 | fun render : Html { 17 |
18 | 19 | 20 | 23 |
24 | } 25 | } 26 | ``` 27 | 28 | As you can see the `input` variable will be a `Maybe(Dom.Element)` and this is because there is no guarantee that the element will be in the DOM at the time the function runs. 29 | 30 | This works with components as well: 31 | 32 | ```text 33 | component Item { 34 | state text : String = "Hello" 35 | 36 | fun update(text : String) : Promise(Never, Void) { 37 | next { text = text } 38 | } 39 | 40 | fun render : Html { 41 |
42 | <{ text }> 43 |
44 | } 45 | } 46 | 47 | component Main { 48 | fun handleClick : Promise(Never, Void) { 49 | case (item) { 50 | Maybe::Just component => component.update("Bello") 51 | Maybe::Nothing => next {} 52 | } 53 | } 54 | 55 | fun render : Html { 56 |
57 | 58 | 59 | 62 |
63 | } 64 | } 65 | ``` 66 | 67 | -------------------------------------------------------------------------------- /reference/components/styling.md: -------------------------------------------------------------------------------- 1 | # Styling 2 | 3 | Mint includes CSS support for styling HTML elements. 4 | 5 | Within a component, you define styles with a **style block** named with an identifier: 6 | 7 | ```text 8 | component Test { 9 | style base { 10 | font-family: sans; 11 | font-weight: bold; 12 | color: red; 13 | } 14 | 15 | fun render : Html { 16 | 17 | Hello 18 | 19 | } 20 | } 21 | ``` 22 | 23 | Then you apply the styles to an HTML element by specifying it after the tag of the element `` . 24 | 25 | You can have CSS definitions, sub rules, media queries, `if` and `case` expressions inside a `style` block. 26 | 27 | You can even apply multiple styles to an HTML element: 28 | 29 | ```text 30 | component Main { 31 | style a { 32 | color: red; 33 | } 34 | 35 | style b { 36 | background: blue; 37 | } 38 | 39 | fun render : Html { 40 | 41 | } 42 | } 43 | ``` 44 | 45 | ## Interpolation 46 | 47 | Expressions can be interpolated in the value of a property using the interpolation syntax `#{...}`: 48 | 49 | ```text 50 | component Test { 51 | style base { 52 | color: #{color}; 53 | } 54 | 55 | get color : String { 56 | "red" 57 | } 58 | 59 | fun render : Html { 60 | 61 | Hello 62 | 63 | } 64 | } 65 | ``` 66 | 67 | Here the color of the text evaluates to `"red"`. 68 | 69 | ## Arguments 70 | 71 | A style block can take many arguments just like a function: 72 | 73 | ```text 74 | component Main { 75 | style base(color : String) { 76 | color: #{color}; 77 | } 78 | 79 | fun render : Html { 80 |
81 | 82 | "I am red!" 83 |
84 | 85 | 86 | "I am blue!" 87 | 88 | 89 | } 90 | } 91 | ``` 92 | 93 | ## Sub rules 94 | 95 | Additional rules that match the element can be defined in the same block: 96 | 97 | ```text 98 | component Test { 99 | style base { 100 | color: cyan; 101 | 102 | /* The ampersand character references the rule it resids in. */ 103 | &:focus { 104 | color: red; 105 | } 106 | 107 | /* This is a sub rule which uses the descendant selector. */ 108 | a { 109 | color: blue; 110 | } 111 | } 112 | 113 | fun render : Html { 114 | 115 | <{ "Hello " }> 116 | 117 | <{ "world" }> 118 | 119 | 120 | } 121 | } 122 | ``` 123 | 124 | This is useful for styling **pseudo elements and states** or style child elements. 125 | 126 | ## Media queries 127 | 128 | [Media queries](https://www.w3.org/TR/css3-mediaqueries/) can be defined for a style or sub rule. When matched, their contents apply to all elements that match them. 129 | 130 | ```text 131 | component Test { 132 | style base { 133 | color: red; 134 | 135 | @media (min-width: 600px) { 136 | color: blue; 137 | } 138 | } 139 | fun render : Html { 140 | 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 | --------------------------------------------------------------------------------