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 = 42
let count = 42;
+Type annotation on binding | let count: int = 42
let 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 = 10
let x: int = 10;
+Float | let x: float = 10.0
let x: float = 10.0;
+Boolean | let x: bool = false
let 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 10
let 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 + b
let 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 b
if (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 b
let 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`