From a338b6a1d1cb6b2658de1bb691ba51f59f8df2d9 Mon Sep 17 00:00:00 2001 From: Rigidity Date: Fri, 5 Jul 2024 21:25:29 -0400 Subject: [PATCH] Add initial docs --- docs/definitions/constants.md | 48 +++++++++ docs/definitions/functions.md | 155 ++++++++++++++++++++++++++++ docs/definitions/let-bindings.md | 47 +++++++++ docs/getting-started/hello-world.md | 6 ++ docs/types/enums.md | 85 +++++++++++++++ docs/types/structs.md | 31 ++++++ docs/types/type-aliases.md | 18 ++++ sidebars.ts | 17 +++ src/theme/prism-rue.ts | 5 +- 9 files changed, 409 insertions(+), 3 deletions(-) create mode 100644 docs/definitions/constants.md create mode 100644 docs/definitions/functions.md create mode 100644 docs/definitions/let-bindings.md create mode 100644 docs/types/enums.md create mode 100644 docs/types/structs.md create mode 100644 docs/types/type-aliases.md diff --git a/docs/definitions/constants.md b/docs/definitions/constants.md new file mode 100644 index 0000000..ece1d05 --- /dev/null +++ b/docs/definitions/constants.md @@ -0,0 +1,48 @@ +--- +slug: /constants +--- + +# Constants + +A constant is similar to a let binding. The difference is that it can be defined outside of a function, as a standalone item. Therefore, the order doesn't matter, but it can only reference other items within the same scope, rather than statements like let bindings. + +Here is an example: + +```rue +const TWO: Int = 2; + +fun main() -> Int { + 42 * TWO +} +``` + +## Inline Constants + +When you mark a constant as inline, it will be inserted wherever it's referenced rather than being defined within the compiled output only a single time. This is typically not a good idea to do unless the constant's value is really short and it's more efficient to inline it. Benchmarking would be required to determine this. + +An inline constant can be defined and used in the same way: + +```rue +inline const TWO: Int = 2; + +fun main() -> Int { + 42 * TWO +} +``` + +## Circular References + +Constants must not reference themselves, either directly or indirectly. If you find yourself needing to do this for some reason, consider writing a function instead. + +For example, this is clearly impossible: + +```rue +const NUM: Int = NUM; +``` + +But this is also circular: + +```rue +const FIRST: Int = SECOND; +const SECOND: Int = FIRST; +``` diff --git a/docs/definitions/functions.md b/docs/definitions/functions.md new file mode 100644 index 0000000..115bca2 --- /dev/null +++ b/docs/definitions/functions.md @@ -0,0 +1,155 @@ +--- +slug: /functions +--- + +# Functions + +A function allows you to separate code, reuse it in multiple places, and use recursion to repeat things multiple times. + +Here is a simple function example: + +```rue +fun main() -> Int { + square(42) +} + +fun square(num: Int) -> Int { + num * num +} +``` + +This will square the value `42` and return the result. + +## Inline Functions + +If a function is concise and you don't want the additional overhead of the function call, you can define an inline function instead. This will replace function calls with the body directly, instead of defining the function and referencing it. + +For example, in this example the function body is simple enough that it makes sense to inline it: + +```rue +fun main() -> Int { + double(100) +} + +inline fun double(num: Int) -> Int { + num * 2 +} +``` + +This is the exact same as writing the following: + +```rue +fun main() -> Int { + 42 * 2 +} +``` + +As you can see, `num` just got replaced with the argument value and inserted in place of the function call. + +:::note +Inline functions are treated as constants, so recursion is not allowed. Use normal functions if you need recursion. +::: + +## Recursion + +You can use [recursion](https://en.wikipedia.org/wiki/Recursion) to run code multiple times. + +For example, the classic [factorial](https://en.wikipedia.org/wiki/Factorial): + +```rue +fun main() -> Int { + factorial(5) +} + +fun factorial(num: Int) -> Int { + if num > 1 { + num * factorial(num - 1) + } else { + 1 + } +} +``` + +In this case, we recursively call factorial on the previous number and multiply with the current number until we reach the base case of `1`. + +So it will call: + +- `5 * factorial(4)` +- `4 * factorial(3)` +- `3 * factorial(2)` + +And so on, until it reaches the final result of `120`. + +## Lambda Functions + +A lambda function, also known as an [anonymous function](https://en.wikipedia.org/wiki/Anonymous_function), lets you define a function as an expression rather than a named item. + +It's convenient for passing functions as parameters: + +```rue +fun main() -> Int { + map([1, 2, 3], fun(num) => num * 100) +} + +fun map(list: Int[], mapper: fun(num: Int) -> Int) -> Int[] { + if list is (Int, Int[]) { + return [mapper(list.first), ...map(list.rest, mapper)]; + } + nil +} +``` + +## Generic Types + +You can define generic types on functions which will get replaced when called. + +Here's a simple example, building on the previous: + +```rue +fun main() -> Int { + map([1, 2, 3], fun(num) => num * 100) +} + +fun map(list: T[], mapper: fun(num: T) -> T) -> T[] { + if list is (T, T[]) { + return [mapper(list.first), ...map(list.rest, mapper)]; + } + nil +} +``` + +:::info +The only difference is that we made `map` generic over any type `T` rather than being specific to `Int`, since the type doesn't matter. +::: + +## Captures + +Functions can capture values from scopes they are defined in, even if they aren't an explicit parameter. This is seen in the previous example, where `factorial` captures itself so it can call it recursively. + +The `main` function itself captures `factorial` so it can call it. + +## Closures + +If a function leaves the scope it's defined in, it must first [partially apply](https://en.wikipedia.org/wiki/Partial_application) its captures. This creates a [closure](). + +Because of this, functions can be passed to other functions or variables, otherwise known as [higher-order functions](https://en.wikipedia.org/wiki/Higher-order_function). This is one of the most powerful functional programming features. + +Here is an example of this: + +```rue +fun main() -> Int { + let doubler = fn(2); + doubler(1000) +} + +fun multiplier(factor: Int) -> fun(num: Int) -> Int { + fun fn(num: Int) -> Int { + num * factor + } + fn +} +``` + +In this example, `multiplier` returns a function which captures `factor`. This creates a closure which is assigned to `doubler`, then called. + +The output is `2000`. diff --git a/docs/definitions/let-bindings.md b/docs/definitions/let-bindings.md new file mode 100644 index 0000000..80ef5c9 --- /dev/null +++ b/docs/definitions/let-bindings.md @@ -0,0 +1,47 @@ +--- +slug: /let-bindings +--- + +# Let Bindings + +In Rue, everything is immutable. This means there isn't the traditional concept of a mutable variable whose value can be reassigned. Once a value is given to a variable, it's final unless you make a new variable. + +Here's an example: + +```rue +fun main() -> Int { + let num = 42; + num + 10 +} +``` + +This will bind `num` to the value `42`, then add it to `10` and return the result. + +:::note + +This is not possible, since it's immutable: + +```rue +fun main() -> Int { + let num = 42; + num = 34; // You can't assign to bindings. + num + 10 +} +``` + +::: + +## What's the point? + +Bindings let you prevent repetition and optimize code when a value is used more than once. It can also be used to make code clearer. + +For example, we use `num` twice in this example, but it only needs to be written once, both in the code and in the compiled output: + +```rue +fun main() -> Int { + let num = 42; + num * num +} +``` + +If a binding is only used once, it will be inlined as though you wrote the value directly. diff --git a/docs/getting-started/hello-world.md b/docs/getting-started/hello-world.md index 36913fb..b0fb19b 100644 --- a/docs/getting-started/hello-world.md +++ b/docs/getting-started/hello-world.md @@ -14,8 +14,14 @@ fun main() -> Bytes { } ``` +:::info +You don't need to use `return` at the end of a function. Everything in Rue must return a value, including functions, so this is implied. +::: + Now, run the following command to run the file: ```bash rue build hello.rue --run ``` + +You should see `"Hello, world!"` printed in the console. diff --git a/docs/types/enums.md b/docs/types/enums.md new file mode 100644 index 0000000..6b3c6da --- /dev/null +++ b/docs/types/enums.md @@ -0,0 +1,85 @@ +--- +slug: /enums +--- + +# Enums + +An enum allows you to define a type which can contain multiple variants, each with their own discriminator value, and optionally fields. + +This is an example of an enum without fields: + +```rue +enum Mode { + Locked, + Unlocked, +} + +fun main() -> Int { + // By default, the type is `Mode::Locked` + let locked = Mode::Locked; + + // Let's generalize that to `Mode` + let mode: Mode = locked; + + // Now we can check the type + if !(mode is Mode::Locked) { + raise "This isn't possible."; + } + + // Lastly, we can cast to an `Int` + mode as Int +} +``` + +## Discriminants + +By default, the first discriminant is `0`, and each one after that increments by `1`. + +However, you can also explicitly set the discriminant: + +```rue +enum Mode { + Locked = 5, + Unlocked = 10, +} +``` + +## Fields + +If any of the enum variants have fields, all variants of the enum will be represented as a list, with the discriminant being the first item. + +Here is an example: + +```rue +enum Value { + None, + Scalar { + num: Int, + }, + Point { + x: Int, + y: Int, + }, +} + +fun main() -> Int { + number(Value::Point { x: 42, y: 34 }) +} + +fun number(value: Value) -> Int { + if value is Value::None { + return 0; + } + + if value is Value::Scalar { + return value.num; + } + + assume value is Value::Point; + value.x + value.y +} +``` + +:::note +There is currently no way to do pattern matching, chain if statements, or narrow the enum variants down incrementally. However, these are planned features. +::: diff --git a/docs/types/structs.md b/docs/types/structs.md new file mode 100644 index 0000000..ecaa9b8 --- /dev/null +++ b/docs/types/structs.md @@ -0,0 +1,31 @@ +--- +slug: /structs +--- + +# Structs + +A struct allows you to define a type which can contain multiple fields. + +Here is a simple example: + +```rue +struct Point { + x: Int, + y: Int, +} + +fun main() -> Int { + let point = Point { + x: 42, + y: 34, + }; + + assert !is_origin(point); + + point.x * point.y +} + +fun is_origin(point: Point) -> Bool { + point.x == 0 && point.y == 0 +} +``` diff --git a/docs/types/type-aliases.md b/docs/types/type-aliases.md new file mode 100644 index 0000000..e0cbd6b --- /dev/null +++ b/docs/types/type-aliases.md @@ -0,0 +1,18 @@ +--- +slug: /type-aliases +--- + +# Type Aliases + +A type alias is simply a way to define a named shorthand for a longer type. + +This is an example: + +```rue +type Point = (Int, Int); + +fun main() -> Int { + let point: Point = (42, 34); + point.first + point.rest +} +``` diff --git a/sidebars.ts b/sidebars.ts index d4e5585..0fcbc4e 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -6,6 +6,23 @@ const sidebars: SidebarsConfig = { type: "category", label: "Getting Started", items: ["getting-started/installation", "getting-started/hello-world"], + collapsed: false, + }, + { + type: "category", + label: "Definitions", + items: [ + "definitions/let-bindings", + "definitions/constants", + "definitions/functions", + ], + collapsed: false, + }, + { + type: "category", + label: "Types", + items: ["types/structs", "types/enums", "types/type-aliases"], + collapsed: false, }, ], }; diff --git a/src/theme/prism-rue.ts b/src/theme/prism-rue.ts index 2b29f40..d20f78e 100644 --- a/src/theme/prism-rue.ts +++ b/src/theme/prism-rue.ts @@ -8,7 +8,7 @@ Prism.languages.rue = { alias: "function", }, field: { - pattern: /\b[a-zA-Z_][a-zA-Z0-9_]*(?=\s*:)/, + pattern: /(?!\b(?:let|const))\b[a-zA-Z_][a-zA-Z0-9_]*(?=\s*:)/, alias: "property", }, "field-access": { @@ -18,8 +18,6 @@ Prism.languages.rue = { }, string: { pattern: /"[^"]*"/, - lookbehind: true, - greedy: true, }, comment: { pattern: /\/\/.*|\/\*[^]*?\*\//, @@ -53,4 +51,5 @@ Prism.languages.rue = { }, builtin: /\b(?:Int|Any|Bytes32|Bytes|PublicKey|Nil|Bool)\b/, "class-name": /\b[A-Z][a-z][a-zA-Z0-9_]*\b/, + constant: /\b[A-Z][A-Z0-9_]*\b/, };