diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index b4bb42c3c..a261de38b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,6 +1,12 @@ import { readFileSync } from "fs"; import { join } from "path"; import { defineConfig } from "vitepress"; +import { bundledLanguages } from "shiki"; + +// Modify bundledLanguages so it no longer contains the bundled OCaml grammar. This is needed because vitepress config +// doesn't allow you to override bundled grammars, see +// https://github.com/vuejs/vitepress/blob/78c4d3dda085f31912578237dfbe7b1c62f48859/src/node/markdown/plugins/highlight.ts#L65 +delete bundledLanguages['ocaml']; const toggleSyntaxScript = readFileSync(join(__dirname, './toggleSyntax.js'), 'utf8'); @@ -9,16 +15,21 @@ const reasonGrammar = JSON.parse( readFileSync(join(__dirname, "./reasonml.tmLanguage.json"), "utf8") ); -// https://github.com/ocamllabs/vscode-ocaml-platform/blob/master/syntaxes/dune.json +// From https://github.com/ocamllabs/vscode-ocaml-platform/blob/master/syntaxes/dune.json const duneGrammar = JSON.parse( readFileSync(join(__dirname, "./dune.tmLanguage.json"), "utf8") ); -// https://github.com/ocamllabs/vscode-ocaml-platform/blob/master/syntaxes/opam.json +// From https://github.com/ocamllabs/vscode-ocaml-platform/blob/master/syntaxes/opam.json const opamGrammar = JSON.parse( readFileSync(join(__dirname, "./opam.tmLanguage.json"), "utf8") ); +// From https://github.com/ocamllabs/vscode-ocaml-platform/blob/master/syntaxes/ocaml.json +const ocamlGrammar = JSON.parse( + readFileSync(join(__dirname, "./ocaml.tmLanguage.json"), "utf8") +); + const base = process.env.BASE || "unstable"; // https://vitepress.dev/reference/site-config @@ -38,7 +49,7 @@ export default defineConfig({ hostname: `https://melange.re/${base}/`, }, markdown: { - languages: [reasonGrammar, duneGrammar, opamGrammar], + languages: [duneGrammar, ocamlGrammar, opamGrammar, reasonGrammar], }, themeConfig: { outline: { level: [2, 3] }, @@ -112,6 +123,26 @@ export default defineConfig({ }, ], }, + { + text: "Language Basics", + items: [ + { text: "Overview", link: "/overview" }, + { text: "Let bindings", link: "/let-bindings" }, + { text: "Primitives", link: "/primitives" }, + { text: "Basic structures", link: "/basic-structures" }, + { text: "Types", link: "/types" }, + { text: "Records", link: "/records" }, + { text: "Variants", link: "/variants" }, + { text: "Options and nullability", link: "/options" }, + { text: "Functions", link: "/functions" }, + { text: "Recursion", link: "/recursion" }, + { text: "Destructuring", link: "/destructuring" }, + { text: "Pattern matching", link: "/pattern-matching" }, + { text: "Mutable bindings", link: "/mutable-bindings" }, + { text: "Loops", link: "/loops" }, + { text: "Module", link: "/module" }, + ], + }, { text: "Reference", items: [{ text: "API", link: "/api" }], diff --git a/docs/.vitepress/ocaml.tmLanguage.json b/docs/.vitepress/ocaml.tmLanguage.json new file mode 100644 index 000000000..615d3a6df --- /dev/null +++ b/docs/.vitepress/ocaml.tmLanguage.json @@ -0,0 +1,654 @@ +{ + "name": "ocaml", + "scopeName": "source.ocaml", + "fileTypes": ["ml", "eliom", ".ocamlinit"], + "patterns": [ + { "include": "#directives" }, + { "include": "#comments" }, + { "include": "#strings" }, + { "include": "#characters" }, + { "include": "#attributes" }, + { "include": "#extensions" }, + { "include": "#modules" }, + { "include": "#bindings" }, + { "include": "#operators" }, + { "include": "#keywords" }, + { "include": "#literals" }, + { "include": "#types" }, + { "include": "#identifiers" } + ], + "repository": { + "directives": { + "patterns": [ + { + "comment": "line number directive", + "begin": "^[[:space:]]*(#)[[:space:]]*([[:digit:]]+)", + "end": "$", + "beginCaptures": { + "1": { "name": "keyword.other.ocaml" }, + "2": { "name": "constant.numeric.decimal.integer.ocaml" } + }, + "contentName": "comment.line.directive.ocaml" + }, + { + "comment": "toplevel directives", + "patterns": [ + { + "comment": "general, loading codes", + "begin": "^[[:space:]]*(#)[[:space:]]*(help|quit|cd|directory|remove_directory|load_rec|load|use|mod_use)", + "end": "$", + "beginCaptures": { + "1": { "name": "keyword.other.ocaml" }, + "2": { "name": "keyword.other.ocaml" } + }, + "patterns": [{ "include": "#strings" }] + }, + { + "comment": "environment queries", + "begin": "^[[:space:]]*(#)[[:space:]]*(show_class_type|show_class|show_exception|show_module_type|show_module|show_type|show_val|show)", + "end": "$", + "beginCaptures": { + "1": { "name": "keyword.other.ocaml" }, + "2": { "name": "keyword.other.ocaml" } + }, + "patterns": [ + { "include": "#types" }, + { "include": "#identifiers" } + ] + }, + { + "comment": "pretty-printing, tracing", + "begin": "^[[:space:]]*(#)[[:space:]]*(install_printer|print_depth|print_length|remove_printer|trace|untrace_all|untrace)", + "end": "$", + "beginCaptures": { + "1": { "name": "keyword.other.ocaml" }, + "2": { "name": "keyword.other.ocaml" } + }, + "patterns": [ + { "include": "#literals" }, + { "include": "#identifiers" } + ] + }, + { + "comment": "compiler options", + "begin": "^[[:space:]]*(#)[[:space:]]*(labels|ppx|principal|rectypes|warn_error|warnings)", + "end": "$", + "beginCaptures": { + "1": { "name": "keyword.other.ocaml" }, + "2": { "name": "keyword.other.ocaml" } + }, + "patterns": [ + { "include": "#strings" }, + { "include": "#literals" } + ] + } + ] + }, + { + "comment": "topfind directives", + "begin": "^[[:space:]]*(#)[[:space:]]*(require|list|camlp4o|camlp4r|predicates|thread)", + "end": "$", + "beginCaptures": { + "1": { "name": "keyword.other.ocaml" }, + "2": { "name": "keyword.other.ocaml" } + }, + "patterns": [{ "include": "#strings" }] + }, + { + "comment": "cppo directives", + "begin": "^[[:space:]]*(#)[[:space:]]*(define|undef|ifdef|ifndef|if|else|elif|endif|include|warning|error|ext|endext)", + "end": "$", + "beginCaptures": { + "1": { "name": "keyword.other.ocaml" }, + "2": { "name": "keyword.other.ocaml" } + }, + "patterns": [ + { "name": "keyword.other.ocaml", "match": "\\b(defined)\\b" }, + { "name": "keyword.other.ocaml", "match": "\\\\" }, + { "include": "#comments" }, + { "include": "#strings" }, + { "include": "#characters" }, + { "include": "#keywords" }, + { "include": "#operators" }, + { "include": "#literals" }, + { "include": "#types" }, + { "include": "#identifiers" } + ] + } + ] + }, + + "comments": { + "patterns": [ + { + "comment": "empty comment", + "name": "comment.block.ocaml", + "match": "\\(\\*\\*\\)" + }, + { + "comment": "ocamldoc comment", + "name": "comment.doc.ocaml", + "begin": "\\(\\*\\*(?!\\*)", + "end": "\\*\\)", + "patterns": [ + { "include": "source.ocaml.ocamldoc#markup" }, + { "include": "#strings-in-comments" }, + { "include": "#comments" } + ] + }, + { + "comment": "Cinaps comment", + "begin": "\\(\\*\\$", + "end": "\\*\\)", + "beginCaptures": [{ "name": "comment.cinaps.ocaml" }], + "endCaptures": [{ "name": "comment.cinaps.ocaml" }], + "patterns": [{ "include": "$self" }] + }, + { + "comment": "block comment", + "name": "comment.block.ocaml", + "begin": "\\(\\*", + "end": "\\*\\)", + "patterns": [ + { "include": "#strings-in-comments" }, + { "include": "#comments" } + ] + } + ] + }, + + "strings-in-comments": { + "patterns": [ + { + "comment": "char literal", + "match": "'(\\\\)?.'" + }, + { + "comment": "string literal", + "begin": "\"", + "end": "\"", + "patterns": [{ "match": "\\\\\\\\" }, { "match": "\\\\\"" }] + }, + { + "comment": "quoted string literal", + "begin": "\\{[[:lower:]_]*\\|", + "end": "\\|[[:lower:]_]*\\}" + } + ] + }, + + "strings": { + "patterns": [ + { + "comment": "quoted string literal", + "name": "string.quoted.braced.ocaml", + "begin": "\\{(%%?[[:alpha:]_][[:word:]']*(\\.[[:alpha:]_][[:word:]']*)*[[:space:]]*)?[[:lower:]_]*\\|", + "end": "\\|[[:lower:]_]*\\}", + "beginCaptures": { + "1": { "name": "keyword.other.extension.ocaml" } + } + }, + { + "comment": "string literal", + "name": "string.quoted.double.ocaml", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "comment": "escaped newline", + "name": "constant.character.escape.ocaml", + "match": "\\\\$" + }, + { + "comment": "escaped backslash", + "name": "constant.character.escape.ocaml", + "match": "\\\\\\\\" + }, + { + "comment": "escaped quote or whitespace", + "name": "constant.character.escape.ocaml", + "match": "\\\\[\"'ntbr ]" + }, + { + "comment": "character from decimal ASCII code", + "name": "constant.character.escape.ocaml", + "match": "\\\\[[:digit:]]{3}" + }, + { + "comment": "character from hexadecimal ASCII code", + "name": "constant.character.escape.ocaml", + "match": "\\\\x[[:xdigit:]]{2}" + }, + { + "comment": "character from octal ASCII code", + "name": "constant.character.escape.ocaml", + "match": "\\\\o[0-3][0-7]{2}" + }, + { + "comment": "unicode character escape sequence", + "name": "constant.character.escape.ocaml", + "match": "\\\\u\\{[[:xdigit:]]{1,6}\\}" + }, + { + "comment": "printf format string", + "name": "constant.character.printf.ocaml", + "match": "%[-0+ #]*([[:digit:]]+|\\*)?(.([[:digit:]]+|\\*))?[lLn]?[diunlLNxXosScCfFeEgGhHBbat!%@,]" + }, + { + "comment": "unknown escape sequence", + "name": "invalid.illegal.unknown-escape.ocaml", + "match": "\\\\." + } + ] + } + ] + }, + + "characters": { + "patterns": [ + { + "comment": "character literal from escaped backslash", + "name": "string.quoted.single.ocaml", + "match": "'(\\\\\\\\)'", + "captures": { "1": { "name": "constant.character.escape.ocaml" } } + }, + { + "comment": "character literal from escaped quote or whitespace", + "name": "string.quoted.single.ocaml", + "match": "'(\\\\[\"'ntbr ])'", + "captures": { "1": { "name": "constant.character.escape.ocaml" } } + }, + { + "comment": "character literal from decimal ASCII code", + "name": "string.quoted.single.ocaml", + "match": "'(\\\\[[:digit:]]{3})'", + "captures": { "1": { "name": "constant.character.escape.ocaml" } } + }, + { + "comment": "character literal from hexadecimal ASCII code", + "name": "string.quoted.single.ocaml", + "match": "'(\\\\x[[:xdigit:]]{2})'", + "captures": { "1": { "name": "constant.character.escape.ocaml" } } + }, + { + "comment": "character literal from octal ASCII code", + "name": "string.quoted.single.ocaml", + "match": "'(\\\\o[0-3][0-7]{2})'", + "captures": { "1": { "name": "constant.character.escape.ocaml" } } + }, + { + "comment": "character literal from unknown escape sequence", + "name": "string.quoted.single.ocaml", + "match": "'(\\\\.)'", + "captures": { + "1": { "name": "invalid.illegal.unknown-escape.ocaml" } + } + }, + { + "comment": "character literal", + "name": "string.quoted.single.ocaml", + "match": "'.'" + } + ] + }, + + "attributes": { + "begin": "\\[(@|@@|@@@)[[:space:]]*([[:alpha:]_]+(\\.[[:word:]']+)*)", + "end": "\\]", + "beginCaptures": { + "1": { "name": "keyword.operator.attribute.ocaml" }, + "2": { + "name": "keyword.other.attribute.ocaml", + "patterns": [ + { + "name": "keyword.other.ocaml punctuation.other.period punctuation.separator.period", + "match": "\\." + } + ] + } + }, + "patterns": [{ "include": "$self" }] + }, + + "extensions": { + "begin": "\\[(%|%%)[[:space:]]*([[:alpha:]_]+(\\.[[:word:]']+)*)", + "end": "\\]", + "beginCaptures": { + "1": { "name": "keyword.operator.extension.ocaml" }, + "2": { + "name": "keyword.other.extension.ocaml", + "patterns": [ + { + "name": "keyword.other.ocaml punctuation.other.period punctuation.separator.period", + "match": "\\." + } + ] + } + }, + "patterns": [{ "include": "$self" }] + }, + + "modules": { + "patterns": [ + { + "begin": "\\b(sig)\\b", + "end": "\\b(end)\\b", + "beginCaptures": [{ "name": "keyword.other.ocaml" }], + "endCaptures": [{ "name": "keyword.other.ocaml" }], + "patterns": [{ "include": "source.ocaml.interface" }] + }, + { + "begin": "\\b(struct)\\b", + "end": "\\b(end)\\b", + "beginCaptures": [{ "name": "keyword.other.ocaml" }], + "endCaptures": [{ "name": "keyword.other.ocaml" }], + "patterns": [{ "include": "$self" }] + } + ] + }, + + "bindings": { + "patterns": [ + { + "comment": "for loop", + "match": "\\b(for)[[:space:]]+([[:lower:]_][[:word:]']*)", + "captures": { + "1": { "name": "keyword.ocaml" }, + "2": { "name": "entity.name.function.binding.ocaml" } + } + }, + { + "comment": "local open/exception/module", + "match": "\\b(let)[[:space:]]+(open|exception|module)\\b(?!')", + "captures": { + "1": { "name": "keyword.ocaml" }, + "2": { "name": "keyword.ocaml" } + } + }, + { + "comment": "let expression", + "match": "\\b(let)[[:space:]]+(?!lazy\\b(?!'))(rec[[:space:]]+)?(?!rec\\b(?!'))([[:lower:]_][[:word:]']*)(?![[:word:]'])[[:space:]]*(?!,|::|[[:space:]])", + "captures": { + "1": { "name": "keyword.ocaml" }, + "2": { "name": "keyword.ocaml" }, + "3": { "name": "entity.name.function.binding.ocaml" } + } + }, + { + "comment": "using binding operators", + "match": "\\b(let|and)([$&*+\\-/=>@^|<][!?$&*+\\-/=>@^|%:]*)[[:space:]]*(?!lazy\\b(?!'))([[:lower:]_][[:word:]']*)(?![[:word:]'])[[:space:]]*(?!,|::|[[:space:]])", + "captures": { + "1": { "name": "keyword.ocaml" }, + "2": { "name": "keyword.ocaml" }, + "3": { "name": "entity.name.function.binding.ocaml" } + } + }, + { + "comment": "first class module packing", + "match": "\\([[:space:]]*(val)[[:space:]]+([[:lower:]_][[:word:]']*)", + "captures": { + "1": { "name": "keyword.ocaml" }, + "2": { "patterns": [{ "include": "$self" }] } + } + }, + { + "comment": "locally abstract types", + "match": "(?:\\(|(:))[[:space:]]*(type)((?:[[:space:]]+[[:lower:]_][[:word:]']*)+)", + "captures": { + "1": { + "name": "keyword.other.ocaml punctuation.other.colon punctuation.colon" + }, + "2": { "name": "keyword.ocaml" }, + "3": { "name": "entity.name.function.binding.ocaml" } + } + }, + { + "comment": "optional labeled argument with type", + "begin": "(\\?)\\([[:space:]]*([[:lower:]_][[:word:]']*)", + "beginCaptures": { + "1": { "name": "variable.parameter.optional.ocaml" }, + "2": { "name": "variable.parameter.optional.ocaml" } + }, + "end": "\\)", + "patterns": [{ "include": "$self" }] + }, + { + "comment": "labeled argument with type", + "begin": "(~)\\([[:space:]]*([[:lower:]_][[:word:]']*)", + "beginCaptures": { + "1": { "name": "variable.parameter.labeled.ocaml" }, + "2": { "name": "variable.parameter.labeled.ocaml" } + }, + "end": "\\)", + "patterns": [{ "include": "$self" }] + }, + { "include": "source.ocaml.interface#bindings" } + ] + }, + + "operators": { + "patterns": [ + { + "comment": "binding operator", + "name": "keyword.ocaml", + "match": "\\b(let|and)[$&*+\\-/=>@^|<][!?$&*+\\-/=>@^|%:]*" + }, + { + "comment": "infix symbol", + "name": "keyword.operator.ocaml", + "match": "[$&*+\\-/=>@^%<][~!?$&*+\\-/=>@^|%<:.]*" + }, + { + "comment": "infix symbol that begins with vertical bar", + "name": "keyword.operator.ocaml", + "match": "\\|[~!?$&*+\\-/=>@^|%<:.]+" + }, + { + "comment": "vertical bar", + "name": "keyword.other.ocaml", + "match": "(?@^|%<:.]+" + }, + { + "comment": "prefix symbol", + "name": "keyword.operator.ocaml", + "match": "![~!?$&*+\\-/=>@^|%<:.]*" + }, + { + "comment": "prefix symbol", + "name": "keyword.operator.ocaml", + "match": "[?~][~!?$&*+\\-/=>@^|%<:.]+" + }, + { + "comment": "named operator", + "name": "keyword.operator.ocaml", + "match": "\\b(or|mod|land|lor|lxor|lsl|lsr|asr)\\b" + }, + { + "comment": "method invocation", + "name": "keyword.other.ocaml", + "match": "#" + }, + { + "comment": "type annotation", + "name": "keyword.other.ocaml punctuation.other.colon punctuation.colon", + "match": ":" + }, + { + "comment": "field accessor", + "name": "keyword.other.ocaml punctuation.other.period punctuation.separator.period", + "match": "\\." + }, + { + "comment": "semicolon separator", + "name": "keyword.other.ocaml punctuation.separator.terminator punctuation.separator.semicolon", + "match": ";" + }, + { + "comment": "comma separator", + "name": "keyword.other.ocaml punctuation.comma punctuation.separator.comma", + "match": "," + } + ] + }, + + "keywords": { + "patterns": [ + { + "comment": "reserved ocaml keyword", + "name": "keyword.other.ocaml", + "match": "\\b(and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|match|method|module|mutable|new|nonrec|object|of|open|private|rec|sig|struct|then|to|try|type|val|virtual|when|while|with)\\b(?!')" + } + ] + }, + + "literals": { + "patterns": [ + { + "comment": "boolean literal", + "name": "constant.language.boolean.ocaml", + "match": "\\b(true|false)\\b" + }, + + { + "comment": "floating point decimal literal with exponent", + "name": "constant.numeric.decimal.float.ocaml", + "match": "\\b([[:digit:]][[:digit:]_]*(\\.[[:digit:]_]*)?[eE][+-]?[[:digit:]][[:digit:]_]*[g-zG-Z]?)\\b" + }, + { + "comment": "floating point decimal literal", + "name": "constant.numeric.decimal.float.ocaml", + "match": "\\b([[:digit:]][[:digit:]_]*)(\\.[[:digit:]_]*[g-zG-Z]?\\b|\\.)" + }, + { + "comment": "floating point hexadecimal literal with exponent part", + "name": "constant.numeric.hexadecimal.float.ocaml", + "match": "\\b((0x|0X)[[:xdigit:]][[:xdigit:]_]*(\\.[[:xdigit:]_]*)?[pP][+-]?[[:digit:]][[:digit:]_]*[g-zG-Z]?)\\b" + }, + { + "comment": "floating point hexadecimal literal", + "name": "constant.numeric.hexadecimal.float.ocaml", + "match": "\\b((0x|0X)[[:xdigit:]][[:xdigit:]_]*)(\\.[[:xdigit:]_]*[g-zG-Z]?\\b|\\.)" + }, + + { + "comment": "decimal integer literal", + "name": "constant.numeric.decimal.integer.ocaml", + "match": "\\b([[:digit:]][[:digit:]_]*[lLng-zG-Z]?)\\b" + }, + { + "comment": "hexadecimal integer literal", + "name": "constant.numeric.hexadecimal.integer.ocaml", + "match": "\\b((0x|0X)[[:xdigit:]][[:xdigit:]_]*[lLng-zG-Z]?)\\b" + }, + { + "comment": "octal integer literal", + "name": "constant.numeric.octal.integer.ocaml", + "match": "\\b((0o|0O)[0-7][0-7_]*[lLng-zG-Z]?)\\b" + }, + + { + "comment": "binary integer literal", + "name": "constant.numeric.binary.integer.ocaml", + "match": "\\b((0b|0B)[0-1][0-1_]*[lLng-zG-Z]?)\\b" + }, + + { + "comment": "unit literal", + "name": "constant.language.unit.ocaml", + "match": "\\(\\)" + }, + { + "comment": "parentheses", + "begin": "\\(", + "end": "\\)", + "patterns": [{ "include": "$self" }] + }, + + { + "comment": "empty array", + "name": "constant.language.array.ocaml", + "match": "\\[\\|\\|\\]" + }, + { + "comment": "array", + "begin": "\\[\\|", + "end": "\\|\\]", + "patterns": [{ "include": "$self" }] + }, + + { + "comment": "empty list", + "name": "constant.language.list.ocaml", + "match": "\\[\\]" + }, + { + "comment": "list", + "begin": "\\[", + "end": "]", + "patterns": [{ "include": "$self" }] + }, + { + "comment": "braces", + "begin": "\\{", + "end": "\\}", + "patterns": [{ "include": "$self" }] + } + ] + }, + + "types": { + "patterns": [ + { + "comment": "type parameter", + "name": "storage.type.ocaml", + "match": "'[[:alpha:]][[:word:]']*\\b|'_\\b" + }, + { + "comment": "weak type parameter", + "name": "storage.type.weak.ocaml", + "match": "'_[[:alpha:]][[:word:]']*\\b" + }, + { + "comment": "builtin type", + "name": "support.type.ocaml", + "match": "\\b(unit|bool|int|int32|int64|nativeint|float|char|bytes|string)\\b" + } + ] + }, + + "identifiers": { + "patterns": [ + { + "comment": "wildcard underscore", + "name": "constant.language.ocaml", + "match": "\\b_\\b" + }, + { + "comment": "capital identifier for constructor, exception, or module", + "name": "constant.language.capital-identifier.ocaml", + "match": "\\b[[:upper:]][[:word:]']*('|\\b)" + }, + { + "comment": "lowercase identifier", + "name": "source.ocaml", + "match": "\\b[[:lower:]_][[:word:]']*('|\\b)" + }, + { + "comment": "polymorphic variant tag", + "name": "constant.language.polymorphic-variant.ocaml", + "match": "\\`[[:alpha:]][[:word:]']*\\b" + }, + { + "comment": "empty list (can be used as a constructor)", + "name": "constant.language.list.ocaml", + "match": "\\[\\]" + } + ] + } + } +} diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 000000000..5f0ceb065 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,385 @@ +# Overview + +This is an overview of most language features in Reason. It does not explain +them in detail, but should serve as a quick reference. Please see the guides +on the left for additional details about each feature. + +## Let Bindings + +_Details: [Let Bindings](/let-bindings)_ + +Feature | Example +--------------------------------|---------- +String value | let hi = "Hello World"let hi = "Hello World"; +Int value | let count = 42let count = 42; +Type annotation on binding | let count: int = 42let count: int = 42; + +- Note: Let bindings are immutable and cannot change once created. + +## Built In Types + +_Details: [Primitives](/primitives)_ + +Feature | Example +--------------------------------|---------- +Int | let x: int = 10let x: int = 10; +Float | let x: float = 10.0let x: float = 10.0; +Boolean | let x: bool = falselet x: bool = false; +String | let x: string = "ten"let x: string = "ten"; +Char | let x: char = 'c'let x: char = 'c'; +Unit | let x: unit = ()let x: unit = (); +Option | let x: int option = Some 10let x: option(int) = Some(10); +Tuple | let x: (int, string) = (10, "ten")let x: (int, string) = (10, "ten"); +List | let x: int list = [1; 2; 3];let x: list(int) = [1, 2, 3]; +Array | let x: int array = [|1; 2; 3|]let x: array(int) = [|1, 2, 3|]; +Functions | let x : int -> int -> int = fun a b -> a + blet x: (int, int) => int = (a, b) => a + b; + +## Strings + +_Details: [Strings](/primitives#strings)_ + +Feature | Example +--------------------------------|---------- +String | `"Hello"` +String concatenation | "Hello " ^ "World""Hello " ++ "World" +Character | `'x'` +Character at index | let x = "Hello" in x.[2]let x = "Hello"; x.[2]; + +- String Functions: module Stringmodule String + +## Numbers + +- _Details: [Integer](/primitives#integer)_ +- _Details: [Float](/primitives#float)_ + +Feature | Example +--------------------------------|---------- +Integer | `23`, `-23` +Integer operations | `23 + 1 - 7 * 2 / 5` +Integer modulo | `13 mod 2` +Float | `23.0`, `-23.0` +Float operations | `23.0 +. 1.0 -. 7.0 *. 2.0 /. 5.0` +Float exponentiation | `2.0 ** 3.0` + +## Booleans and Logical Operators + +_Details: [Boolean](/primitives#boolean)_ + +Feature | Example +--------------------------------|---------- +Boolean Values | `true`, `false` +Comparison | `>`, `<`, `>=`, `<=` +Boolean operations | not!, `&&`, || +Reference equality | =====, !=!== +Structural equality | ===, <>!= + +## If-Else Expressions + +Feature | Example +--------------------------------|---------- +If-Else expressions | if condition then a else bif (condition) { a; } else { b; } +Ternary expressions | not applicablecondition ? a : b; + +- Note: These are expressions and can be assigned to a variable: +let x = if condition then a else blet x = if (condition) { a; } else { b; }; + +## Functions + +_Details: [Functions](/functions)_ + +Feature | Example +--------------------------------|---------- +Function definition | `let divide = (a, b) => a / b;` +Function calls | `divide(6, 2); // 3` +Named arguments | `let divide = (~a, ~b) => a / b;` +Calling named arguments | `divide(~a=6, ~b=2); // 3` +Named argument punning | `divide(~a, ~b);` +Recursive functions | `let rec infinite = () => infinite();` + +### Advanced Functions + +Feature | Example +--------------------------------|---------- +Partial application | `let divideTen = divide(10); divideTen(5); // 2` +Partially applying out of order | `let half = divide(_, 2); half(10); // 5` +Optional arguments | `let print = (~prefix=?, text) => {...};` +Optional arguments with default | `let divide = (~a=100, ~b) => a / b;` +Function chaining (pipe) | 32 |> half |> half; // 8 + +### Function Types + +Feature | Example +--------------------------------|---------- +Inline typing | `let divide = (a: int, b: int): int => a / b;` +Standalone type | `type intFn = (int, int) => int;` +Using standalone type | `let divide: intFn = (a, b) => a / b;` +Typing optional arguments | `let print = (~prefix: option(string)=?, text) => {...};` + +## Implicit Return + +There is no `return` keyword in Reason. The last expression in a block or +function definition is the returned value. + +```ocaml +let twentyThree () = + let x = 10 in + let x = x + 10 in + (* x + 3 is the implicit return of the function. *) + x + 3 +``` +```reasonml +let twentyThree = () => { + let x = 10; + let x = x + 10; + /* x + 3 is the implicit return of the function. */ + x + 3; +}; +``` + +## Basic Structures + +_Details: [Basic Structures](basic-structures)_ + +Feature | Example +--------------------------------|---------- +List (Immutable) | `[1, 2, 3]` +List add to front | `[a1, a2, ...theRest]` +List concat | `[a1, a2] @ theRest` +Array (Mutable) | [|1, 2, 3|] +Array access | let arr = [|1, 2, 3|]; arr[1]; +Tuples | `(1, "hello")` + +- List Functions: [`module List`](https://reasonml.github.io/api/List.html) +- Array Functions: [`module Array`](https://reasonml.github.io/api/Array.html) + +## Maps and Sets + +There are several different ways to interact with Maps and Sets depending on the +specific environment being used. In standard Reason code there are `Map` and +`Set` modules: + +- [`module Map.Make`](https://reasonml.github.io/api/Map.Make.html) +- [`module Set.Make`](https://reasonml.github.io/api/Set.Make.html) + +When using BuckleScript `belt` exposes these modules: + +- [`module Belt.Map`](https://bucklescript.github.io/bucklescript/api/Belt.Map.html) +- [`module Belt.Set`](https://bucklescript.github.io/bucklescript/api/Belt.Set.html) + +There are also other libraries that will provide their own implementation of +these data structures. Check the style guide of the project you are +working in to determine which module to use. + +## Type Annotations + +Any expression or argument may include a "type annotation". In most cases, type annotations +are not necessary and the compiler will infer the types automatically. You may include +type annotations to verify your own understanding against what the compiler infers. + +Feature | Example +--------------------------------|---------- +Expression type annotation | `let x = (expression: int)` +Annotation on let binding | `let x: int = expression;` +Argument/return value annotation| `let addOne = (a: int): int => a + 1;` + +## Type Parameters + +Types can be made generic with type parameters. + +Feature | Example +--------------------------------|---------- +Type parameters | `type pair('a, 'b) = ('a, 'b);` +Annotation with parameters | `let x: pair(int, string) = (10, "ten");` +String list | `let x: list(string) = ["Hello", "World"];` + +## Records + +_Details: [Records](records)_ + +Feature | Example +--------------------------------|---------- +Record definition | `type t = {foo: int, bar: string};` +Record creation | `let x = {foo: 10, bar: "hello"};` +Record access | `x.foo;` +Record spread | `let y = {...x, bar: "world"};` +Destructuring | `let {foo, bar} = x;` +Mutable record fields | `type t = {mutable baz: int}; let z = {baz: 10};` +Mutable record updates | `z.baz = 23;` +With type parameters | `type t('a) = {foo: 'a, bar: string};` + +- Note: Record types are [nominal](https://en.wikipedia.org/wiki/Nominal_type_system). This means that two different record definitions (`type x = {...};`) with the exact same fields are not compatible. They cannot be used interchangeably and cannot be spread into each other. + +## Variants + +_Details: [Variants](variants)_ + +Variant types model values that may assume one of many known variations. This +feature is similar to "enums" in other languages, but each variant form may +optionally specify data that is carried along with it. + +Feature | Example +--------------------------------|---------- +Variant definition | type t = | Foo | Bar; +Variants with args | type t = | Foo(string) | Bar(int); +With type parameters | type t('a) = | One('a) | Two('a, 'a); +Using a variant | `let x = Two("Hello", "World");` + +## Options + +_Details: [Options](options)_ + +Options are a built-in variant that represent the presence or absence of a +value. It is similar to the concept of "nullable" values in other languages. Options +are used often. + +Feature | Example +--------------------------------|---------- +Definition (already defined) | type option('a) = | None | Some('a); +Value that is present | `let x = Some("Hello World");` +Value that is absent | `let y = None;` + +## Pattern Matching + +Pattern matching is a very powerful feature in Reason. It matches against variants +and ensures all cases are covered. Start matching using the `switch` keyword: + +```ocaml +match foo with +| Some value -> doSomething value +| None -> error () +``` +```reasonml +switch (foo) { +| Some(value) => doSomething(value) +| None => error() +} +``` + +Feature | Example +--------------------------------|---------- +Basic case | | Some(value) => doSomething(value) +When conditions | | Some(value) when value > 10 => doSomething(value) +Catch-all case | | _ => doSomething() +Matching lists | | [a, b, ...rest] => doSomething(rest) +Matching records | | {foo: value} => doSomething(value) +Matching literals | | "Hello" => handleHello() + +## Unit + +The special "unit" value (written `()`) represents something that never has any +meaningful value (this is distinct from options which may have a value). +Functions usually indicate that they perform a side effect by returning a unit +value. + +Feature | Example +--------------------------------|---------- +Creating a unit | `let x = ();` +Passing to a function | `fn(a, b, ());` +Unit as only argument | `let fn = () => 1; fn();` + +## Refs + +_Details: [Mutable Bindings](mutable-bindings)_ + +Refs allow mutable "variables" in your program. They are a thin wrapper around +a record with a mutable field called `contents`. + +Feature | Example +--------------------------------|---------- +Type (already defined) | `type ref('a) = {mutable contents: 'a};` +Ref creation | `let x = ref(10);` or `let x = {contents: 10};` +Ref access | `x^;` or `x.contents;` +Ref update | `x := 20;` or `x.contents = 20;` + +## Loops + +_Details: [Loops](loops.md)_ + +Loops are discouraged in most cases. Instead functional programming patterns +like `map`, `filter`, or `reduce` can usually be used in their place. + +Feature | Example +--------------------------------|---------- +While | `while (condition) {...}` +For (incrementing) | `for (i in 0 to 9) {...}` (inclusive) +For (decrementing) | `for (i in 9 downto 0) {...}` (inclusive) + +- Note: There is no `break` or early returns in Reason. Use a ref containing a +bool for break-like behavior: `let break = ref(false); while (!break^ && condition) {...};` + +## Modules + +_Details: [Modules](modules)_ + +Modules are a way to group types and values. Each Reason file implicitly +creates a module of the same name. Each `type` definition and `let` binding in +a module automatically becomes a "member" of that module which can be accessed +by other modules. Modules can also be nested using the `module` keyword. + +Feature | Example +--------------------------------|---------- +Module creation | `module Foo = { let bar = 10; };` +Module member access | `Foo.bar;` +Module types | `module type Foo = { let bar: int; };` + +## Functors + +Functors are like functions that create modules. This is an advanced topic +that can be very powerful. Here is a basic example: + +```ocaml +module type Stringable = sig + type t + val toString : t -> string +end + +module Printer (Item : Stringable) = struct + let print (t : Item.t) = print_endline (Item.toString t) + + let printList (list : Item.t list) = + list |> List.map Item.toString |> String.concat ", " |> print_endline +end + +module IntPrinter = Printer (struct + type t = int + let toString = string_of_int +end) + +let () = + IntPrinter.print 10; (* 10 *) + IntPrinter.printList [ 1; 2; 3 ] (* 1, 2, 3 *) +``` +```reasonml +module type Stringable = { + type t; + let toString: (t) => string; +}; + +module Printer = (Item: Stringable) => { + let print = (t: Item.t) => { + print_endline(Item.toString(t)); + }; + + let printList = (list: list(Item.t)) => { + list + |> List.map(Item.toString) + |> String.concat(", ") + |> print_endline; + }; +}; + +module IntPrinter = Printer({ + type t = int; + let toString = string_of_int; +}); + +IntPrinter.print(10); // 10 +IntPrinter.printList([1, 2, 3]); // 1, 2, 3 +``` + +## Comments + +Feature | Example +--------------------------------|---------- +Multiline Comment | `/* Comment here */` +Single line Comment | `// Comment here`