diff --git a/Cargo.lock b/Cargo.lock index 9e1e170e..7c78a4ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1089,6 +1089,7 @@ dependencies = [ "num-traits", "numbat-exchange-rates", "once_cell", + "percent-encoding", "plotly", "pretty_dtoa", "rand", diff --git a/book/build.py b/book/build.py index 61ab370f..5c04fa99 100644 --- a/book/build.py +++ b/book/build.py @@ -1,6 +1,7 @@ import subprocess from pathlib import Path import urllib.parse +import os SCRIPT_DIR = Path(__file__).parent.resolve() @@ -15,14 +16,14 @@ def generate_example( print(path_out) code = [] - with open(path_in, "r") as fin: + with open(path_in, "r", encoding="utf-8") as fin: for line in fin: if not (strip_asserts and "assert_eq" in line): code.append(line) url = f"https://numbat.dev/?q={urllib.parse.quote_plus(''.join(code))}" - with open(path_out, "w") as fout: + with open(path_out, "w", encoding="utf-8") as fout: fout.write("\n") fout.write("\n") fout.write(f"# {title}\n") @@ -80,7 +81,7 @@ def xkcd_footer(number, img_name): ) path_units = SCRIPT_DIR / "src" / "list-units.md" -with open(path_units, "w") as f: +with open(path_units, "w", encoding="utf-8") as f: print("Generating list of units...", flush=True) subprocess.run( ["cargo", "run", "--release", "--quiet", "--example=inspect", "units"], @@ -91,7 +92,7 @@ def xkcd_footer(number, img_name): def list_of_functions(file_name, document): path = SCRIPT_DIR / "src" / f"list-functions-{file_name}.md" - with open(path, "w") as f: + with open(path, "w", encoding="utf-8") as f: print(f"# {document['title']}\n", file=f, flush=True) if introduction := document.get("introduction"): @@ -119,6 +120,8 @@ def list_of_functions(file_name, document): print( f"Generating list of functions for module '{module}'...", flush=True ) + env = os.environ.copy() + env["TZ"] = "UTC" subprocess.run( [ "cargo", @@ -132,6 +135,7 @@ def list_of_functions(file_name, document): ], stdout=f, text=True, + env=env, ) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 9a1d0e10..a66f2703 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -21,7 +21,6 @@ - [XKCD 687](./example-xkcd_687.md) - [XKCD 2585](./example-xkcd_2585.md) - [XKCD 2812](./example-xkcd_2812.md) -- [IDE / editor integration](./editor-integration.md) # Numbat language reference @@ -69,6 +68,11 @@ - [Type system](./type-system.md) +# Other topics + +- [IDE / editor integration](./editor-integration.md) +- [Comparison with other tools](./comparison.md) + # Support - [Contact us](./contact-us.md) diff --git a/book/src/comparison.md b/book/src/comparison.md new file mode 100644 index 00000000..a30fff27 --- /dev/null +++ b/book/src/comparison.md @@ -0,0 +1,50 @@ +# Comparison with other tools + +The following table provides a comparison of Numbat with other scientific calculators and programming languages. This comparison +is certainly *not* objective, as we only list criteria that we consider important. If you think that a tool or language is missing +or misrepresented, please [let us know](https://github.com/sharkdp/numbat/issues). + +| | Numbat | [Qalculate](https://qalculate.github.io/) | [Kalker](https://github.com/PaddiM8/kalker) | [GNU Units](https://www.gnu.org/software/units/) | [Frink](https://frinklang.org/) | [Wolfram Alpha](https://www.wolframalpha.com/) | +|----------------------------------------|-----------------|-----------|--------|-----------|-------|---------------| +| FOSS License | MIT, Apache-2.0 | GPL-2.0 | MIT | GPL-3.0 | ❌ | ❌ | +| **Interfaces** | | | | | | | +| Command-line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Web version | ✓ | ❌ | ✓ | ❌ | ❌ | ✓ | +| Graphical | ❌ | ✓ | ❌ | ❌ | (✓) | ✓ | +| **Units** | | | | | | | +| Comprehensive list of units | ✓ | ✓ | ❌ | ✓ | ✓ | ✓ | +| Custom units | ✓ | ✓ | ❌ | ✓ | ✓ | ❌ | +| Physical dimensions | ✓ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Currency conversions | ✓ | ✓ | ❌ | ❌ | ✓ | ✓ | +| Date and time calculations | ✓ | ✓ | ❌ | ❌ | ✓ | ✓ | +| **Language features** | | | | | | | +| Custom functions | ✓ | ✓ | ✓ | ❌ | ✓ | ❌ | +| Real programming language | ✓ | ❌ | ❌ | ❌ | ✓ | ? | +| Strongly typed | ✓ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Calculator features** | | | | | | | +| Symbolic calculations | ❌ | (✓) | ❌ | ❌ | (✓) | ✓ | +| Hex/Oct/Bin mode | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| Complex numbers | ❌ ([#180](https://github.com/sharkdp/numbat/issues/180)) | ✓ | ✓ | ❌ | ✓ | ✓ | +| Vectors, Matrices | ❌ | ✓ | ✓ | ❌ | ✓ | ✓ | + +## Detailed comparison + +- [Qalculate](https://qalculate.github.io/) is a fantastic calculator with a strong support for units and conversions. + If you don't need the full power of a programming language, Qalculate is probably more feature-complete than Numbat. +- [Frink](https://frinklang.org/) is a special-purpose programming language with a focus on scientific calculations + and units of measurement. The language is probably more powerful than Numbat, but lacks a static type system. It's also + a imperative/OOP language, while Numbat is a functional/declarative language. Frink is not open-source. +- [GNU Units](https://www.gnu.org/software/units/) is probably the most comprehensive tool in terms of pre-defined units. + Numbat makes it very easy to define [custom units](./unit-definitions.md). If you think that a unit should be part + of the standard library, please [let us know](https://github.com/sharkdp/numbat/issues). +- [Wolfram Alpha](https://www.wolframalpha.com/) is a very powerful tool, but it's focused on single-line queries instead + of longer computations. The query language lacks a strict syntax (which some might consider a feature). The tool is not + open source and sometimes has limitations with respect to the number/size of queries you can make. + +## Other interesting tools / languages + +- [F#](https://fsharp.org/) is the only programming language that we know of that comes close in terms of having an + expressive type system that is based on units of measure. In fact, Numbats type system is heavily inspired by F#, + except that it uses physical dimensions instead of physical units on the type level. Both languages have feature + full [type inference](./function-definitions.md#type-inference). F# is not listed above, as it's not really suitable + as a scientific calculator. diff --git a/book/src/date-and-time.md b/book/src/date-and-time.md index 1c622699..d157034f 100644 --- a/book/src/date-and-time.md +++ b/book/src/date-and-time.md @@ -30,7 +30,7 @@ now() -> unixtime # What is the date corresponding to a given UNIX timestamp? from_unixtime(1707568901) -# How long are one million seconds in years, months, days, hours, minutes, seconds +# How long are one million seconds in years, months, days, hours, minutes, seconds? 1 million seconds -> human ``` diff --git a/book/src/list-functions-datetime.md b/book/src/list-functions-datetime.md index ede5544f..8eaa2fdc 100644 --- a/book/src/list-functions-datetime.md +++ b/book/src/list-functions-datetime.md @@ -18,6 +18,26 @@ Parses a string (date and time) into a `DateTime` object. See [here](./date-and- fn datetime(input: String) -> DateTime ``` +
+Examples + +
>>> datetime("2022-07-20T21:52+0200") + + = 2022-07-20 19:52:00 UTC [DateTime] +
+ +
>>> datetime("2022-07-20 21:52 Europe/Berlin") + + = 2022-07-20 21:52:00 CEST (UTC +02), Europe/Berlin [DateTime] +
+ +
>>> datetime("2022/07/20 09:52 PM +0200") + + = 2022-07-20 21:52:00 (UTC +02) [DateTime] +
+ +
+ ### `format_datetime` Formats a `DateTime` object as a string. @@ -25,6 +45,16 @@ Formats a `DateTime` object as a string. fn format_datetime(format: String, input: DateTime) -> String ``` +
+Examples + +
>>> format_datetime("This is a date in %B in the year %Y.", datetime("2022-07-20 21:52 +0200")) + + = "This is a date in July in the year 2022." [String] +
+ +
+ ### `get_local_timezone` Returns the users local timezone. @@ -32,6 +62,16 @@ Returns the users local timezone. fn get_local_timezone() -> String ``` +
+Examples + +
>>> get_local_timezone() + + = "UTC" [String] +
+ +
+ ### `tz` Returns a timezone conversion function, typically used with the conversion operator. @@ -39,6 +79,21 @@ Returns a timezone conversion function, typically used with the conversion opera fn tz(tz: String) -> Fn[(DateTime) -> DateTime] ``` +
+Examples + +
>>> datetime("2022-07-20 21:52 +0200") -> tz("Europe/Amsterdam") + + = 2022-07-20 21:52:00 CEST (UTC +02), Europe/Amsterdam [DateTime] +
+ +
>>> datetime("2022-07-20 21:52 +0200") -> tz("Asia/Taipei") + + = 2022-07-21 03:52:00 CST (UTC +08), Asia/Taipei [DateTime] +
+ +
+ ### `unixtime` Converts a `DateTime` to a UNIX timestamp. Can be used on the right hand side of a conversion operator: `now() -> unixtime`. @@ -46,6 +101,16 @@ Converts a `DateTime` to a UNIX timestamp. Can be used on the right hand side of fn unixtime(input: DateTime) -> Scalar ``` +
+Examples + +
>>> datetime("2022-07-20 21:52 +0200") -> unixtime + + = 1_658_346_720 +
+ +
+ ### `from_unixtime` Converts a UNIX timestamp to a `DateTime` object. @@ -53,6 +118,16 @@ Converts a UNIX timestamp to a `DateTime` object. fn from_unixtime(input: Scalar) -> DateTime ``` +
+Examples + +
>>> from_unixtime(2^31) + + = 2038-01-19 03:14:08 UTC [DateTime] +
+ +
+ ### `today` Returns the current date at midnight (in the local time). @@ -67,6 +142,16 @@ Parses a string (only date) into a `DateTime` object. fn date(input: String) -> DateTime ``` +
+Examples + +
>>> date("2022-07-20") + + = 2022-07-20 00:00:00 UTC [DateTime] +
+ +
+ ### `time` Parses a string (time only) into a `DateTime` object. @@ -81,6 +166,16 @@ Adds the given time span to a `DateTime`. This uses leap-year and DST-aware cale fn calendar_add(dt: DateTime, span: Time) -> DateTime ``` +
+Examples + +
>>> calendar_add(datetime("2022-07-20 21:52 +0200"), 2 years) + + = 2024-07-20 21:52:00 (UTC +02) [DateTime] +
+ +
+ ### `calendar_sub` Subtract the given time span from a `DateTime`. This uses leap-year and DST-aware calendar arithmetic with variable-length days, months, and years. @@ -88,6 +183,16 @@ Subtract the given time span from a `DateTime`. This uses leap-year and DST-awar fn calendar_sub(dt: DateTime, span: Time) -> DateTime ``` +
+Examples + +
>>> calendar_sub(datetime("2022-07-20 21:52 +0200"), 3 years) + + = 2019-07-20 21:52:00 (UTC +02) [DateTime] +
+ +
+ ### `weekday` Get the day of the week from a given `DateTime`. @@ -95,6 +200,16 @@ Get the day of the week from a given `DateTime`. fn weekday(dt: DateTime) -> String ``` +
+Examples + +
>>> weekday(datetime("2022-07-20 21:52 +0200")) + + = "Wednesday" [String] +
+ +
+ ### `julian_date` (Julian date) Convert a `DateTime` to a Julian date, the number of days since the origin of the Julian date system (noon on November 24, 4714 BC in the proleptic Gregorian calendar). More information [here](https://en.wikipedia.org/wiki/Julian_day). @@ -103,6 +218,16 @@ More information [here](https://en.wikipedia.org/wiki/Julian_day). fn julian_date(dt: DateTime) -> Time ``` +
+Examples + +
>>> julian_date(datetime("2022-07-20 21:52 +0200")) + + = 2.45978e+6 day [Time] +
+ +
+ ### `human` (Human-readable time duration) Converts a time duration to a human-readable string in days, hours, minutes and seconds. More information [here](https://numbat.dev/doc/date-and-time.html). @@ -111,3 +236,14 @@ More information [here](https://numbat.dev/doc/date-and-time.html). fn human(time: Time) -> String ``` +
+Examples + +How long is a microcentury? +
>>> century/1e6 -> human + + = "52 minutes + 35.693 seconds" [String] +
+ +
+ diff --git a/book/src/list-functions-lists.md b/book/src/list-functions-lists.md index 10723fa0..c3919ea0 100644 --- a/book/src/list-functions-lists.md +++ b/book/src/list-functions-lists.md @@ -9,6 +9,16 @@ Get the length of a list. fn len(xs: List) -> Scalar ``` +
+Examples + +
>>> len([3, 2, 1]) + + = 3 +
+ +
+ ### `head` Get the first element of a list. Yields a runtime error if the list is empty. @@ -16,6 +26,16 @@ Get the first element of a list. Yields a runtime error if the list is empty. fn head
(xs: List) -> A ``` +
+Examples + +
>>> head([3, 2, 1]) + + = 3 +
+ +
+ ### `tail` Get everything but the first element of a list. Yields a runtime error if the list is empty. @@ -23,6 +43,16 @@ Get everything but the first element of a list. Yields a runtime error if the li fn tail
(xs: List) -> List ``` +
+Examples + +
>>> tail([3, 2, 1]) + + = [2, 1] [List] +
+ +
+ ### `cons` Prepend an element to a list. @@ -30,6 +60,16 @@ Prepend an element to a list. fn cons
(x: A, xs: List) -> List ``` +
+Examples + +
>>> cons(77, [3, 2, 1]) + + = [77, 3, 2, 1] [List] +
+ +
+ ### `cons_end` Append an element to the end of a list. @@ -37,6 +77,16 @@ Append an element to the end of a list. fn cons_end
(x: A, xs: List) -> List ``` +
+Examples + +
>>> cons_end(77, [3, 2, 1]) + + = [3, 2, 1, 77] [List] +
+ +
+ ### `is_empty` Check if a list is empty. @@ -44,6 +94,21 @@ Check if a list is empty. fn is_empty
(xs: List) -> Bool ``` +
+Examples + +
>>> is_empty([3, 2, 1]) + + = false [Bool] +
+ +
>>> is_empty([]) + + = true [Bool] +
+ +
+ ### `concat` Concatenate two lists. @@ -51,6 +116,16 @@ Concatenate two lists. fn concat
(xs1: List, xs2: List) -> List ``` +
+Examples + +
>>> concat([3, 2, 1], [10, 11]) + + = [3, 2, 1, 10, 11] [List] +
+ +
+ ### `take` Get the first `n` elements of a list. @@ -58,6 +133,16 @@ Get the first `n` elements of a list. fn take
(n: Scalar, xs: List) -> List ``` +
+Examples + +
>>> take(2, [3, 2, 1, 0]) + + = [3, 2] [List] +
+ +
+ ### `drop` Get everything but the first `n` elements of a list. @@ -65,6 +150,16 @@ Get everything but the first `n` elements of a list. fn drop
(n: Scalar, xs: List) -> List ``` +
+Examples + +
>>> drop(2, [3, 2, 1, 0]) + + = [1, 0] [List] +
+ +
+ ### `element_at` Get the element at index `i` in a list. @@ -72,6 +167,16 @@ Get the element at index `i` in a list. fn element_at
(i: Scalar, xs: List) -> A ``` +
+Examples + +
>>> element_at(2, [3, 2, 1, 0]) + + = 1 +
+ +
+ ### `range` Generate a range of integer numbers from `start` to `end` (inclusive). @@ -79,6 +184,16 @@ Generate a range of integer numbers from `start` to `end` (inclusive). fn range(start: Scalar, end: Scalar) -> List ``` +
+Examples + +
>>> range(2, 12) + + = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] [List] +
+ +
+ ### `reverse` Reverse the order of a list. @@ -86,6 +201,16 @@ Reverse the order of a list. fn reverse
(xs: List) -> List ``` +
+Examples + +
>>> reverse([3, 2, 1]) + + = [1, 2, 3] [List] +
+ +
+ ### `map` Generate a new list by applying a function to each element of the input list. @@ -93,6 +218,17 @@ Generate a new list by applying a function to each element of the input list. fn map(f: Fn[(A) -> B], xs: List
) -> List ``` +
+Examples + +Square all elements of a list. +
>>> map(sqr, [3, 2, 1]) + + = [9, 4, 1] [List] +
+ +
+ ### `filter` Filter a list by a predicate. @@ -100,6 +236,16 @@ Filter a list by a predicate. fn filter
(p: Fn[(A) -> Bool], xs: List) -> List ``` +
+Examples + +
>>> filter(is_finite, [0, 1e10, NaN, -inf]) + + = [0, 10_000_000_000] [List] +
+ +
+ ### `foldl` Fold a function over a list. @@ -107,6 +253,17 @@ Fold a function over a list. fn foldl(f: Fn[(A, B) -> A], acc: A, xs: List) -> A ``` +
+Examples + +Join a list of strings by folding. +
>>> foldl(str_append, "", ["Num", "bat", "!"]) + + = "Numbat!" [String] +
+ +
+ ### `sort_by_key` Sort a list of elements, using the given key function that maps the element to a quantity. @@ -114,6 +271,18 @@ Sort a list of elements, using the given key function that maps the element to a fn sort_by_key(key: Fn[(A) -> D], xs: List
) -> List ``` +
+Examples + +Sort by last digit. +
>>> fn last_digit(x) = mod(x, 10) +sort_by_key(last_digit, [701, 313, 9999, 4]) + + = [701, 313, 4, 9999] [List] +
+ +
+ ### `sort` Sort a list of quantities. @@ -121,6 +290,16 @@ Sort a list of quantities. fn sort(xs: List) -> List ``` +
+Examples + +
>>> sort([3, 2, 7, 8, -4, 0, -5]) + + = [-5, -4, 0, 2, 3, 7, 8] [List] +
+ +
+ ### `intersperse` Add an element between each pair of elements in a list. @@ -128,6 +307,16 @@ Add an element between each pair of elements in a list. fn intersperse
(sep: A, xs: List) -> List ``` +
+Examples + +
>>> intersperse(0, [1, 1, 1, 1]) + + = [1, 0, 1, 0, 1, 0, 1] [List] +
+ +
+ ### `sum` Sum all elements of a list. @@ -135,6 +324,16 @@ Sum all elements of a list. fn sum(xs: List) -> D ``` +
+Examples + +
>>> sum([3 m, 200 cm, 1000 mm]) + + = 6 m [Length] +
+ +
+ ### `linspace` Generate a list of `n_steps` evenly spaced numbers from `start` to `end` (inclusive). @@ -142,6 +341,16 @@ Generate a list of `n_steps` evenly spaced numbers from `start` to `end` (inclus fn linspace(start: D, end: D, n_steps: Scalar) -> List ``` +
+Examples + +
>>> linspace(-5 m, 5 m, 11) + + = [-5 m, -4 m, -3 m, -2 m, -1 m, 0 m, 1 m, 2 m, 3 m, 4 m, 5 m] [List] +
+ +
+ ### `join` Convert a list of strings into a single string by concatenating them with a separator. @@ -149,6 +358,16 @@ Convert a list of strings into a single string by concatenating them with a sepa fn join(xs: List, sep: String) -> String ``` +
+Examples + +
>>> join(["snake", "case"], "_") + + = "snake_case" [String] +
+ +
+ ### `split` Split a string into a list of strings using a separator. @@ -156,3 +375,13 @@ Split a string into a list of strings using a separator. fn split(input: String, separator: String) -> List ``` +
+Examples + +
>>> split("Numbat is a statically typed programming language.", " ") + + = ["Numbat", "is", "a", "statically", "typed", "programming", "language."] [List] +
+ +
+ diff --git a/book/src/list-functions-math.md b/book/src/list-functions-math.md index 07d7d79f..53cb49c2 100644 --- a/book/src/list-functions-math.md +++ b/book/src/list-functions-math.md @@ -13,6 +13,16 @@ Return the input value. fn id
(x: A) -> A ``` +
+Examples + +
>>> id(8 kg) + + = 8 kg [Mass] +
+ +
+ ### `abs` (Absolute value) Return the absolute value \\( |x| \\) of the input. This works for quantities, too: `abs(-5 m) = 5 m`. More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.abs). @@ -21,6 +31,16 @@ More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method. fn abs(x: T) -> T ``` +
+Examples + +
>>> abs(-22.2 m) + + = 22.2 m [Length] +
+ +
+ ### `sqrt` (Square root) Return the square root \\( \sqrt{x} \\) of the input: `sqrt(121 m^2) = 11 m`. More information [here](https://en.wikipedia.org/wiki/Square_root). @@ -29,6 +49,16 @@ More information [here](https://en.wikipedia.org/wiki/Square_root). fn sqrt(x: D^2) -> D ``` +
+Examples + +
>>> sqrt(4 are) -> m + + = 20 m [Length] +
+ +
+ ### `cbrt` (Cube root) Return the cube root \\( \sqrt[3]{x} \\) of the input: `cbrt(8 m^3) = 2 m`. More information [here](https://en.wikipedia.org/wiki/Cube_root). @@ -37,6 +67,16 @@ More information [here](https://en.wikipedia.org/wiki/Cube_root). fn cbrt(x: D^3) -> D ``` +
+Examples + +
>>> cbrt(8 L) -> cm + + = 20.0 cm [Length] +
+ +
+ ### `sqr` (Square function) Return the square of the input, \\( x^2 \\): `sqr(5 m) = 25 m^2`. @@ -44,6 +84,16 @@ Return the square of the input, \\( x^2 \\): `sqr(5 m) = 25 m^2`. fn sqr(x: D) -> D^2 ``` +
+Examples + +
>>> sqr(7) + + = 49 +
+ +
+ ### `round` (Rounding) Round to the nearest integer. If the value is half-way between two integers, round away from \\( 0 \\). See also: `round_in`. More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.round). @@ -52,13 +102,45 @@ More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method. fn round(x: Scalar) -> Scalar ``` +
+Examples + +
>>> round(5.5) + + = 6 +
+ +
>>> round(-5.5) + + = -6 +
+ +
+ ### `round_in` (Rounding) -Round to the nearest multiple of `base`. For example: `round_in(m, 5.3 m) == 5 m`. +Round to the nearest multiple of `base`. ```nbt fn round_in(base: D, value: D) -> D ``` +
+Examples + +Round in meters. +
>>> round_in(m, 5.3 m) + + = 5 m [Length] +
+ +Round in centimeters. +
>>> round_in(cm, 5.3 m) + + = 530 cm [Length] +
+ +
+ ### `floor` (Floor function) Returns the largest integer less than or equal to \\( x \\). See also: `floor_in`. More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.floor). @@ -67,13 +149,40 @@ More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method. fn floor(x: Scalar) -> Scalar ``` +
+Examples + +
>>> floor(5.5) + + = 5 +
+ +
+ ### `floor_in` (Floor function) -Returns the largest integer multiple of `base` less than or equal to `value`. For example: `floor_in(m, 5.7 m) == 5 m`. +Returns the largest integer multiple of `base` less than or equal to `value`. ```nbt fn floor_in(base: D, value: D) -> D ``` +
+Examples + +Floor in meters. +
>>> floor_in(m, 5.7 m) + + = 5 m [Length] +
+ +Floor in centimeters. +
>>> floor_in(cm, 5.7 m) + + = 570 cm [Length] +
+ +
+ ### `ceil` (Ceil function) Returns the smallest integer greater than or equal to \\( x \\). See also: `ceil_in`. More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.ceil). @@ -82,13 +191,40 @@ More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method. fn ceil(x: Scalar) -> Scalar ``` +
+Examples + +
>>> ceil(5.5) + + = 6 +
+ +
+ ### `ceil_in` (Ceil function) -Returns the smallest integer multuple of `base` greater than or equal to `value`. For example: `ceil_in(m, 5.3 m) == 6 m`. +Returns the smallest integer multiple of `base` greater than or equal to `value`. ```nbt fn ceil_in(base: D, value: D) -> D ``` +
+Examples + +Ceil in meters. +
>>> ceil_in(m, 5.3 m) + + = 6 m [Length] +
+ +Ceil in centimeters. +
>>> ceil_in(cm, 5.3 m) + + = 530 cm [Length] +
+ +
+ ### `trunc` (Truncation) Returns the integer part of \\( x \\). Non-integer numbers are always truncated towards zero. See also: `trunc_in`. More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.trunc). @@ -97,13 +233,45 @@ More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method. fn trunc(x: Scalar) -> Scalar ``` +
+Examples + +
>>> trunc(5.5) + + = 5 +
+ +
>>> trunc(-5.5) + + = -5 +
+ +
+ ### `trunc_in` (Truncation) -Truncates to an integer multiple of `base` (towards zero). For example: `trunc_in(m, -5.7 m) == -5 m`. +Truncates to an integer multiple of `base` (towards zero). ```nbt fn trunc_in(base: D, value: D) -> D ``` +
+Examples + +Truncate in meters. +
>>> trunc_in(m, 5.7 m) + + = 5 m [Length] +
+ +Truncate in centimeters. +
>>> trunc_in(cm, 5.7 m) + + = 570 cm [Length] +
+ +
+ ### `mod` (Modulo) Calculates the least nonnegative remainder of \\( a (\mod b) \\). More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.rem_euclid). @@ -112,6 +280,16 @@ More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method. fn mod(a: T, b: T) -> T ``` +
+Examples + +
>>> mod(27, 5) + + = 2 +
+ +
+ ## Transcendental functions Defined in: `math::transcendental` @@ -124,6 +302,16 @@ More information [here](https://en.wikipedia.org/wiki/Exponential_function). fn exp(x: Scalar) -> Scalar ``` +
+Examples + +
>>> exp(4) + + = 54.5982 +
+ +
+ ### `ln` (Natural logarithm) The natural logarithm with base \\( e \\). More information [here](https://en.wikipedia.org/wiki/Natural_logarithm). @@ -132,6 +320,16 @@ More information [here](https://en.wikipedia.org/wiki/Natural_logarithm). fn ln(x: Scalar) -> Scalar ``` +
+Examples + +
>>> ln(20) + + = 2.99573 +
+ +
+ ### `log` (Natural logarithm) The natural logarithm with base \\( e \\). More information [here](https://en.wikipedia.org/wiki/Natural_logarithm). @@ -140,6 +338,16 @@ More information [here](https://en.wikipedia.org/wiki/Natural_logarithm). fn log(x: Scalar) -> Scalar ``` +
+Examples + +
>>> log(20) + + = 2.99573 +
+ +
+ ### `log10` (Common logarithm) The common logarithm with base \\( 10 \\). More information [here](https://en.wikipedia.org/wiki/Common_logarithm). @@ -148,6 +356,16 @@ More information [here](https://en.wikipedia.org/wiki/Common_logarithm). fn log10(x: Scalar) -> Scalar ``` +
+Examples + +
>>> log10(100) + + = 2 +
+ +
+ ### `log2` (Binary logarithm) The binary logarithm with base \\( 2 \\). More information [here](https://en.wikipedia.org/wiki/Binary_logarithm). @@ -156,6 +374,16 @@ More information [here](https://en.wikipedia.org/wiki/Binary_logarithm). fn log2(x: Scalar) -> Scalar ``` +
+Examples + +
>>> log2(256) + + = 8 +
+ +
+ ### `gamma` (Gamma function) The gamma function, \\( \Gamma(x) \\). More information [here](https://en.wikipedia.org/wiki/Gamma_function). @@ -264,27 +492,57 @@ fn atanh(x: Scalar) -> Scalar Defined in: `math::statistics` ### `maximum` (Maxmimum) -Get the largest element of a list: `maximum([30 cm, 2 m]) = 2 m`. +Get the largest element of a list. ```nbt fn maximum(xs: List) -> D ``` +
+Examples + +
>>> maximum([30 cm, 2 m]) + + = 2 m [Length] +
+ +
+ ### `minimum` (Minimum) -Get the smallest element of a list: `minimum([30 cm, 2 m]) = 30 cm`. +Get the smallest element of a list. ```nbt fn minimum(xs: List) -> D ``` +
+Examples + +
>>> minimum([30 cm, 2 m]) + + = 30 cm [Length] +
+ +
+ ### `mean` (Arithmetic mean) -Calculate the arithmetic mean of a list of quantities: `mean([1 m, 2 m, 300 cm]) = 2 m`. +Calculate the arithmetic mean of a list of quantities. More information [here](https://en.wikipedia.org/wiki/Arithmetic_mean). ```nbt fn mean(xs: List) -> D ``` +
+Examples + +
>>> mean([1 m, 2 m, 300 cm]) + + = 2 m [Length] +
+ +
+ ### `variance` (Variance) Calculate the population variance of a list of quantities. More information [here](https://en.wikipedia.org/wiki/Variance). @@ -293,6 +551,16 @@ More information [here](https://en.wikipedia.org/wiki/Variance). fn variance(xs: List) -> D^2 ``` +
+Examples + +
>>> variance([1 m, 2 m, 300 cm]) + + = 0.666667 m² [Area] +
+ +
+ ### `stdev` (Standard deviation) Calculate the population standard deviation of a list of quantities. More information [here](https://en.wikipedia.org/wiki/Standard_deviation). @@ -301,6 +569,16 @@ More information [here](https://en.wikipedia.org/wiki/Standard_deviation). fn stdev(xs: List) -> D ``` +
+Examples + +
>>> stdev([1 m, 2 m, 300 cm]) + + = 0.816497 m [Length] +
+ +
+ ### `median` (Median) Calculate the median of a list of quantities. More information [here](https://en.wikipedia.org/wiki/Median). @@ -309,6 +587,16 @@ More information [here](https://en.wikipedia.org/wiki/Median). fn median(xs: List) -> D ``` +
+Examples + +
>>> median([1 m, 2 m, 400 cm]) + + = 2 m [Length] +
+ +
+ ## Random sampling, distributions Defined in: `core::random`, `math::distributions` @@ -413,6 +701,16 @@ More information [here](https://en.wikipedia.org/wiki/Greatest_common_divisor). fn gcd(a: Scalar, b: Scalar) -> Scalar ``` +
+Examples + +
>>> gcd(60, 42) + + = 6 +
+ +
+ ### `lcm` (Least common multiple) The smallest positive integer that is divisible by both \\( a \\) and \\( b \\). More information [here](https://en.wikipedia.org/wiki/Least_common_multiple). @@ -421,6 +719,16 @@ More information [here](https://en.wikipedia.org/wiki/Least_common_multiple). fn lcm(a: Scalar, b: Scalar) -> Scalar ``` +
+Examples + +
>>> lcm(14, 4) + + = 28 +
+ +
+ ## Numerical methods Defined in: `numerics::diff`, `numerics::solve`, `numerics::fixed_point` @@ -433,6 +741,28 @@ More information [here](https://en.wikipedia.org/wiki/Numerical_differentiation) fn diff(f: Fn[(X) -> Y], x: X) -> Y / X ``` +
+Examples + +Compute the derivative of \\( f(x) = x² -x -1 \\) at \\( x=1 \\). +
>>> use numerics::diff +fn polynomial(x) = x² - x - 1 +diff(polynomial, 1) + + = 1.0 +
+ +Compute the free fall velocity after \\( t=2 s \\). +
>>> use numerics::diff +fn distance(t) = 0.5 g0 t² +fn velocity(t) = diff(distance, t) +velocity(2 s) + + = 19.6133 m/s [Velocity] +
+ +
+ ### `root_bisect` (Bisection method) Find the root of the function \\( f \\) in the interval \\( [x_1, x_2] \\) using the bisection method. The function \\( f \\) must be continuous and \\( f(x_1) \cdot f(x_2) < 0 \\). More information [here](https://en.wikipedia.org/wiki/Bisection_method). @@ -441,6 +771,19 @@ More information [here](https://en.wikipedia.org/wiki/Bisection_method). fn root_bisect(f: Fn[(X) -> Y], x1: X, x2: X, x_tol: X, y_tol: Y) -> X ``` +
+Examples + +Find the root of \\( f(x) = x² +x -2 \\) in the interval \\( [0, 100] \\). +
>>> use numerics::solve +fn f(x) = x² +x -2 +root_bisect(f, 0, 100, 0.01, 0.01) + + = 1.00098 +
+ +
+ ### `root_newton` (Newton's method) Find the root of the function \\( f(x) \\) and its derivative \\( f'(x) \\) using Newton's method. More information [here](https://en.wikipedia.org/wiki/Newton%27s_method). @@ -449,6 +792,20 @@ More information [here](https://en.wikipedia.org/wiki/Newton%27s_method). fn root_newton(f: Fn[(X) -> Y], f_prime: Fn[(X) -> Y / X], x0: X, y_tol: Y) -> X ``` +
+Examples + +Find a root of \\( f(x) = x² -3x +2 \\) using Newton's method. +
>>> use numerics::solve +fn f(x) = x² -3x +2 +fn f_prime(x) = 2x -3 +root_newton(f, f_prime, 0 , 0.01) + + = 0.996078 +
+ +
+ ### `fixed_point` (Fixed-point iteration) Compute the approximate fixed point of a function \\( f: X \rightarrow X \\) starting from \\( x_0 \\), until \\( |f(x) - x| < ε \\). More information [here](https://en.wikipedia.org/wiki/Fixed-point_iteration). @@ -457,6 +814,19 @@ More information [here](https://en.wikipedia.org/wiki/Fixed-point_iteration). fn fixed_point(f: Fn[(X) -> X], x0: X, ε: X) -> X ``` +
+Examples + +Compute the fixed poin of \\( f(x) = x/2 -1 \\). +
>>> use numerics::fixed_point +fn function(x) = x/2 - 1 +fixed_point(function, 0, 0.01) + + = -1.99219 +
+ +
+ ## Geometry Defined in: `math::geometry` @@ -468,6 +838,16 @@ The length of the hypotenuse of a right-angled triangle \\( \sqrt{x^2+y^2} \\). fn hypot2(x: T, y: T) -> T ``` +
+Examples + +
>>> hypot2(3 m, 4 m) + + = 5 m [Length] +
+ +
+ ### `hypot3` The Euclidean norm of a 3D vector \\( \sqrt{x^2+y^2+z^2} \\). @@ -475,6 +855,16 @@ The Euclidean norm of a 3D vector \\( \sqrt{x^2+y^2+z^2} \\). fn hypot3(x: T, y: T, z: T) -> T ``` +
+Examples + +
>>> hypot3(8, 9, 12) + + = 17 +
+ +
+ ### `circle_area` The area of a circle, \\( \pi r^2 \\). @@ -515,6 +905,18 @@ More information [here](https://en.wikipedia.org/wiki/Quadratic_equation). fn quadratic_equation(a: A, b: B, c: B^2 / A) -> List ``` +
+Examples + +Solve the equation \\( 2x² -x -1 = 0 \\) +
>>> use extra::algebra +quadratic_equation(2, -1, -1) + + = [1, -0.5] [List] +
+ +
+ ## Trigonometry (extra) Defined in: `math::trigonometry_extra` diff --git a/book/src/list-functions-other.md b/book/src/list-functions-other.md index a8a97830..179f4360 100644 --- a/book/src/list-functions-other.md +++ b/book/src/list-functions-other.md @@ -25,6 +25,21 @@ More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method. fn is_nan(n: T) -> Bool ``` +
+Examples + +
>>> is_nan(37) + + = false [Bool] +
+ +
>>> is_nan(NaN) + + = true [Bool] +
+ +
+ ### `is_infinite` Returns true if the input is positive infinity or negative infinity. More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.is_infinite). @@ -33,6 +48,21 @@ More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method. fn is_infinite(n: T) -> Bool ``` +
+Examples + +
>>> is_infinite(37) + + = false [Bool] +
+ +
>>> is_infinite(-inf) + + = true [Bool] +
+ +
+ ### `is_finite` Returns true if the input is neither infinite nor `NaN`. @@ -40,6 +70,21 @@ Returns true if the input is neither infinite nor `NaN`. fn is_finite(n: T) -> Bool ``` +
+Examples + +
>>> is_finite(37) + + = true [Bool] +
+ +
>>> is_finite(-inf) + + = false [Bool] +
+ +
+ ## Quantities Defined in: `core::quantities` @@ -51,6 +96,16 @@ Extract the unit of a quantity (the `km/h` in `20 km/h`). This can be useful in fn unit_of(x: T) -> T ``` +
+Examples + +
>>> unit_of(20 km/h) + + = 1 km/h [Velocity] +
+ +
+ ### `value_of` Extract the plain value of a quantity (the `20` in `20 km/h`). This can be useful in generic code, but should generally be avoided otherwise. @@ -58,53 +113,137 @@ Extract the plain value of a quantity (the `20` in `20 km/h`). This can be usefu fn value_of(x: T) -> Scalar ``` +
+Examples + +
>>> value_of(20 km/h) + + = 20 +
+ +
+ ## Chemical elements Defined in: `chemistry::elements` ### `element` (Chemical element) -Get properties of a chemical element by its symbol or name (case-insensitive). For example: `element("H")` or `element("hydrogen")`. +Get properties of a chemical element by its symbol or name (case-insensitive). ```nbt fn element(pattern: String) -> ChemicalElement ``` +
+Examples + +Get the entire element struct for hydrogen. +
>>> element("H") + + = ChemicalElement { symbol: "H", name: "Hydrogen", atomic_number: 1, group: 1, group_name: "Alkali metals", period: 1, melting_point: 13.99 K, boiling_point: 20.271 K, density: 0.00008988 g/cm³, electron_affinity: 0.754 eV, ionization_energy: 13.598 eV, vaporization_heat: 0.904 kJ/mol } [ChemicalElement] +
+ +Get the ionization energy of hydrogen. +
>>> element("hydrogen").ionization_energy + + = 13.598 eV [Energy or Torque] +
+ +
+ ## Mixed unit conversion Defined in: `units::mixed` +### `unit_list` (Unit list) +Convert a value to a mixed representation using the provided units. + +```nbt +fn unit_list(units: List, value: D) -> List +``` + +
+Examples + +
>>> 5500 m |> unit_list([miles, yards, feet, inches]) + + = [3 mi, 734 yd, 2 ft, 7.43307 in] [List] +
+ +
+ ### `DMS` (Degrees, minutes, seconds) Convert an angle to a mixed degrees, (arc)minutes, and (arc)seconds representation. Also called sexagesimal degree notation. More information [here](https://en.wikipedia.org/wiki/Sexagesimal_degree). ```nbt -fn DMS(alpha: Angle) -> String +fn DMS(alpha: Angle) -> List ``` +
+Examples + +
>>> 46.5858° -> DMS + + = [46°, 35′, 8.88″] [List] +
+ +
+ ### `DM` (Degrees, decimal minutes) Convert an angle to a mixed degrees and decimal minutes representation. More information [here](https://en.wikipedia.org/wiki/Decimal_degrees). ```nbt -fn DM(alpha: Angle) -> String +fn DM(alpha: Angle) -> List ``` +
+Examples + +
>>> 46.5858° -> DM + + = [46°, 35.148′] [List] +
+ +
+ ### `feet_and_inches` (Feet and inches) Convert a length to a mixed feet and inches representation. More information [here](https://en.wikipedia.org/wiki/Foot_(unit)). ```nbt -fn feet_and_inches(length: Length) -> String +fn feet_and_inches(length: Length) -> List ``` +
+Examples + +
>>> 180 cm -> feet_and_inches + + = [5 ft, 10.8661 in] [List] +
+ +
+ ### `pounds_and_ounces` (Pounds and ounces) Convert a mass to a mixed pounds and ounces representation. More information [here](https://en.wikipedia.org/wiki/Pound_(mass)). ```nbt -fn pounds_and_ounces(mass: Mass) -> String +fn pounds_and_ounces(mass: Mass) -> List ``` +
+Examples + +
>>> 1 kg -> pounds_and_ounces + + = [2 lb, 3.27396 oz] [List] +
+ +
+ ## Temperature conversion Defined in: `physics::temperature_conversion` @@ -117,6 +256,17 @@ More information [here](https://en.wikipedia.org/wiki/Conversion_of_scales_of_te fn from_celsius(t_celsius: Scalar) -> Temperature ``` +
+Examples + +300 °C in Kelvin. +
>>> from_celsius(300) + + = 573.15 K [Temperature] +
+ +
+ ### `celsius` Converts from Kelvin to degree Celcius (°C). This can be used on the right hand side of a conversion operator: `200 K -> celsius`. More information [here](https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature). @@ -125,6 +275,17 @@ More information [here](https://en.wikipedia.org/wiki/Conversion_of_scales_of_te fn celsius(t_kelvin: Temperature) -> Scalar ``` +
+Examples + +300 K in degree Celsius. +
>>> 300K -> celsius + + = 26.85 +
+ +
+ ### `from_fahrenheit` Converts from degree Fahrenheit (°F) to Kelvin. More information [here](https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature). @@ -133,6 +294,17 @@ More information [here](https://en.wikipedia.org/wiki/Conversion_of_scales_of_te fn from_fahrenheit(t_fahrenheit: Scalar) -> Temperature ``` +
+Examples + +300 °F in Kelvin. +
>>> from_fahrenheit(300) + + = 422.039 K [Temperature] +
+ +
+ ### `fahrenheit` Converts from Kelvin to degree Fahrenheit (°F). This can be used on the right hand side of a conversion operator: `200 K -> fahrenheit`. More information [here](https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature). @@ -141,6 +313,17 @@ More information [here](https://en.wikipedia.org/wiki/Conversion_of_scales_of_te fn fahrenheit(t_kelvin: Temperature) -> Scalar ``` +
+Examples + +300 K in degree Fahrenheit. +
>>> 300K -> fahrenheit + + = 80.33 +
+ +
+ ## Color format conversion Defined in: `extra::color` @@ -152,31 +335,86 @@ Create a `Color` from RGB (red, green, blue) values in the range \\( [0, 256) \\ fn rgb(red: Scalar, green: Scalar, blue: Scalar) -> Color ``` +
+Examples + +
>>> use extra::color +rgb(125, 128, 218) + + = Color { red: 125, green: 128, blue: 218 } [Color] +
+ +
+ ### `color` -Create a `Color` from a (hexadecimal) value, e.g. `color(0xff7700)`. +Create a `Color` from a (hexadecimal) value. ```nbt fn color(rgb_hex: Scalar) -> Color ``` +
+Examples + +
>>> use extra::color +color(0xff7700) + + = Color { red: 255, green: 119, blue: 0 } [Color] +
+ +
+ ### `color_rgb` -Convert a color to its RGB representation, e.g. `cyan -> color_rgb`. +Convert a color to its RGB representation. ```nbt fn color_rgb(color: Color) -> String ``` +
+Examples + +
>>> use extra::color +cyan -> color_rgb + + = "rgb(0, 255, 255)" [String] +
+ +
+ ### `color_rgb_float` -Convert a color to its RGB floating point representation, e.g. `cyan -> color_rgb_float`. +Convert a color to its RGB floating point representation. ```nbt fn color_rgb_float(color: Color) -> String ``` +
+Examples + +
>>> use extra::color +cyan -> color_rgb_float + + = "rgb(0.000, 1.000, 1.000)" [String] +
+ +
+ ### `color_hex` -Convert a color to its hexadecimal representation, e.g. `rgb(225, 36, 143) -> color_hex`. +Convert a color to its hexadecimal representation. ```nbt fn color_hex(color: Color) -> String ``` +
+Examples + +
>>> use extra::color +rgb(225, 36, 143) -> color_hex + + = "#e1248f" [String] +
+ +
+ diff --git a/book/src/list-functions-strings.md b/book/src/list-functions-strings.md index e955338c..c233d39b 100644 --- a/book/src/list-functions-strings.md +++ b/book/src/list-functions-strings.md @@ -9,6 +9,16 @@ The length of a string. fn str_length(s: String) -> Scalar ``` +
+Examples + +
>>> str_length("Numbat") + + = 6 +
+ +
+ ### `str_slice` Subslice of a string. @@ -16,20 +26,50 @@ Subslice of a string. fn str_slice(s: String, start: Scalar, end: Scalar) -> String ``` +
+Examples + +
>>> str_slice("Numbat", 3, 6) + + = "bat" [String] +
+ +
+ ### `chr` -Get a single-character string from a Unicode code point. Example: `0x2764 -> chr`. +Get a single-character string from a Unicode code point. ```nbt fn chr(n: Scalar) -> String ``` +
+Examples + +
>>> 0x2764 -> chr + + = "❤" [String] +
+ +
+ ### `ord` -Get the Unicode code point of the first character in a string. Example: `"❤" -> ord`. +Get the Unicode code point of the first character in a string. ```nbt fn ord(s: String) -> Scalar ``` +
+Examples + +
>>> "❤" -> ord + + = 10084 +
+ +
+ ### `lowercase` Convert a string to lowercase. @@ -37,6 +77,16 @@ Convert a string to lowercase. fn lowercase(s: String) -> String ``` +
+Examples + +
>>> lowercase("Numbat") + + = "numbat" [String] +
+ +
+ ### `uppercase` Convert a string to uppercase. @@ -44,6 +94,16 @@ Convert a string to uppercase. fn uppercase(s: String) -> String ``` +
+Examples + +
>>> uppercase("Numbat") + + = "NUMBAT" [String] +
+ +
+ ### `str_append` Concatenate two strings. @@ -51,6 +111,16 @@ Concatenate two strings. fn str_append(a: String, b: String) -> String ``` +
+Examples + +
>>> str_append("Numbat", "!") + + = "Numbat!" [String] +
+ +
+ ### `str_find` Find the first occurrence of a substring in a string. @@ -58,6 +128,16 @@ Find the first occurrence of a substring in a string. fn str_find(haystack: String, needle: String) -> Scalar ``` +
+Examples + +
>>> str_find("Numbat is a statically typed programming language.", "typed") + + = 23 +
+ +
+ ### `str_contains` Check if a string contains a substring. @@ -65,6 +145,16 @@ Check if a string contains a substring. fn str_contains(haystack: String, needle: String) -> Bool ``` +
+Examples + +
>>> str_contains("Numbat is a statically typed programming language.", "typed") + + = true [Bool] +
+ +
+ ### `str_replace` Replace all occurrences of a substring in a string. @@ -72,6 +162,16 @@ Replace all occurrences of a substring in a string. fn str_replace(s: String, pattern: String, replacement: String) -> String ``` +
+Examples + +
>>> str_replace("Numbat is a statically typed programming language.", "statically typed programming language", "scientific calculator") + + = "Numbat is a scientific calculator." [String] +
+ +
+ ### `str_repeat` Repeat the input string `n` times. @@ -79,27 +179,67 @@ Repeat the input string `n` times. fn str_repeat(a: String, n: Scalar) -> String ``` +
+Examples + +
>>> str_repeat("abc", 4) + + = "abcabcabcabc" [String] +
+ +
+ ### `base` -Convert a number to the given base. Example: `42 |> base(16)`. +Convert a number to the given base. ```nbt fn base(b: Scalar, x: Scalar) -> String ``` +
+Examples + +
>>> 42 |> base(16) + + = "2a" [String] +
+ +
+ ### `bin` -Get a binary representation of a number. Example: `42 -> bin`. +Get a binary representation of a number. ```nbt fn bin(x: Scalar) -> String ``` +
+Examples + +
>>> 42 -> bin + + = "0b101010" [String] +
+ +
+ ### `oct` -Get an octal representation of a number. Example: `42 -> oct`. +Get an octal representation of a number. ```nbt fn oct(x: Scalar) -> String ``` +
+Examples + +
>>> 42 -> oct + + = "0o52" [String] +
+ +
+ ### `dec` Get a decimal representation of a number. @@ -107,10 +247,30 @@ Get a decimal representation of a number. fn dec(x: Scalar) -> String ``` +
+Examples + +
>>> 0b111 -> dec + + = "7" [String] +
+ +
+ ### `hex` -Get a hexadecimal representation of a number. Example: `2^31-1 -> hex`. +Get a hexadecimal representation of a number. ```nbt fn hex(x: Scalar) -> String ``` +
+Examples + +
>>> 2^31-1 -> hex + + = "0x7fffffff" [String] +
+ +
+ diff --git a/examples/tests/mixed_units.nbt b/examples/tests/mixed_units.nbt index 3dba44b7..2bb71ddd 100644 --- a/examples/tests/mixed_units.nbt +++ b/examples/tests/mixed_units.nbt @@ -4,33 +4,48 @@ assert_eq(38° + 53′ + 23″, 38.8897°, 1e-4°) assert_eq(-(77° + 0′ + 32″), -77.0089°, 1e-4°) -assert_eq(38.8897° -> DMS, "38° 53′ 23″") -assert_eq(-77.0089° -> DMS, "-77° 0′ 32″") +assert_eq("{38.8897° -> DMS}", "{[38°, 53′, 22.92″]}") +assert_eq("{-77.0089° -> DMS}", "{[-77°, -0′, -32.04″]}") ## Stuttgart assert_eq(48° + 46′ + 32″, 48.7756°, 1e-4°) assert_eq(9° + 10′ + 58″, 9.1828°, 1e-4°) -assert_eq(48.7756° -> DMS, "48° 46′ 32″") -assert_eq(9.1828° -> DMS, "9° 10′ 58″") +assert_eq("{48.7756° -> DMS}", "{[48°, 46′, 32.16″]}") +assert_eq("{9.1828° -> DMS}", "{[9°, 10′, 58.08″]}") # Degrees, decimal minutes (DM) -assert_eq(38.8897° -> DM, "38° 53.382′") -assert_eq(-77.0089° -> DM, "-77° 0.534′") +assert_eq("{38.8897° -> DM}", "{[38°, 53.382′]}") +assert_eq("{-77.0089° -> DM}", "{[-77°, -0.534′]}") # Feet and inches -assert_eq(5.5 ft -> feet_and_inches, "5 ft 6 in") -assert_eq(6.75 ft -> feet_and_inches, "6 ft 9 in") -assert_eq(-5.5 ft -> feet_and_inches, "-5 ft 6 in") -assert_eq(0 -> feet_and_inches, "0 ft 0 in") -assert_eq(1 ft -> feet_and_inches, "1 ft 0 in") -assert_eq(2.345 inch -> feet_and_inches, "0 ft 2.345 in") +assert_eq("{5.5 ft -> feet_and_inches}", "{[5 ft, 6 in]}") +assert_eq("{6.75 ft -> feet_and_inches}", "{[6 ft, 9 in]}") +assert_eq("{-5.5 ft -> feet_and_inches}", "{[-5 ft, -6 in]}") +assert_eq("{0 -> feet_and_inches}", "{[0 ft, 0 in]}") +assert_eq("{1 ft -> feet_and_inches}", "{[1 ft, 0 in]}") +assert_eq("{2.345 inch -> feet_and_inches}", "{[0 ft, 2.345 in]}") # Pounds and ounces -assert_eq(5 lb -> pounds_and_ounces, "5 lb 0 oz") -assert_eq(5.5 lb -> pounds_and_ounces, "5 lb 8 oz") -assert_eq(6.75 lb -> pounds_and_ounces, "6 lb 12 oz") -assert_eq(-5.5 lb -> pounds_and_ounces, "-5 lb 8 oz") +assert_eq("{5 lb -> pounds_and_ounces}", "{[5 lb, 0 oz]}") +assert_eq("{5.5 lb -> pounds_and_ounces}", "{[5 lb, 8 oz]}") +assert_eq("{6.75 lb -> pounds_and_ounces}", "{[6 lb, 12 oz]}") +assert_eq("{-5.5 lb -> pounds_and_ounces}", "{[-5 lb, -8 oz]}") + +# Unit list + +let test1 = 12 m + 34 cm + 5 mm + 678 µm +assert_eq(test1 |> unit_list([m]) |> head, test1) +assert_eq(test1 |> unit_list([m, cm]) |> sum, test1) +assert_eq(test1 |> unit_list([m, cm, mm]) |> sum, test1) +assert_eq(test1 |> unit_list([m, cm, mm, µm]) |> sum, test1) + +let test2 = 12 degree + 34 arcminute + 5 arcsec +assert_eq(test2 |> unit_list([degree]) |> head, test2) +assert_eq(test2 |> unit_list([degree, arcmin]) |> sum, test2) +assert_eq(test2 |> unit_list([degree, arcmin, arcsec]) |> sum, test2) + + diff --git a/numbat-cli/src/completer.rs b/numbat-cli/src/completer.rs index 43dfd24a..b249bbd7 100644 --- a/numbat-cli/src/completer.rs +++ b/numbat-cli/src/completer.rs @@ -131,7 +131,7 @@ impl Completer for NumbatCompleter { candidates .map(|w| Pair { display: w.to_string(), - replacement: w.to_string(), + replacement: w, }) .collect(), )) diff --git a/numbat-cli/src/highlighter.rs b/numbat-cli/src/highlighter.rs index 6f4d5410..a8e98e2b 100644 --- a/numbat-cli/src/highlighter.rs +++ b/numbat-cli/src/highlighter.rs @@ -40,17 +40,23 @@ impl Highlighter for NumbatHighlighter { if ctx.variable_names().any(|n| n == candidate) || ctx.function_names().any(|n| format!("{n}(") == candidate) { - Cow::Owned(ansi_format(&markup::identifier(candidate), false)) + Cow::Owned(ansi_format( + &markup::identifier(candidate.to_string()), + false, + )) } else if ctx .unit_names() .iter() .any(|un| un.iter().any(|n| n == candidate)) { - Cow::Owned(ansi_format(&markup::unit(candidate), false)) + Cow::Owned(ansi_format(&markup::unit(candidate.to_string()), false)) } else if ctx.dimension_names().iter().any(|n| n == candidate) { - Cow::Owned(ansi_format(&markup::type_identifier(candidate), false)) + Cow::Owned(ansi_format( + &markup::type_identifier(candidate.to_string()), + false, + )) } else if KEYWORDS.iter().any(|k| k == &candidate) { - Cow::Owned(ansi_format(&markup::keyword(candidate), false)) + Cow::Owned(ansi_format(&markup::keyword(candidate.to_string()), false)) } else { Cow::Borrowed(candidate) } diff --git a/numbat-cli/src/main.rs b/numbat-cli/src/main.rs index ff5fa868..47b44829 100644 --- a/numbat-cli/src/main.rs +++ b/numbat-cli/src/main.rs @@ -418,7 +418,7 @@ impl Cli { let m = m::text( "successfully saved session history to", ) + m::space() - + m::string(dst); + + m::string(dst.to_string()); println!("{}", ansi_format(&m, interactive)); } Err(err) => { diff --git a/numbat/Cargo.toml b/numbat/Cargo.toml index 69420a40..b527c5fb 100644 --- a/numbat/Cargo.toml +++ b/numbat/Cargo.toml @@ -49,6 +49,7 @@ glob = "0.3" insta = "1.34.0" once_cell = "1.19.0" criterion = { version = "0.5", features = ["html_reports"] } +percent-encoding = "2.3.1" [[bench]] name = "prelude" diff --git a/numbat/examples/inspect.rs b/numbat/examples/inspect.rs index e61b37ac..0342c5f5 100644 --- a/numbat/examples/inspect.rs +++ b/numbat/examples/inspect.rs @@ -1,5 +1,9 @@ use itertools::Itertools; -use numbat::{module_importer::FileSystemImporter, resolver::CodeSource, Context}; +use numbat::markup::plain_text_format; +use numbat::module_importer::FileSystemImporter; +use numbat::resolver::CodeSource; +use numbat::Context; +use percent_encoding; use std::path::Path; const AUTO_GENERATED_HINT: &str = ""; @@ -40,8 +44,8 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi } } -fn inspect_functions_in_module(ctx: &Context, module: String) { - for (fn_name, name, signature, description, url, code_source) in ctx.functions() { +fn inspect_functions_in_module(ctx: &Context, prelude_ctx: &Context, module: String) { + for (fn_name, name, signature, description, url, examples, code_source) in ctx.functions() { let CodeSource::Module(module_path, _) = code_source else { unreachable!(); }; @@ -57,19 +61,7 @@ fn inspect_functions_in_module(ctx: &Context, module: String) { } if let Some(ref description_raw) = description { - let description_raw = description_raw.trim().to_string(); - - // Replace $..$ with \\( .. \\) for mdbook. - let mut description = String::new(); - for (i, part) in description_raw.split('$').enumerate() { - if i % 2 == 0 { - description.push_str(part); - } else { - description.push_str("\\\\( "); - description.push_str(part); - description.push_str(" \\\\)"); - } - } + let description = replace_equation_delimiters(description_raw.trim().to_string()); if description.ends_with('.') { println!("{description}"); @@ -86,17 +78,118 @@ fn inspect_functions_in_module(ctx: &Context, module: String) { println!("{signature}"); println!("```"); println!(); + + if !examples.is_empty() { + println!("
"); + println!("Examples"); + println!(); + + for (example_code, example_description) in examples { + let mut example_ctx = prelude_ctx.clone(); + let extra_import = if !example_ctx + .resolver() + .imported_modules + .contains(&module_path) + { + format!("use {}\n", module) + } else { + "".into() + }; + let _result = example_ctx + .interpret(&extra_import, CodeSource::Internal) + .unwrap(); + + if let Ok((statements, results)) = + example_ctx.interpret(&example_code, CodeSource::Internal) + { + let code = extra_import + &example_code; + + //Format the example input + let example_input = format!(">>> {}", code); + + //Encode the example url + let example_url = format!( + "https://numbat.dev/?q={}", + percent_encoding::utf8_percent_encode( + &code, + percent_encoding::NON_ALPHANUMERIC + ) + ); + + //Assemble the example output + let result_markup = results.to_markup( + statements.last(), + &example_ctx.dimension_registry(), + true, + true, + ); + let example_output = &plain_text_format(&result_markup, false); + + //Print the example + if let Some(example_description) = example_description { + println!("{}", replace_equation_delimiters(example_description)); + } + + print!("
");
+                    print!("
"); + print!("", + "Run this code", + "Run this code", + example_url); + print!("
"); + print!(""); + for l in example_input.lines() { + println!("{}", l); + } + println!(); + for l in example_output.lines() { + println!("{}", l); + } + println!("
"); + println!(); + } else { + eprintln!( + "Warning: Example \"{example_code}\" of function {fn_name} did not run successfully." + ); + } + } + println!("
"); + println!(); + } } } -fn main() { - let module_path = Path::new(&std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("modules"); +// Replace $..$ with \\( .. \\) for mdbook. +fn replace_equation_delimiters(text_in: String) -> String { + let mut text_out = String::new(); + for (i, part) in text_in.split('$').enumerate() { + if i % 2 == 0 { + text_out.push_str(part); + } else { + text_out.push_str("\\\\( "); + text_out.push_str(part); + text_out.push_str(" \\\\)"); + } + } + return text_out; +} +fn prepare_context() -> Context { + let module_path = Path::new(&std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("modules"); let mut importer = FileSystemImporter::default(); importer.add_path(module_path); - let mut ctx = Context::new(importer); + return Context::new(importer); +} + +fn main() { + let mut ctx = prepare_context(); let _result = ctx.interpret("use all", CodeSource::Internal).unwrap(); + let mut example_ctx = prepare_context(); + let _result = example_ctx + .interpret("use prelude", CodeSource::Internal) + .unwrap(); + let mut args = std::env::args(); args.next(); if let Some(arg) = args.next() { @@ -104,7 +197,7 @@ fn main() { "units" => inspect_units(&ctx), "functions" => { let module = args.next().unwrap(); - inspect_functions_in_module(&ctx, module) + inspect_functions_in_module(&ctx, &example_ctx, module) } _ => eprintln!("USAGE: inspect [units|functions ]"), } diff --git a/numbat/modules/chemistry/elements.nbt b/numbat/modules/chemistry/elements.nbt index 17d74bb3..e77fed50 100644 --- a/numbat/modules/chemistry/elements.nbt +++ b/numbat/modules/chemistry/elements.nbt @@ -49,6 +49,8 @@ fn _convert_from_raw(raw: _ChemicalElementRaw) -> ChemicalElement = } @name("Chemical element") -@description("Get properties of a chemical element by its symbol or name (case-insensitive). For example: `element(\"H\")` or `element(\"hydrogen\")`.") +@description("Get properties of a chemical element by its symbol or name (case-insensitive).") +@example("element(\"H\")", "Get the entire element struct for hydrogen.") +@example("element(\"hydrogen\").ionization_energy", "Get the ionization energy of hydrogen.") fn element(pattern: String) -> ChemicalElement = _convert_from_raw(_get_chemical_element_data_raw(pattern)) diff --git a/numbat/modules/core/functions.nbt b/numbat/modules/core/functions.nbt index c088f693..75d36b52 100644 --- a/numbat/modules/core/functions.nbt +++ b/numbat/modules/core/functions.nbt @@ -2,64 +2,85 @@ use core::scalar @name("Identity function") @description("Return the input value.") +@example("id(8 kg)") fn id
(x: A) -> A = x @name("Absolute value") @description("Return the absolute value $|x|$ of the input. This works for quantities, too: `abs(-5 m) = 5 m`.") @url("https://doc.rust-lang.org/std/primitive.f64.html#method.abs") +@example("abs(-22.2 m)") fn abs(x: T) -> T @name("Square root") @description("Return the square root $\\sqrt\{x\}$ of the input: `sqrt(121 m^2) = 11 m`.") @url("https://en.wikipedia.org/wiki/Square_root") +@example("sqrt(4 are) -> m") fn sqrt(x: D^2) -> D = x^(1/2) @name("Cube root") @description("Return the cube root $\\sqrt[3]\{x\}$ of the input: `cbrt(8 m^3) = 2 m`.") @url("https://en.wikipedia.org/wiki/Cube_root") +@example("cbrt(8 L) -> cm") fn cbrt(x: D^3) -> D = x^(1/3) @name("Square function") @description("Return the square of the input, $x^2$: `sqr(5 m) = 25 m^2`.") +@example("sqr(7)") fn sqr(x: D) -> D^2 = x^2 @name("Rounding") @description("Round to the nearest integer. If the value is half-way between two integers, round away from $0$. See also: `round_in`.") @url("https://doc.rust-lang.org/std/primitive.f64.html#method.round") +@example("round(5.5)") +@example("round(-5.5)") fn round(x: Scalar) -> Scalar @name("Rounding") -@description("Round to the nearest multiple of `base`. For example: `round_in(m, 5.3 m) == 5 m`.") +@description("Round to the nearest multiple of `base`.") +@example("round_in(m, 5.3 m)", "Round in meters.") +@example("round_in(cm, 5.3 m)", "Round in centimeters.") fn round_in(base: D, value: D) -> D = round(value / base) × base @name("Floor function") @description("Returns the largest integer less than or equal to $x$. See also: `floor_in`.") @url("https://doc.rust-lang.org/std/primitive.f64.html#method.floor") +@example("floor(5.5)") fn floor(x: Scalar) -> Scalar @name("Floor function") -@description("Returns the largest integer multiple of `base` less than or equal to `value`. For example: `floor_in(m, 5.7 m) == 5 m`.") +@description("Returns the largest integer multiple of `base` less than or equal to `value`.") +@example("floor_in(m, 5.7 m)", "Floor in meters.") +@example("floor_in(cm, 5.7 m)", "Floor in centimeters.") fn floor_in(base: D, value: D) -> D = floor(value / base) × base @name("Ceil function") @description("Returns the smallest integer greater than or equal to $x$. See also: `ceil_in`.") @url("https://doc.rust-lang.org/std/primitive.f64.html#method.ceil") +@example("ceil(5.5)") fn ceil(x: Scalar) -> Scalar @name("Ceil function") -@description("Returns the smallest integer multuple of `base` greater than or equal to `value`. For example: `ceil_in(m, 5.3 m) == 6 m`.") +@description("Returns the smallest integer multiple of `base` greater than or equal to `value`.") +@example("ceil_in(m, 5.3 m)", "Ceil in meters.") +@example("ceil_in(cm, 5.3 m)", "Ceil in centimeters.") + fn ceil_in(base: D, value: D) -> D = ceil(value / base) × base @name("Truncation") @description("Returns the integer part of $x$. Non-integer numbers are always truncated towards zero. See also: `trunc_in`.") @url("https://doc.rust-lang.org/std/primitive.f64.html#method.trunc") +@example("trunc(5.5)") +@example("trunc(-5.5)") fn trunc(x: Scalar) -> Scalar @name("Truncation") -@description("Truncates to an integer multiple of `base` (towards zero). For example: `trunc_in(m, -5.7 m) == -5 m`.") +@description("Truncates to an integer multiple of `base` (towards zero).") +@example("trunc_in(m, 5.7 m)", "Truncate in meters.") +@example("trunc_in(cm, 5.7 m)", "Truncate in centimeters.") fn trunc_in(base: D, value: D) -> D = trunc(value / base) × base @name("Modulo") @description("Calculates the least nonnegative remainder of $a (\\mod b)$.") @url("https://doc.rust-lang.org/std/primitive.f64.html#method.rem_euclid") +@example("mod(27, 5)") fn mod(a: T, b: T) -> T diff --git a/numbat/modules/core/lists.nbt b/numbat/modules/core/lists.nbt index 8cd81b46..8c56dc49 100644 --- a/numbat/modules/core/lists.nbt +++ b/numbat/modules/core/lists.nbt @@ -3,48 +3,60 @@ use core::error use core::strings @description("Get the length of a list") +@example("len([3, 2, 1])") fn len(xs: List) -> Scalar @description("Get the first element of a list. Yields a runtime error if the list is empty.") +@example("head([3, 2, 1])") fn head(xs: List) -> A @description("Get everything but the first element of a list. Yields a runtime error if the list is empty.") +@example("tail([3, 2, 1])") fn tail(xs: List) -> List @description("Prepend an element to a list") +@example("cons(77, [3, 2, 1])") fn cons(x: A, xs: List) -> List @description("Append an element to the end of a list") +@example("cons_end(77, [3, 2, 1])") fn cons_end(x: A, xs: List) -> List @description("Check if a list is empty") +@example("is_empty([3, 2, 1])") +@example("is_empty([])") fn is_empty(xs: List) -> Bool = xs == [] @description("Concatenate two lists") +@example("concat([3, 2, 1], [10, 11])") fn concat(xs1: List, xs2: List) -> List = if is_empty(xs1) then xs2 else cons(head(xs1), concat(tail(xs1), xs2)) @description("Get the first `n` elements of a list") +@example("take(2, [3, 2, 1, 0])") fn take(n: Scalar, xs: List) -> List = if n == 0 || is_empty(xs) then [] else cons(head(xs), take(n - 1, tail(xs))) @description("Get everything but the first `n` elements of a list") +@example("drop(2, [3, 2, 1, 0])") fn drop(n: Scalar, xs: List) -> List = if n == 0 || is_empty(xs) then xs else drop(n - 1, tail(xs)) @description("Get the element at index `i` in a list") +@example("element_at(2, [3, 2, 1, 0])") fn element_at(i: Scalar, xs: List) -> A = if i == 0 then head(xs) else element_at(i - 1, tail(xs)) @description("Generate a range of integer numbers from `start` to `end` (inclusive)") +@example("range(2, 12)") fn range(start: Scalar, end: Scalar) -> List = if start > end then [] @@ -52,18 +64,21 @@ fn range(start: Scalar, end: Scalar) -> List = @description("Reverse the order of a list") +@example("reverse([3, 2, 1])") fn reverse(xs: List) -> List = if is_empty(xs) then [] else cons_end(head(xs), reverse(tail(xs))) @description("Generate a new list by applying a function to each element of the input list") +@example("map(sqr, [3, 2, 1])", "Square all elements of a list.") fn map(f: Fn[(A) -> B], xs: List) -> List = if is_empty(xs) then [] else cons(f(head(xs)), map(f, tail(xs))) @description("Filter a list by a predicate") +@example("filter(is_finite, [0, 1e10, NaN, -inf])") fn filter(p: Fn[(A) -> Bool], xs: List) -> List = if is_empty(xs) then [] @@ -72,6 +87,7 @@ fn filter(p: Fn[(A) -> Bool], xs: List) -> List = else filter(p, tail(xs)) @description("Fold a function over a list") +@example("foldl(str_append, \"\", [\"Num\", \"bat\", \"!\"])", "Join a list of strings by folding.") fn foldl(f: Fn[(A, B) -> A], acc: A, xs: List) -> A = if is_empty(xs) then acc @@ -88,6 +104,7 @@ fn _merge(xs, ys, cmp) = @description("Sort a list of elements, using the given key function that maps the element to a quantity") +@example("fn last_digit(x) = mod(x, 10)\nsort_by_key(last_digit, [701, 313, 9999, 4])","Sort by last digit.") fn sort_by_key(key: Fn[(A) -> D], xs: List) -> List = if is_empty(xs) then [] @@ -98,9 +115,11 @@ fn sort_by_key(key: Fn[(A) -> D], xs: List) -> List = key) @description("Sort a list of quantities") +@example("sort([3, 2, 7, 8, -4, 0, -5])") fn sort(xs: List) -> List = sort_by_key(id, xs) @description("Add an element between each pair of elements in a list") +@example("intersperse(0, [1, 1, 1, 1])") fn intersperse(sep: A, xs: List) -> List = if is_empty(xs) then [] @@ -110,6 +129,7 @@ fn intersperse(sep: A, xs: List) -> List = fn _add(x, y) = x + y # TODO: replace this with a local function once we support them @description("Sum all elements of a list") +@example("sum([3 m, 200 cm, 1000 mm])") fn sum(xs: List) -> D = foldl(_add, 0, xs) # TODO: implement linspace using `map` or similar once we have closures. This is ugly. @@ -119,12 +139,14 @@ fn _linspace_helper(start, end, n_steps, i) = else cons(start + (end - start) * i / (n_steps - 1), _linspace_helper(start, end, n_steps, i + 1)) @description("Generate a list of `n_steps` evenly spaced numbers from `start` to `end` (inclusive)") +@example("linspace(-5 m, 5 m, 11)") fn linspace(start: D, end: D, n_steps: Scalar) -> List = if n_steps <= 1 then error("Number of steps must be larger than 1") else _linspace_helper(start, end, n_steps, 0) @description("Convert a list of strings into a single string by concatenating them with a separator") +@example("join([\"snake\", \"case\"], \"_\")") fn join(xs: List, sep: String) = if is_empty(xs) then "" @@ -133,6 +155,7 @@ fn join(xs: List, sep: String) = else "{head(xs)}{sep}{join(tail(xs), sep)}" @description("Split a string into a list of strings using a separator") +@example("split(\"Numbat is a statically typed programming language.\", \" \")") fn split(input: String, separator: String) -> List = if input == "" then [] diff --git a/numbat/modules/core/mixed_units.nbt b/numbat/modules/core/mixed_units.nbt index 8b339327..f87da4ef 100644 --- a/numbat/modules/core/mixed_units.nbt +++ b/numbat/modules/core/mixed_units.nbt @@ -3,25 +3,15 @@ use core::lists # Helper functions for mixed-unit conversions. See units::mixed for more. -fn _mixed_units_helper(q: D, units: List, names: List, round_last: Bool) -> List = - if is_empty(units) - then - [] - else - cons( - if len(units) == 1 - then - if round_last - then "{round(q / head(units))}{head(names)}" - else "{q / head(units)}{head(names)}" - else "{trunc(q / head(units))}{head(names)}", - _mixed_units_helper( - q - trunc(q / head(units)) * head(units), - tail(units), - tail(names), - round_last)) +fn _zero_length(val: A) -> A = val * 0 -> val -fn _mixed_units(q: D, units: List, names: List, round_last: Bool) -> String = - if q < 0 - then str_append("-", _mixed_units(-q, units, names, round_last)) - else join(_mixed_units_helper(q, units, names, round_last), "") +fn _mixed_unit_list(val: D, units: List, acc: List) -> List = + if val == 0 + then concat(acc, map(_zero_length, units)) + else if len(units) == 1 + then cons_end(val -> head(units), acc) + else _mixed_unit_list(val - unit_val, tail(units), cons_end(unit_val, acc)) + where unit_val: D = + if (len(units) > 0) + then (val |> trunc_in(head(units))) + else error("Units list cannot be empty") diff --git a/numbat/modules/core/numbers.nbt b/numbat/modules/core/numbers.nbt index 2dba36c9..3265ea38 100644 --- a/numbat/modules/core/numbers.nbt +++ b/numbat/modules/core/numbers.nbt @@ -1,10 +1,16 @@ @description("Returns true if the input is `NaN`.") @url("https://doc.rust-lang.org/std/primitive.f64.html#method.is_nan") +@example("is_nan(37)") +@example("is_nan(NaN)") fn is_nan(n: T) -> Bool @description("Returns true if the input is positive infinity or negative infinity.") @url("https://doc.rust-lang.org/std/primitive.f64.html#method.is_infinite") +@example("is_infinite(37)") +@example("is_infinite(-inf)") fn is_infinite(n: T) -> Bool @description("Returns true if the input is neither infinite nor `NaN`.") +@example("is_finite(37)") +@example("is_finite(-inf)") fn is_finite(n: T) -> Bool = !is_nan(n) && !is_infinite(n) diff --git a/numbat/modules/core/quantities.nbt b/numbat/modules/core/quantities.nbt index c7cd9b07..3d886bd2 100644 --- a/numbat/modules/core/quantities.nbt +++ b/numbat/modules/core/quantities.nbt @@ -1,7 +1,9 @@ use core::scalar @description("Extract the unit of a quantity (the `km/h` in `20 km/h`). This can be useful in generic code, but should generally be avoided otherwise.") +@example("unit_of(20 km/h)") fn unit_of(x: T) -> T @description("Extract the plain value of a quantity (the `20` in `20 km/h`). This can be useful in generic code, but should generally be avoided otherwise.") +@example("value_of(20 km/h)") fn value_of(x: T) -> Scalar = x / unit_of(x) diff --git a/numbat/modules/core/strings.nbt b/numbat/modules/core/strings.nbt index fb1ef77b..aa21dab5 100644 --- a/numbat/modules/core/strings.nbt +++ b/numbat/modules/core/strings.nbt @@ -3,27 +3,35 @@ use core::functions use core::error @description("The length of a string") +@example("str_length(\"Numbat\")") fn str_length(s: String) -> Scalar @description("Subslice of a string") +@example("str_slice(\"Numbat\", 3, 6)") fn str_slice(s: String, start: Scalar, end: Scalar) -> String -@description("Get a single-character string from a Unicode code point. Example: `0x2764 -> chr`") +@description("Get a single-character string from a Unicode code point.") +@example("0x2764 -> chr") fn chr(n: Scalar) -> String -@description("Get the Unicode code point of the first character in a string. Example: `\"❤\" -> ord`") +@description("Get the Unicode code point of the first character in a string.") +@example("\"❤\" -> ord") fn ord(s: String) -> Scalar @description("Convert a string to lowercase") +@example("lowercase(\"Numbat\")") fn lowercase(s: String) -> String @description("Convert a string to uppercase") +@example("uppercase(\"Numbat\")") fn uppercase(s: String) -> String @description("Concatenate two strings") +@example("str_append(\"Numbat\", \"!\")") fn str_append(a: String, b: String) -> String = "{a}{b}" @description("Find the first occurrence of a substring in a string") +@example("str_find(\"Numbat is a statically typed programming language.\", \"typed\")") fn str_find(haystack: String, needle: String) -> Scalar = if len_haystack == 0 then -1 @@ -36,10 +44,12 @@ fn str_find(haystack: String, needle: String) -> Scalar = and tail_haystack = str_slice(haystack, 1, len_haystack) @description("Check if a string contains a substring") +@example("str_contains(\"Numbat is a statically typed programming language.\", \"typed\")") fn str_contains(haystack: String, needle: String) -> Bool = str_find(haystack, needle) != -1 @description("Replace all occurrences of a substring in a string") +@example("str_replace(\"Numbat is a statically typed programming language.\", \"statically typed programming language\", \"scientific calculator\")") fn str_replace(s: String, pattern: String, replacement: String) -> String = if pattern == "" then s @@ -50,6 +60,7 @@ fn str_replace(s: String, pattern: String, replacement: String) -> String = else s @description("Repeat the input string `n` times") +@example("str_repeat(\"abc\", 4)") fn str_repeat(a: String, n: Scalar) -> String = if n > 0 then str_append(a, str_repeat(a, n - 1)) @@ -76,7 +87,8 @@ fn _digit_in_base(base: Scalar, x: Scalar) -> String = # TODO: once we have anonymous functions / closures, we can implement base in a way # that it returns a partially-applied version of '_number_in_base'. This would allow # arbitrary 'x -> base(b)' conversions. -@description("Convert a number to the given base. Example: `42 |> base(16)`") +@description("Convert a number to the given base.") +@example("42 |> base(16)") fn base(b: Scalar, x: Scalar) -> String = if x < 0 then "-{base(b, -x)}" @@ -84,14 +96,18 @@ fn base(b: Scalar, x: Scalar) -> String = then _digit_in_base(b, x) else str_append(base(b, floor(x / b)), _digit_in_base(b, mod(x, b))) -@description("Get a binary representation of a number. Example: `42 -> bin`") +@description("Get a binary representation of a number.") +@example("42 -> bin") fn bin(x: Scalar) -> String = if x < 0 then "-{bin(-x)}" else "0b{base(2, x)}" -@description("Get an octal representation of a number. Example: `42 -> oct`") +@description("Get an octal representation of a number.") +@example("42 -> oct") fn oct(x: Scalar) -> String = if x < 0 then "-{oct(-x)}" else "0o{base(8, x)}" @description("Get a decimal representation of a number.") +@example("0b111 -> dec") fn dec(x: Scalar) -> String = base(10, x) -@description("Get a hexadecimal representation of a number. Example: `2^31-1 -> hex`") +@description("Get a hexadecimal representation of a number.") +@example("2^31-1 -> hex") fn hex(x: Scalar) -> String = if x < 0 then "-{hex(-x)}" else "0x{base(16, x)}" diff --git a/numbat/modules/datetime/functions.nbt b/numbat/modules/datetime/functions.nbt index fe9854ad..4e350a4d 100644 --- a/numbat/modules/datetime/functions.nbt +++ b/numbat/modules/datetime/functions.nbt @@ -7,15 +7,22 @@ use units::time fn now() -> DateTime @description("Parses a string (date and time) into a `DateTime` object. See [here](./date-and-time.md#date-time-formats) for an overview of the supported formats.") +@example("datetime(\"2022-07-20T21:52+0200\")") +@example("datetime(\"2022-07-20 21:52 Europe/Berlin\")") +@example("datetime(\"2022/07/20 09:52 PM +0200\")") fn datetime(input: String) -> DateTime @description("Formats a `DateTime` object as a string.") +@example("format_datetime(\"This is a date in %B in the year %Y.\", datetime(\"2022-07-20 21:52 +0200\"))") fn format_datetime(format: String, input: DateTime) -> String @description("Returns the users local timezone.") +@example("get_local_timezone()") fn get_local_timezone() -> String @description("Returns a timezone conversion function, typically used with the conversion operator.") +@example("datetime(\"2022-07-20 21:52 +0200\") -> tz(\"Europe/Amsterdam\")") +@example("datetime(\"2022-07-20 21:52 +0200\") -> tz(\"Asia/Taipei\")") fn tz(tz: String) -> Fn[(DateTime) -> DateTime] @description("Timezone conversion function targeting the users local timezone (`datetime -> local`).") @@ -25,9 +32,11 @@ let local: Fn[(DateTime) -> DateTime] = tz(get_local_timezone()) let UTC: Fn[(DateTime) -> DateTime] = tz("UTC") @description("Converts a `DateTime` to a UNIX timestamp. Can be used on the right hand side of a conversion operator: `now() -> unixtime`.") +@example("datetime(\"2022-07-20 21:52 +0200\") -> unixtime") fn unixtime(input: DateTime) -> Scalar @description("Converts a UNIX timestamp to a `DateTime` object.") +@example("from_unixtime(2^31)") fn from_unixtime(input: Scalar) -> DateTime fn _today_str() = format_datetime("%Y-%m-%d", now()) @@ -36,6 +45,7 @@ fn _today_str() = format_datetime("%Y-%m-%d", now()) fn today() -> DateTime = datetime("{_today_str()} 00:00:00") @description("Parses a string (only date) into a `DateTime` object.") +@example("date(\"2022-07-20\")") fn date(input: String) -> DateTime = if str_contains(input, " ") then datetime(str_replace(input, " ", " 00:00:00 ")) @@ -50,6 +60,7 @@ fn _add_months(dt: DateTime, n_months: Scalar) -> DateTime fn _add_years(dt: DateTime, n_years: Scalar) -> DateTime @description("Adds the given time span to a `DateTime`. This uses leap-year and DST-aware calendar arithmetic with variable-length days, months, and years.") +@example("calendar_add(datetime(\"2022-07-20 21:52 +0200\"), 2 years)") fn calendar_add(dt: DateTime, span: Time) -> DateTime = if span_unit == days then _add_days(dt, span / days) @@ -65,14 +76,17 @@ fn calendar_add(dt: DateTime, span: Time) -> DateTime = span_unit = unit_of(span) @description("Subtract the given time span from a `DateTime`. This uses leap-year and DST-aware calendar arithmetic with variable-length days, months, and years.") +@example("calendar_sub(datetime(\"2022-07-20 21:52 +0200\"), 3 years)") fn calendar_sub(dt: DateTime, span: Time) -> DateTime = calendar_add(dt, -span) @description("Get the day of the week from a given `DateTime`.") +@example("weekday(datetime(\"2022-07-20 21:52 +0200\"))") fn weekday(dt: DateTime) -> String = format_datetime("%A", dt) @name("Julian date") @description("Convert a `DateTime` to a Julian date, the number of days since the origin of the Julian date system (noon on November 24, 4714 BC in the proleptic Gregorian calendar).") @url("https://en.wikipedia.org/wiki/Julian_day") +@example("julian_date(datetime(\"2022-07-20 21:52 +0200\"))") fn julian_date(dt: DateTime) -> Time = (dt - datetime("-4713-11-24 12:00:00 +0000")) -> days diff --git a/numbat/modules/datetime/human.nbt b/numbat/modules/datetime/human.nbt index 23d94396..aab64aca 100644 --- a/numbat/modules/datetime/human.nbt +++ b/numbat/modules/datetime/human.nbt @@ -55,4 +55,5 @@ fn _abs_human(time: Time) -> String = @name("Human-readable time duration") @url("https://numbat.dev/doc/date-and-time.html") @description("Converts a time duration to a human-readable string in days, hours, minutes and seconds.") +@example("century/1e6 -> human", "How long is a microcentury?") fn human(time: Time) -> String = _human_manage_past(abs(time) -> _abs_human, time) diff --git a/numbat/modules/extra/algebra.nbt b/numbat/modules/extra/algebra.nbt index 60e034d1..fd7bccf3 100644 --- a/numbat/modules/extra/algebra.nbt +++ b/numbat/modules/extra/algebra.nbt @@ -7,6 +7,7 @@ fn _qe_solution(a: A, b: B, c: B² / A, sign: Scalar) -> B / A = @name("Solve quadratic equations") @url("https://en.wikipedia.org/wiki/Quadratic_equation") @description("Returns the solutions of the equation a x² + b x + c = 0") +@example("quadratic_equation(2, -1, -1)", "Solve the equation $2x² -x -1 = 0$") fn quadratic_equation(a: A, b: B, c: B² / A) -> List = if a == 0 then if b == 0 diff --git a/numbat/modules/extra/color.nbt b/numbat/modules/extra/color.nbt index 6b50c20a..89af9e94 100644 --- a/numbat/modules/extra/color.nbt +++ b/numbat/modules/extra/color.nbt @@ -9,10 +9,12 @@ struct Color { } @description("Create a `Color` from RGB (red, green, blue) values in the range $[0, 256)$.") +@example("rgb(125, 128, 218)") fn rgb(red: Scalar, green: Scalar, blue: Scalar) -> Color = Color { red: red, green: green, blue: blue } -@description("Create a `Color` from a (hexadecimal) value, e.g. `color(0xff7700)`") +@description("Create a `Color` from a (hexadecimal) value.") +@example("color(0xff7700)") fn color(rgb_hex: Scalar) -> Color = rgb( floor(rgb_hex / 256^2), @@ -22,15 +24,18 @@ fn color(rgb_hex: Scalar) -> Color = fn _color_to_scalar(color: Color) -> Scalar = color.red * 0x010000 + color.green * 0x000100 + color.blue -@description("Convert a color to its RGB representation, e.g. `cyan -> color_rgb`") +@description("Convert a color to its RGB representation.") +@example("cyan -> color_rgb") fn color_rgb(color: Color) -> String = "rgb({color.red}, {color.green}, {color.blue})" -@description("Convert a color to its RGB floating point representation, e.g. `cyan -> color_rgb_float`") +@description("Convert a color to its RGB floating point representation.") +@example("cyan -> color_rgb_float") fn color_rgb_float(color: Color) -> String = "rgb({color.red / 255:.3}, {color.green / 255:.3}, {color.blue / 255:.3})" -@description("Convert a color to its hexadecimal representation, e.g. `rgb(225, 36, 143) -> color_hex`") +@description("Convert a color to its hexadecimal representation.") +@example("rgb(225, 36, 143) -> color_hex") fn color_hex(color: Color) -> String = str_append("#", str_replace(str_replace("{color -> _color_to_scalar -> hex:>8}", "0x", ""), " ", "0")) diff --git a/numbat/modules/math/constants.nbt b/numbat/modules/math/constants.nbt index c3546448..249ba238 100644 --- a/numbat/modules/math/constants.nbt +++ b/numbat/modules/math/constants.nbt @@ -9,6 +9,7 @@ let π = 3.14159265358979323846264338327950288 @name("Tau") @url("https://en.wikipedia.org/wiki/Turn_(angle)#Tau_proposals") +@aliases(tau) let τ = 2 π @name("Euler's number") diff --git a/numbat/modules/math/geometry.nbt b/numbat/modules/math/geometry.nbt index e2029e20..942ca671 100644 --- a/numbat/modules/math/geometry.nbt +++ b/numbat/modules/math/geometry.nbt @@ -2,9 +2,11 @@ use core::functions use math::constants @description("The length of the hypotenuse of a right-angled triangle $\\sqrt\{x^2+y^2\}$.") +@example("hypot2(3 m, 4 m)") fn hypot2(x: T, y: T) -> T = sqrt(x^2 + y^2) @description("The Euclidean norm of a 3D vector $\\sqrt\{x^2+y^2+z^2\}$.") +@example("hypot3(8, 9, 12)") fn hypot3(x: T, y: T, z: T) -> T = sqrt(x^2 + y^2 + z^2) # The following functions use a generic dimension instead of diff --git a/numbat/modules/math/number_theory.nbt b/numbat/modules/math/number_theory.nbt index e3d45420..a9a4a917 100644 --- a/numbat/modules/math/number_theory.nbt +++ b/numbat/modules/math/number_theory.nbt @@ -4,6 +4,7 @@ use core::functions @name("Greatest common divisor") @description("The largest positive integer that divides each of the integers $a$ and $b$.") @url("https://en.wikipedia.org/wiki/Greatest_common_divisor") +@example("gcd(60, 42)") fn gcd(a: Scalar, b: Scalar) -> Scalar = if b == 0 then abs(a) @@ -12,4 +13,5 @@ fn gcd(a: Scalar, b: Scalar) -> Scalar = @name("Least common multiple") @description("The smallest positive integer that is divisible by both $a$ and $b$.") @url("https://en.wikipedia.org/wiki/Least_common_multiple") +@example("lcm(14, 4)") fn lcm(a: Scalar, b: Scalar) -> Scalar = abs(a * b) / gcd(a, b) diff --git a/numbat/modules/math/statistics.nbt b/numbat/modules/math/statistics.nbt index 293cb076..c99d3e22 100644 --- a/numbat/modules/math/statistics.nbt +++ b/numbat/modules/math/statistics.nbt @@ -5,38 +5,44 @@ fn _max(x: D, y: D) -> D = if x > y then x else y fn _min(x: D, y: D) -> D = if x < y then x else y @name("Maxmimum") -@description("Get the largest element of a list: `maximum([30 cm, 2 m]) = 2 m`.") +@description("Get the largest element of a list.") +@example("maximum([30 cm, 2 m])") fn maximum(xs: List) -> D = if len(xs) == 1 then head(xs) else _max(head(xs), maximum(tail(xs))) @name("Minimum") -@description("Get the smallest element of a list: `minimum([30 cm, 2 m]) = 30 cm`.") +@description("Get the smallest element of a list.") +@example("minimum([30 cm, 2 m])") fn minimum(xs: List) -> D = if len(xs) == 1 then head(xs) else _min(head(xs), minimum(tail(xs))) @name("Arithmetic mean") -@description("Calculate the arithmetic mean of a list of quantities: `mean([1 m, 2 m, 300 cm]) = 2 m`.") +@description("Calculate the arithmetic mean of a list of quantities.") +@example("mean([1 m, 2 m, 300 cm])") @url("https://en.wikipedia.org/wiki/Arithmetic_mean") fn mean(xs: List) -> D = if is_empty(xs) then 0 else sum(xs) / len(xs) @name("Variance") @url("https://en.wikipedia.org/wiki/Variance") @description("Calculate the population variance of a list of quantities") +@example("variance([1 m, 2 m, 300 cm])") fn variance(xs: List) -> D^2 = mean(map(sqr, xs)) - sqr(mean(xs)) @name("Standard deviation") @url("https://en.wikipedia.org/wiki/Standard_deviation") @description("Calculate the population standard deviation of a list of quantities") +@example("stdev([1 m, 2 m, 300 cm])") fn stdev(xs: List) -> D = sqrt(variance(xs)) @name("Median") @url("https://en.wikipedia.org/wiki/Median") @description("Calculate the median of a list of quantities") +@example("median([1 m, 2 m, 400 cm])") fn median(xs: List) -> D = # TODO: this is extremely inefficient if mod(n, 2) == 1 then element_at((n - 1) / 2, sort(xs)) diff --git a/numbat/modules/math/transcendental.nbt b/numbat/modules/math/transcendental.nbt index f079c78f..6e838e8e 100644 --- a/numbat/modules/math/transcendental.nbt +++ b/numbat/modules/math/transcendental.nbt @@ -3,26 +3,31 @@ use core::scalar @name("Exponential function") @description("The exponential function, $e^x$.") @url("https://en.wikipedia.org/wiki/Exponential_function") +@example("exp(4)") fn exp(x: Scalar) -> Scalar @name("Natural logarithm") @description("The natural logarithm with base $e$.") @url("https://en.wikipedia.org/wiki/Natural_logarithm") +@example("ln(20)") fn ln(x: Scalar) -> Scalar @name("Natural logarithm") @description("The natural logarithm with base $e$.") @url("https://en.wikipedia.org/wiki/Natural_logarithm") +@example("log(20)") fn log(x: Scalar) -> Scalar = ln(x) @name("Common logarithm") @description("The common logarithm with base $10$.") @url("https://en.wikipedia.org/wiki/Common_logarithm") +@example("log10(100)") fn log10(x: Scalar) -> Scalar @name("Binary logarithm") @description("The binary logarithm with base $2$.") @url("https://en.wikipedia.org/wiki/Binary_logarithm") +@example("log2(256)") fn log2(x: Scalar) -> Scalar @name("Gamma function") diff --git a/numbat/modules/numerics/diff.nbt b/numbat/modules/numerics/diff.nbt index 63c3c87b..820555e2 100644 --- a/numbat/modules/numerics/diff.nbt +++ b/numbat/modules/numerics/diff.nbt @@ -3,6 +3,8 @@ use core::quantities @name("Numerical differentiation") @url("https://en.wikipedia.org/wiki/Numerical_differentiation") @description("Compute the numerical derivative of the function $f$ at point $x$ using the central difference method.") +@example("fn polynomial(x) = x² - x - 1\ndiff(polynomial, 1)", "Compute the derivative of $f(x) = x² -x -1$ at $x=1$.") +@example("fn distance(t) = 0.5 g0 t²\nfn velocity(t) = diff(distance, t)\nvelocity(2 s)", "Compute the free fall velocity after $t=2 s$.") fn diff(f: Fn[(X) -> Y], x: X) -> Y / X = (f(x + Δx) - f(x - Δx)) / 2 Δx where diff --git a/numbat/modules/numerics/fixed_point.nbt b/numbat/modules/numerics/fixed_point.nbt index a046c9e9..8ee9e978 100644 --- a/numbat/modules/numerics/fixed_point.nbt +++ b/numbat/modules/numerics/fixed_point.nbt @@ -15,5 +15,6 @@ fn _fixed_point(f: Fn[(X) -> X], x0: X, ε: X, max_iter: Scalar) = @name("Fixed-point iteration") @url("https://en.wikipedia.org/wiki/Fixed-point_iteration") @description("Compute the approximate fixed point of a function $f: X \\rightarrow X$ starting from $x_0$, until $|f(x) - x| < ε$.") +@example("fn function(x) = x/2 - 1\nfixed_point(function, 0, 0.01)", "Compute the fixed poin of $f(x) = x/2 -1$.") fn fixed_point(f: Fn[(X) -> X], x0: X, ε: X) = _fixed_point(f, x0, ε, 100) diff --git a/numbat/modules/numerics/solve.nbt b/numbat/modules/numerics/solve.nbt index 7335a39b..6bd5703b 100644 --- a/numbat/modules/numerics/solve.nbt +++ b/numbat/modules/numerics/solve.nbt @@ -4,6 +4,7 @@ use core::error @name("Bisection method") @url("https://en.wikipedia.org/wiki/Bisection_method") @description("Find the root of the function $f$ in the interval $[x_1, x_2]$ using the bisection method. The function $f$ must be continuous and $f(x_1) \cdot f(x_2) < 0$.") +@example("fn f(x) = x² +x -2\nroot_bisect(f, 0, 100, 0.01, 0.01)", "Find the root of $f(x) = x² +x -2$ in the interval $[0, 100]$.") fn root_bisect(f: Fn[(X) -> Y], x1: X, x2: X, x_tol: X, y_tol: Y) -> X = if abs(x1 - x2) < x_tol then x_mean @@ -27,5 +28,6 @@ fn _root_newton_helper(f: Fn[(X) -> Y], f_prime: Fn[(X) -> Y / X @name("Newton's method") @url("https://en.wikipedia.org/wiki/Newton%27s_method") @description("Find the root of the function $f(x)$ and its derivative $f'(x)$ using Newton's method.") +@example("fn f(x) = x² -3x +2\nfn f_prime(x) = 2x -3\nroot_newton(f, f_prime, 0 , 0.01)", "Find a root of $f(x) = x² -3x +2$ using Newton's method.") fn root_newton(f: Fn[(X) -> Y], f_prime: Fn[(X) -> Y / X], x0: X, y_tol: Y) -> X = _root_newton_helper(f, f_prime, x0, y_tol, 10_000) diff --git a/numbat/modules/physics/temperature_conversion.nbt b/numbat/modules/physics/temperature_conversion.nbt index 8dac933d..8a5ec884 100644 --- a/numbat/modules/physics/temperature_conversion.nbt +++ b/numbat/modules/physics/temperature_conversion.nbt @@ -5,10 +5,12 @@ use units::si let _offset_celsius = 273.15 @description("Converts from degree Celsius (°C) to Kelvin.") +@example("from_celsius(300)", "300 °C in Kelvin.") @url("https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature") fn from_celsius(t_celsius: Scalar) -> Temperature = (t_celsius + _offset_celsius) kelvin @description("Converts from Kelvin to degree Celcius (°C). This can be used on the right hand side of a conversion operator: `200 K -> celsius`.") +@example("300K -> celsius", "300 K in degree Celsius.") @url("https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature") fn celsius(t_kelvin: Temperature) -> Scalar = t_kelvin / kelvin - _offset_celsius @@ -16,9 +18,11 @@ let _offset_fahrenheit = 459.67 let _scale_fahrenheit = 5 / 9 @description("Converts from degree Fahrenheit (°F) to Kelvin.") +@example("from_fahrenheit(300)", "300 °F in Kelvin.") @url("https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature") fn from_fahrenheit(t_fahrenheit: Scalar) -> Temperature = ((t_fahrenheit + _offset_fahrenheit) × _scale_fahrenheit) kelvin @description("Converts from Kelvin to degree Fahrenheit (°F). This can be used on the right hand side of a conversion operator: `200 K -> fahrenheit`.") +@example("300K -> fahrenheit", "300 K in degree Fahrenheit.") @url("https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature") fn fahrenheit(t_kelvin: Temperature) -> Scalar = (t_kelvin / kelvin) / _scale_fahrenheit - _offset_fahrenheit diff --git a/numbat/modules/units/mixed.nbt b/numbat/modules/units/mixed.nbt index 8ef93e26..d0aa1639 100644 --- a/numbat/modules/units/mixed.nbt +++ b/numbat/modules/units/mixed.nbt @@ -2,26 +2,36 @@ use core::mixed_units use units::si use units::imperial +@name("Unit list") +@description("Convert a value to a mixed representation using the provided units.") +@example("5500 m |> unit_list([miles, yards, feet, inches])") +fn unit_list(units: List, value: D) -> List = _mixed_unit_list(value, units, []) + @name("Degrees, minutes, seconds") @description("Convert an angle to a mixed degrees, (arc)minutes, and (arc)seconds representation. Also called sexagesimal degree notation.") @url("https://en.wikipedia.org/wiki/Sexagesimal_degree") -fn DMS(alpha: Angle) -> String = - _mixed_units(alpha, [deg, arcmin, arcsec], ["° ", "′ ", "″"], true) +@example("46.5858° -> DMS") +fn DMS(alpha: Angle) -> List = + unit_list([degree, arcminute, arcsecond], alpha) @name("Degrees, decimal minutes") @description("Convert an angle to a mixed degrees and decimal minutes representation.") @url("https://en.wikipedia.org/wiki/Decimal_degrees") -fn DM(alpha: Angle) -> String = - _mixed_units(alpha, [deg, arcmin], ["° ", "′"], false) +@example("46.5858° -> DM") +fn DM(alpha: Angle) -> List = + unit_list([degree, arcminute], alpha) @name("Feet and inches") @description("Convert a length to a mixed feet and inches representation.") @url("https://en.wikipedia.org/wiki/Foot_(unit)") -fn feet_and_inches(length: Length) -> String = - _mixed_units(length, [foot, inch], [" ft ", " in"], false) +@example("180 cm -> feet_and_inches") +fn feet_and_inches(length: Length) -> List = + unit_list([foot, inch], length) @name("Pounds and ounces") @description("Convert a mass to a mixed pounds and ounces representation.") @url("https://en.wikipedia.org/wiki/Pound_(mass)") -fn pounds_and_ounces(mass: Mass) -> String = - _mixed_units(mass, [pound, ounce], [" lb ", " oz"], false) +@example("1 kg -> pounds_and_ounces") +fn pounds_and_ounces(mass: Mass) -> List = + unit_list([pound, ounce], mass) + diff --git a/numbat/modules/units/si.nbt b/numbat/modules/units/si.nbt index 2ef8ffce..8ceddaee 100644 --- a/numbat/modules/units/si.nbt +++ b/numbat/modules/units/si.nbt @@ -202,12 +202,12 @@ unit degree: Angle = π / 180 × radian @name("Minute of arc") @url("https://en.wikipedia.org/wiki/Minute_and_second_of_arc") -@aliases(arcminutes, arcmin, ′) +@aliases(arcminutes, arcmin, ′: short) unit arcminute: Angle = 1 / 60 × degree @name("Second of arc") @url("https://en.wikipedia.org/wiki/Minute_and_second_of_arc") -@aliases(arcseconds, arcsec, ″) +@aliases(arcseconds, arcsec, ″: short) unit arcsecond: Angle = 1 / 60 × arcminute @name("Are") diff --git a/numbat/src/ast.rs b/numbat/src/ast.rs index 2741e392..f857eeac 100644 --- a/numbat/src/ast.rs +++ b/numbat/src/ast.rs @@ -36,21 +36,26 @@ impl PrettyPrint for BinaryOperator { fn pretty_print(&self) -> Markup { use BinaryOperator::*; + let operator = m::operator(match self { + Add => "+", + Sub => "-", + Mul => "×", + Div => "/", + Power => "^", + ConvertTo => "➞", + LessThan => "<", + GreaterThan => ">", + LessOrEqual => "≤", + GreaterOrEqual => "≥", + Equal => "==", + NotEqual => "≠", + LogicalAnd => "&&", + LogicalOr => "||", + }); + match self { - Add => m::space() + m::operator("+") + m::space(), - Sub => m::space() + m::operator("-") + m::space(), - Mul => m::space() + m::operator("×") + m::space(), - Div => m::space() + m::operator("/") + m::space(), - Power => m::operator("^"), - ConvertTo => m::space() + m::operator("➞") + m::space(), - LessThan => m::space() + m::operator("<") + m::space(), - GreaterThan => m::space() + m::operator(">") + m::space(), - LessOrEqual => m::space() + m::operator("≤") + m::space(), - GreaterOrEqual => m::space() + m::operator("≥") + m::space(), - Equal => m::space() + m::operator("==") + m::space(), - NotEqual => m::space() + m::operator("≠") + m::space(), - LogicalAnd => m::space() + m::operator("&&") + m::space(), - LogicalOr => m::space() + m::operator("||") + m::space(), + Power => operator, + _ => m::space() + operator + m::space(), } } } @@ -61,7 +66,7 @@ pub enum StringPart<'a> { Interpolation { span: Span, expr: Box>, - format_specifiers: Option, + format_specifiers: Option<&'a str>, }, } @@ -69,7 +74,7 @@ pub enum StringPart<'a> { pub enum Expression<'a> { Scalar(Span, Number), Identifier(Span, &'a str), - UnitIdentifier(Span, Prefix, String, String), + UnitIdentifier(Span, Prefix, String, String), // can't easily be made &'a str TypedHole(Span), UnaryOperator { op: UnaryOperator, @@ -365,7 +370,7 @@ impl PrettyPrint for TypeExpression { fn pretty_print(&self) -> Markup { match self { TypeExpression::Unity(_) => m::type_identifier("1"), - TypeExpression::TypeIdentifier(_, ident) => m::type_identifier(ident), + TypeExpression::TypeIdentifier(_, ident) => m::type_identifier(ident.clone()), TypeExpression::Multiply(_, lhs, rhs) => { lhs.pretty_print() + m::space() + m::operator("×") + m::space() + rhs.pretty_print() } @@ -507,7 +512,7 @@ impl ReplaceSpans for StringPart<'_> { } => StringPart::Interpolation { span: Span::dummy(), expr: Box::new(expr.replace_spans()), - format_specifiers: format_specifiers.clone(), + format_specifiers: *format_specifiers, }, } } diff --git a/numbat/src/bytecode_interpreter.rs b/numbat/src/bytecode_interpreter.rs index cc1dfd44..636a65b3 100644 --- a/numbat/src/bytecode_interpreter.rs +++ b/numbat/src/bytecode_interpreter.rs @@ -69,15 +69,15 @@ impl BytecodeInterpreter { .rposition(|l| &l.identifier == identifier) { self.vm.add_op1(Op::GetUpvalue, upvalue_position as u16); - } else if LAST_RESULT_IDENTIFIERS.contains(&identifier.as_str()) { + } else if LAST_RESULT_IDENTIFIERS.contains(identifier) { self.vm.add_op(Op::GetLastResult); - } else if let Some(is_foreign) = self.functions.get(identifier) { + } else if let Some(is_foreign) = self.functions.get(*identifier) { let index = self .vm .add_constant(Constant::FunctionReference(if *is_foreign { - FunctionReference::Foreign(identifier.clone()) + FunctionReference::Foreign(identifier.to_string()) } else { - FunctionReference::Normal(identifier.clone()) + FunctionReference::Normal(identifier.to_string()) })); self.vm.add_op1(Op::LoadConstant, index); } else { @@ -178,7 +178,7 @@ impl BytecodeInterpreter { let sorted_exprs = exprs .iter() - .sorted_by_key(|(n, _)| struct_info.fields.get_index_of(n).unwrap()); + .sorted_by_key(|(n, _)| struct_info.fields.get_index_of(*n).unwrap()); for (_, expr) in sorted_exprs.rev() { self.compile_expression(expr)?; @@ -198,7 +198,7 @@ impl BytecodeInterpreter { ); }; - let idx = struct_info.fields.get_index_of(attr).unwrap(); + let idx = struct_info.fields.get_index_of(*attr).unwrap(); self.vm.add_op1(Op::AccessStructField, idx as u16); } @@ -221,7 +221,7 @@ impl BytecodeInterpreter { for part in string_parts { match part { StringPart::Fixed(s) => { - let index = self.vm.add_constant(Constant::String(s.clone())); + let index = self.vm.add_constant(Constant::String(s.to_string())); self.vm.add_op1(Op::LoadConstant, index) } StringPart::Interpolation { @@ -231,7 +231,7 @@ impl BytecodeInterpreter { } => { self.compile_expression(expr)?; let index = self.vm.add_constant(Constant::FormatSpecifiers( - format_specifiers.clone(), + format_specifiers.map(|s| s.to_string()), )); self.vm.add_op1(Op::LoadConstant, index) } @@ -335,7 +335,7 @@ impl BytecodeInterpreter { let current_depth = self.current_depth(); for parameter in parameters { self.locals[current_depth].push(Local { - identifier: parameter.1.clone(), + identifier: parameter.1.to_string(), depth: current_depth, metadata: LocalMetadata::default(), }); @@ -352,7 +352,7 @@ impl BytecodeInterpreter { self.vm.end_function(); - self.functions.insert(name.clone(), false); + self.functions.insert(name.to_string(), false); } Statement::DefineFunction( name, @@ -371,7 +371,7 @@ impl BytecodeInterpreter { self.vm .add_foreign_function(name, parameters.len()..=parameters.len()); - self.functions.insert(name.clone(), true); + self.functions.insert(name.to_string(), true); } Statement::DefineDimension(_name, _dexprs) => { // Declaring a dimension is like introducing a new type. The information @@ -407,7 +407,7 @@ impl BytecodeInterpreter { let constant_idx = self.vm.add_constant(Constant::Unit(Unit::new_base( unit_name, - crate::decorator::get_canonical_unit_name(unit_name.as_str(), &decorators[..]), + crate::decorator::get_canonical_unit_name(unit_name, &decorators[..]), ))); for (name, _) in decorator::name_and_aliases(unit_name, decorators) { self.unit_name_to_constant_index @@ -436,11 +436,7 @@ impl BytecodeInterpreter { let unit_information_idx = self.vm.add_unit_information( unit_name, Some( - &crate::decorator::get_canonical_unit_name( - unit_name.as_str(), - &decorators[..], - ) - .name, + &crate::decorator::get_canonical_unit_name(unit_name, &decorators[..]).name, ), UnitMetadata { type_: type_.to_concrete_type(), // We guarantee that derived-unit definitions do not contain generics, so no TGen(..)s can escape diff --git a/numbat/src/column_formatter.rs b/numbat/src/column_formatter.rs index a025fd99..6165e988 100644 --- a/numbat/src/column_formatter.rs +++ b/numbat/src/column_formatter.rs @@ -37,16 +37,15 @@ impl ColumnFormatter { if min_num_columns < 1 { for entry in entries { - result += Markup::from(FormattedString(OutputType::Normal, format, entry)) - + m::whitespace(" ".repeat(self.padding)); + result += + Markup::from(FormattedString(OutputType::Normal, format, entry.into())) + + m::whitespace(" ".repeat(self.padding)); } return result; } for num_columns in min_num_columns..=self.terminal_width { - // TODO: once we have Rust 1.73, use the div_ceil implementation: - // let num_rows = entries.len().div_ceil(num_columns); - let num_rows = (entries.len() + num_columns - 1) / num_columns; + let num_rows = entries.len().div_ceil(num_columns); let mut table: Vec>> = vec![vec![None; num_columns]; num_rows]; for (idx, entry) in entries.iter().enumerate() { @@ -81,7 +80,7 @@ impl ColumnFormatter { result += Markup::from(FormattedString( OutputType::Normal, format, - (*entry).into(), + entry.to_string().into(), )); result += m::whitespace(" ".repeat(whitespace_length)); } else { diff --git a/numbat/src/datetime.rs b/numbat/src/datetime.rs index 028fd491..ece5056d 100644 --- a/numbat/src/datetime.rs +++ b/numbat/src/datetime.rs @@ -37,7 +37,7 @@ pub fn parse_datetime(input: &str) -> Result { for format in FORMATS { // Try to match the given format plus an additional UTC offset (%z) - if let Ok(dt) = Zoned::strptime(&format!("{format} %z"), input) { + if let Ok(dt) = Zoned::strptime(format!("{format} %z"), input) { return Ok(dt); } diff --git a/numbat/src/decorator.rs b/numbat/src/decorator.rs index 71c94a67..c7fa234f 100644 --- a/numbat/src/decorator.rs +++ b/numbat/src/decorator.rs @@ -1,46 +1,75 @@ -use crate::{prefix_parser::AcceptsPrefix, unit::CanonicalName}; +use crate::{prefix_parser::AcceptsPrefix, span::Span, unit::CanonicalName}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Decorator<'a> { MetricPrefixes, BinaryPrefixes, - Aliases(Vec<(&'a str, Option)>), + Aliases(Vec<(&'a str, Option, Span)>), Url(String), Name(String), Description(String), + Example(String, Option), } -pub fn name_and_aliases<'a>( +/// Get an iterator of data computed from a name and/or its alias's `AcceptsPrefix` and +/// `Span`. If `name` itself is in the list of aliases, then it (or more precisely, the +/// data computed from it) will be placed at the front of the iterator +/// +/// `f` says how to turn a triple of data associated with `name` or an alias, `(name, +/// accepts_prefix, Option)`, into a `T`. The generality is really just here to +/// decide whether to yield `(&'a String, AcceptsPrefix)` or a `(&'a String, +/// AcceptsPrefix, Span)`. +fn name_and_aliases_inner<'a, T: 'a>( name: &'a str, - decorators: &[Decorator<'a>], -) -> Box + 'a> { - let aliases = { - let mut aliases_vec = vec![]; - for decorator in decorators { - if let Decorator::Aliases(aliases) = decorator { - aliases_vec = aliases - .iter() - .map(|(name, accepts_prefix)| { - (*name, accepts_prefix.unwrap_or(AcceptsPrefix::only_long())) - }) - .collect(); + decorators: &'a [Decorator], + f: impl 'a + Fn(&'a str, AcceptsPrefix, Option) -> T, +) -> impl 'a + Iterator { + // contains all the aliases of `name`, starting with `name` itself + let mut aliases_vec = vec![f(name, AcceptsPrefix::only_long(), None)]; + + for decorator in decorators { + if let Decorator::Aliases(aliases) = decorator { + for (n, ap, span) in aliases { + let ap = ap.unwrap_or(AcceptsPrefix::only_long()); + if *n == name { + // use the AcceptsPrefix from the alias, but the span from `name` + // itself; this way we always report a conflicting `name` first + // before reporting any of its aliases. in effect we swallow aliases + // equal to `name` itself (but keep their metadata) + aliases_vec[0] = f(n, ap, None); + } else { + aliases_vec.push(f(n, ap, Some(*span))); + } } } - aliases_vec - }; - - if !aliases.iter().any(|(n, _)| n == &name) { - let name_iter = std::iter::once((name, AcceptsPrefix::only_long())); - Box::new(name_iter.chain(aliases)) - } else { - Box::new(aliases.into_iter()) } + + aliases_vec.into_iter() +} + +/// Returns iterator of `(name_or_alias, accepts_prefix)` for the given name +pub fn name_and_aliases<'a>( + name: &'a str, + decorators: &'a [Decorator], +) -> impl 'a + Iterator { + name_and_aliases_inner(name, decorators, |n, accepts_prefix, _| (n, accepts_prefix)) +} + +/// Returns iterator of `(name_or_alias, accepts_prefix, span)` for the given name +pub fn name_and_aliases_spans<'a>( + name: &'a str, + name_span: Span, + decorators: &'a [Decorator], +) -> impl 'a + Iterator { + name_and_aliases_inner(name, decorators, move |n, accepts_prefix, span| { + (n, accepts_prefix, span.unwrap_or(name_span)) + }) } pub fn get_canonical_unit_name(unit_name: &str, decorators: &[Decorator]) -> CanonicalName { for decorator in decorators { if let Decorator::Aliases(aliases) = decorator { - for (alias, accepts_prefix) in aliases { + for (alias, accepts_prefix, _) in aliases { match accepts_prefix { &Some(ap) if ap.short => { return CanonicalName::new(alias, ap); @@ -89,10 +118,20 @@ pub fn description(decorators: &[Decorator]) -> Option { } } +pub fn examples(decorators: &[Decorator]) -> Vec<(String, Option)> { + let mut examples = Vec::new(); + for decorator in decorators { + if let Decorator::Example(example_code, example_description) = decorator { + examples.push((example_code.clone(), example_description.clone())); + } + } + examples +} + pub fn contains_aliases_with_prefixes(decorates: &[Decorator]) -> bool { for decorator in decorates { if let Decorator::Aliases(aliases) = decorator { - if aliases.iter().any(|(_, prefixes)| prefixes.is_some()) { + if aliases.iter().any(|(_, prefixes, _)| prefixes.is_some()) { return true; } } @@ -110,3 +149,13 @@ pub fn contains_aliases(decorators: &[Decorator]) -> bool { false } + +pub fn contains_examples(decorators: &[Decorator]) -> bool { + for decorator in decorators { + if let Decorator::Example(_, _) = decorator { + return true; + } + } + + false +} diff --git a/numbat/src/help.rs b/numbat/src/help.rs index ea26c297..4dac43b1 100644 --- a/numbat/src/help.rs +++ b/numbat/src/help.rs @@ -62,7 +62,7 @@ pub fn help_markup() -> m::Markup { let mut example_context = Context::new(BuiltinModuleImporter::default()); let _use_prelude_output = evaluate_example(&mut example_context, "use prelude"); for example in examples.iter() { - output += m::text(">>> ") + m::text(example) + m::nl(); + output += m::text(">>> ") + m::text(*example) + m::nl(); output += evaluate_example(&mut example_context, example) + m::nl(); } output diff --git a/numbat/src/interpreter/mod.rs b/numbat/src/interpreter/mod.rs index 998f2f39..b5e93dda 100644 --- a/numbat/src/interpreter/mod.rs +++ b/numbat/src/interpreter/mod.rs @@ -210,7 +210,7 @@ mod tests { .expect("No name resolution errors for inputs in this test suite"); let mut typechecker = crate::typechecker::TypeChecker::default(); let statements_typechecked = typechecker - .check(statements_transformed) + .check(&statements_transformed) .expect("No type check errors for inputs in this test suite"); BytecodeInterpreter::new().interpret_statements( &mut InterpreterSettings::default(), diff --git a/numbat/src/lib.rs b/numbat/src/lib.rs index ec56a233..aa728087 100644 --- a/numbat/src/lib.rs +++ b/numbat/src/lib.rs @@ -47,6 +47,8 @@ mod unit_registry; pub mod value; mod vm; +use std::borrow::Cow; + use bytecode_interpreter::BytecodeInterpreter; use column_formatter::ColumnFormatter; use currency::ExchangeRatesCache; @@ -168,6 +170,7 @@ impl Context { String, Option, Option, + Vec<(String, Option)>, CodeSource, ), > + '_ { @@ -185,6 +188,7 @@ impl Context { .to_string(), meta.description.clone(), meta.url.clone(), + meta.examples.clone(), self.resolver .get_code_source(signature.definition_span.code_source_id), ) @@ -200,15 +204,6 @@ impl Context { } pub fn print_environment(&self) -> Markup { - let mut functions: Vec<_> = self.function_names().collect(); - functions.sort(); - let mut dimensions = Vec::from(self.dimension_names()); - dimensions.sort(); - let mut units = Vec::from(self.unit_names()); - units.sort(); - let mut variables: Vec<_> = self.variable_names().collect(); - variables.sort(); - let mut output = m::empty(); output += m::emphasized("List of functions:") + m::nl(); @@ -253,11 +248,11 @@ impl Context { /// Gets completions for the given word_part /// /// If `add_paren` is true, then an opening paren will be added to the end of function names - pub fn get_completions_for<'a>( + pub fn get_completions_for( &self, - word_part: &'a str, + word_part: &str, add_paren: bool, - ) -> impl Iterator + 'a { + ) -> impl Iterator { const COMMON_METRIC_PREFIXES: &[&str] = &[ "pico", "nano", "micro", "milli", "centi", "kilo", "mega", "giga", "tera", ]; @@ -270,53 +265,60 @@ impl Context { }) .collect(); - let mut words: Vec<_> = KEYWORDS.iter().map(|k| k.to_string()).collect(); + let mut words = Vec::new(); + + let mut add_if_valid = |word: Cow<'_, str>| { + if word.starts_with(word_part) { + words.push(word.into_owned()); + } + }; + + for kw in KEYWORDS { + add_if_valid((*kw).into()); + } for (patterns, _) in UNICODE_INPUT { for pattern in *patterns { - words.push(pattern.to_string()); + add_if_valid((*pattern).into()); } } - { - for variable in self.variable_names() { - words.push(variable.clone()); - } + for variable in self.variable_names() { + add_if_valid(variable.into()); + } - for function in self.function_names() { - if add_paren { - words.push(format!("{function}(")); - } else { - words.push(function.to_string()); - } + for mut function in self.function_names() { + if add_paren { + function.push('('); } + add_if_valid(function.into()); + } - for dimension in self.dimension_names() { - words.push(dimension.clone()); - } + for dimension in self.dimension_names() { + add_if_valid(dimension.into()); + } - for (_, (_, meta)) in self.unit_representations() { - for (unit, accepts_prefix) in meta.aliases { - words.push(unit.clone()); - - // Add some of the common long prefixes for units that accept them. - // We do not add all possible prefixes here in order to keep the - // number of completions to a reasonable size. Also, we do not add - // short prefixes for units that accept them, as that leads to lots - // and lots of 2-3 character words. - if accepts_prefix.long && meta.metric_prefixes { - for prefix in &metric_prefixes { - words.push(format!("{prefix}{unit}")); - } + for (_, (_, meta)) in self.unit_representations() { + for (unit, accepts_prefix) in meta.aliases { + // Add some of the common long prefixes for units that accept them. + // We do not add all possible prefixes here in order to keep the + // number of completions to a reasonable size. Also, we do not add + // short prefixes for units that accept them, as that leads to lots + // and lots of 2-3 character words. + if accepts_prefix.long && meta.metric_prefixes { + for prefix in &metric_prefixes { + add_if_valid(format!("{prefix}{unit}").into()); } } + + add_if_valid(unit.into()); } } words.sort(); words.dedup(); - words.into_iter().filter(move |w| w.starts_with(word_part)) + words.into_iter() } pub fn print_info_for_keyword(&mut self, keyword: &str) -> Markup { @@ -336,7 +338,8 @@ impl Context { .ok() .map(|(_, md)| md) { - let mut help = m::text("Unit: ") + m::unit(md.name.as_deref().unwrap_or(keyword)); + let mut help = + m::text("Unit: ") + m::unit(md.name.unwrap_or_else(|| keyword.to_string())); if let Some(url) = &md.url { help += m::text(" (") + m::string(url_encode(url)) + m::text(")"); } @@ -357,12 +360,13 @@ impl Context { let desc = "Description: "; let mut lines = description.lines(); help += m::text(desc) - + m::text(lines.by_ref().next().unwrap_or("").trim()) + + m::text(lines.by_ref().next().unwrap_or("").trim().to_string()) + m::nl(); for line in lines { - help += - m::whitespace(" ".repeat(desc.len())) + m::text(line.trim()) + m::nl(); + help += m::whitespace(" ".repeat(desc.len())) + + m::text(line.trim().to_string()) + + m::nl(); } } @@ -385,17 +389,17 @@ impl Context { if !prefix.is_none() { help += m::nl() + m::value("1 ") - + m::unit(keyword) + + m::unit(keyword.to_string()) + m::text(" = ") + m::value(prefix.factor().pretty_print()) + m::space() - + m::unit(&full_name); + + m::unit(full_name.to_string()); } if let Some(BaseUnitAndFactor(prod, num)) = x { help += m::nl() + m::value("1 ") - + m::unit(&full_name) + + m::unit(full_name.to_string()) + m::text(" = ") + m::value(num.pretty_print()) + m::space() @@ -407,7 +411,8 @@ impl Context { Some(m::FormatType::Unit), ); } else { - help += m::nl() + m::unit(&full_name) + m::text(" is a base unit"); + help += + m::nl() + m::unit(full_name.to_string()) + m::text(" is a base unit"); } }; @@ -420,9 +425,9 @@ impl Context { if let Some(l) = self.interpreter.lookup_global(keyword) { let mut help = m::text("Variable: "); if let Some(name) = &l.metadata.name { - help += m::text(name); + help += m::text(name.clone()); } else { - help += m::identifier(keyword); + help += m::identifier(keyword.to_string()); } if let Some(url) = &l.metadata.url { help += m::text(" (") + m::string(url_encode(url)) + m::text(")"); @@ -432,11 +437,14 @@ impl Context { if let Some(description) = &l.metadata.description { let desc = "Description: "; let mut lines = description.lines(); - help += - m::text(desc) + m::text(lines.by_ref().next().unwrap_or("").trim()) + m::nl(); + help += m::text(desc) + + m::text(lines.by_ref().next().unwrap_or("").trim().to_string()) + + m::nl(); for line in lines { - help += m::whitespace(" ".repeat(desc.len())) + m::text(line.trim()) + m::nl(); + help += m::whitespace(" ".repeat(desc.len())) + + m::text(line.trim().to_string()) + + m::nl(); } } @@ -465,9 +473,9 @@ impl Context { let mut help = m::text("Function: "); if let Some(name) = &metadata.name { - help += m::text(name); + help += m::text(name.to_string()); } else { - help += m::identifier(keyword); + help += m::identifier(keyword.to_string()); } if let Some(url) = &metadata.url { help += m::text(" (") + m::string(url_encode(url)) + m::text(")"); @@ -482,11 +490,14 @@ impl Context { if let Some(description) = &metadata.description { let desc = "Description: "; let mut lines = description.lines(); - help += - m::text(desc) + m::text(lines.by_ref().next().unwrap_or("").trim()) + m::nl(); + help += m::text(desc) + + m::text(lines.by_ref().next().unwrap_or("").trim().to_string()) + + m::nl(); for line in lines { - help += m::whitespace(" ".repeat(desc.len())) + m::text(line.trim()) + m::nl(); + help += m::whitespace(" ".repeat(desc.len())) + + m::text(line.trim().to_string()) + + m::nl(); } } @@ -584,7 +595,7 @@ impl Context { let result = self .typechecker - .check(transformed_statements) + .check(&transformed_statements) .map_err(|err| NumbatError::TypeCheckError(*err)); if result.is_err() { diff --git a/numbat/src/markup.rs b/numbat/src/markup.rs index 850e3908..563fbd76 100644 --- a/numbat/src/markup.rs +++ b/numbat/src/markup.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{borrow::Cow, fmt::Display}; #[derive(Debug, Copy, Clone, PartialEq)] pub enum FormatType { @@ -23,7 +23,7 @@ pub enum OutputType { } #[derive(Debug, Clone, PartialEq)] -pub struct FormattedString(pub OutputType, pub FormatType, pub String); +pub struct FormattedString(pub OutputType, pub FormatType, pub Cow<'static, str>); #[derive(Debug, Clone, Default, PartialEq)] pub struct Markup(pub Vec); @@ -43,10 +43,9 @@ impl Display for Markup { impl std::ops::Add for Markup { type Output = Markup; - fn add(self, rhs: Self) -> Self::Output { - let mut res = self.0; - res.extend_from_slice(&rhs.0); - Markup(res) + fn add(mut self, rhs: Self) -> Self::Output { + self.0.extend(rhs.0); + self } } @@ -66,7 +65,7 @@ pub fn space() -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Whitespace, - " ".to_string(), + " ".into(), )) } @@ -74,99 +73,99 @@ pub fn empty() -> Markup { Markup::default() } -pub fn whitespace(text: impl AsRef) -> Markup { +pub fn whitespace(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Whitespace, - text.as_ref().to_string(), + text.into(), )) } -pub fn emphasized(text: impl AsRef) -> Markup { +pub fn emphasized(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Emphasized, - text.as_ref().to_string(), + text.into(), )) } -pub fn dimmed(text: impl AsRef) -> Markup { +pub fn dimmed(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Dimmed, - text.as_ref().to_string(), + text.into(), )) } -pub fn text(text: impl AsRef) -> Markup { +pub fn text(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Text, - text.as_ref().to_string(), + text.into(), )) } -pub fn string(text: impl AsRef) -> Markup { +pub fn string(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::String, - text.as_ref().to_string(), + text.into(), )) } -pub fn keyword(text: impl AsRef) -> Markup { +pub fn keyword(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Keyword, - text.as_ref().to_string(), + text.into(), )) } -pub fn value(text: impl AsRef) -> Markup { +pub fn value(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Value, - text.as_ref().to_string(), + text.into(), )) } -pub fn unit(text: impl AsRef) -> Markup { +pub fn unit(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Unit, - text.as_ref().to_string(), + text.into(), )) } -pub fn identifier(text: impl AsRef) -> Markup { +pub fn identifier(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Identifier, - text.as_ref().to_string(), + text.into(), )) } -pub fn type_identifier(text: impl AsRef) -> Markup { +pub fn type_identifier(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::TypeIdentifier, - text.as_ref().to_string(), + text.into(), )) } -pub fn operator(text: impl AsRef) -> Markup { +pub fn operator(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Operator, - text.as_ref().to_string(), + text.into(), )) } -pub fn decorator(text: impl AsRef) -> Markup { +pub fn decorator(text: impl Into>) -> Markup { Markup::from(FormattedString( OutputType::Normal, FormatType::Decorator, - text.as_ref().to_string(), + text.into(), )) } @@ -206,6 +205,10 @@ pub struct PlainTextFormatter; impl Formatter for PlainTextFormatter { fn format_part(&self, FormattedString(_, _, text): &FormattedString) -> String { - text.clone() + text.to_string() } } + +pub fn plain_text_format(m: &Markup, indent: bool) -> String { + PlainTextFormatter {}.format(m, indent) +} diff --git a/numbat/src/parser.rs b/numbat/src/parser.rs index e9da7e02..e2146c6e 100644 --- a/numbat/src/parser.rs +++ b/numbat/src/parser.rs @@ -96,6 +96,9 @@ pub enum ParseErrorKind { #[error("Trailing '=' sign. Use `let {0} = …` if you intended to define a new constant.")] TrailingEqualSign(String), + #[error("Trailing '=' sign. Use `fn {0} = …` if you intended to define a function.")] + TrailingEqualSignFunction(String), + #[error("Expected identifier after 'let' keyword")] ExpectedIdentifierAfterLet, @@ -198,6 +201,9 @@ pub enum ParseErrorKind { #[error("Aliases cannot be used on functions.")] AliasUsedOnFunction, + #[error("Example decorators can only be used on functions.")] + ExampleUsedOnUnsuitableKind, + #[error("Numerical overflow in dimension exponent")] OverflowInDimensionExponent, @@ -310,12 +316,22 @@ impl<'a> Parser<'a> { break; } TokenKind::Equal => { + let last_token = self.last(tokens).unwrap(); + + let mut input = String::new(); + for token in tokens.iter().take(self.current) { + input.push_str(token.lexeme); + } + errors.push(ParseError { - kind: ParseErrorKind::TrailingEqualSign( - self.last(tokens).unwrap().lexeme.to_owned(), - ), + kind: if last_token.kind == TokenKind::RightParen { + ParseErrorKind::TrailingEqualSignFunction(input) + } else { + ParseErrorKind::TrailingEqualSign(input) + }, span: self.peek(tokens).span, }); + self.recover_from_error(tokens); } _ => { @@ -369,14 +385,17 @@ impl<'a> Parser<'a> { fn list_of_aliases( &mut self, tokens: &[Token<'a>], - ) -> Result)>> { + ) -> Result, Span)>> { if self.match_exact(tokens, TokenKind::RightParen).is_some() { return Ok(vec![]); } - let mut identifiers = vec![(self.identifier(tokens)?, self.accepts_prefix(tokens)?)]; + let span = self.peek(tokens).span; + let mut identifiers = vec![(self.identifier(tokens)?, self.accepts_prefix(tokens)?, span)]; + while self.match_exact(tokens, TokenKind::Comma).is_some() { - identifiers.push((self.identifier(tokens)?, self.accepts_prefix(tokens)?)); + let span = self.peek(tokens).span; + identifiers.push((self.identifier(tokens)?, self.accepts_prefix(tokens)?, span)); } if self.match_exact(tokens, TokenKind::RightParen).is_none() { @@ -455,6 +474,14 @@ impl<'a> Parser<'a> { span: self.peek(tokens).span, }); } + + if decorator::contains_examples(&self.decorator_stack) { + return Err(ParseError { + kind: ParseErrorKind::ExampleUsedOnUnsuitableKind, + span: self.peek(tokens).span, + }); + } + std::mem::swap(&mut decorators, &mut self.decorator_stack); } @@ -734,6 +761,55 @@ impl<'a> Parser<'a> { }); } } + "example" => { + if self.match_exact(tokens, TokenKind::LeftParen).is_some() { + if let Some(token_code) = self.match_exact(tokens, TokenKind::StringFixed) { + if self.match_exact(tokens, TokenKind::Comma).is_some() { + //Code and description + if let Some(token_description) = + self.match_exact(tokens, TokenKind::StringFixed) + { + if self.match_exact(tokens, TokenKind::RightParen).is_none() { + return Err(ParseError::new( + ParseErrorKind::MissingClosingParen, + self.peek(tokens).span, + )); + } + + Decorator::Example( + strip_and_escape(token_code.lexeme), + Some(strip_and_escape(token_description.lexeme)), + ) + } else { + return Err(ParseError { + kind: ParseErrorKind::ExpectedString, + span: self.peek(tokens).span, + }); + } + } else { + //Code but no description + if self.match_exact(tokens, TokenKind::RightParen).is_none() { + return Err(ParseError::new( + ParseErrorKind::MissingClosingParen, + self.peek(tokens).span, + )); + } + + Decorator::Example(strip_and_escape(token_code.lexeme), None) + } + } else { + return Err(ParseError { + kind: ParseErrorKind::ExpectedString, + span: self.peek(tokens).span, + }); + } + } else { + return Err(ParseError { + kind: ParseErrorKind::ExpectedLeftParenAfterDecorator, + span: self.peek(tokens).span, + }); + } + } _ => { return Err(ParseError { kind: ParseErrorKind::UnknownDecorator, @@ -768,6 +844,13 @@ impl<'a> Parser<'a> { let unit_name = identifier.lexeme; + if decorator::contains_examples(&self.decorator_stack) { + return Err(ParseError { + kind: ParseErrorKind::ExampleUsedOnUnsuitableKind, + span: self.peek(tokens).span, + }); + } + let mut decorators = vec![]; std::mem::swap(&mut decorators, &mut self.decorator_stack); @@ -1578,7 +1661,7 @@ impl<'a> Parser<'a> { let format_specifiers = self .match_exact(tokens, TokenKind::StringInterpolationSpecifiers) - .map(|token| token.lexeme.to_owned()); + .map(|token| token.lexeme); parts.push(StringPart::Interpolation { span: expr.full_span(), @@ -1987,9 +2070,12 @@ mod tests { use std::fmt::Write; use super::*; - use crate::ast::{ - binop, boolean, conditional, factorial, identifier, list, logical_neg, negate, scalar, - struct_, ReplaceSpans, + use crate::{ + ast::{ + binop, boolean, conditional, factorial, identifier, list, logical_neg, negate, scalar, + struct_, ReplaceSpans, + }, + span::ByteIndex, }; #[track_caller] @@ -2438,7 +2524,26 @@ mod tests { )), decorators: vec![ decorator::Decorator::Name("myvar".into()), - decorator::Decorator::Aliases(vec![("foo", None), ("bar", None)]), + decorator::Decorator::Aliases(vec![ + ( + "foo", + None, + Span { + start: ByteIndex(24), + end: ByteIndex(27), + code_source_id: 0, + }, + ), + ( + "bar", + None, + Span { + start: ByteIndex(29), + end: ByteIndex(32), + code_source_id: 0, + }, + ), + ]), ], }), ); @@ -2793,6 +2898,24 @@ mod tests { }, ); + parse_as( + &["@name(\"Some function\") @example(\"some_function(2)\", \"Use this function:\") @example(\"let some_var = some_function(0)\") fn some_function(x) = 1"], + Statement::DefineFunction { + function_name_span: Span::dummy(), + function_name: "some_function".into(), + type_parameters: vec![], + parameters: vec![(Span::dummy(), "x".into(), None)], + body: Some(scalar!(1.0)), + local_variables: vec![], + return_type_annotation: None, + decorators: vec![ + decorator::Decorator::Name("Some function".into()), + decorator::Decorator::Example("some_function(2)".into(), Some("Use this function:".into())), + decorator::Decorator::Example("let some_var = some_function(0)".into(), None), + ], + }, + ); + parse_as( &["fn double_kef(x) = y where y = x * 2"], Statement::DefineFunction { @@ -3279,7 +3402,7 @@ mod tests { StringPart::Interpolation { span: Span::dummy(), expr: Box::new(binop!(scalar!(1.0), Add, scalar!(2.0))), - format_specifiers: Some(":0.2".to_string()), + format_specifiers: Some(":0.2"), }, ], ), diff --git a/numbat/src/prefix_parser.rs b/numbat/src/prefix_parser.rs index 9f11e600..93d92019 100644 --- a/numbat/src/prefix_parser.rs +++ b/numbat/src/prefix_parser.rs @@ -1,5 +1,5 @@ +use indexmap::IndexMap; use std::collections::HashMap; - use std::sync::OnceLock; use crate::span::Span; @@ -52,6 +52,25 @@ impl AcceptsPrefix { } } +/// The spans associated with an alias passed to `@aliases` +#[derive(Debug, Clone, Copy)] +pub(crate) struct AliasSpanInfo { + /// The span of the name to which the alias refers + pub(crate) name_span: Span, + /// The span of the alias itself (in an `@aliases` decorator) + pub(crate) alias_span: Span, +} + +impl AliasSpanInfo { + #[cfg(test)] + fn dummy() -> Self { + Self { + name_span: Span::dummy(), + alias_span: Span::dummy(), + } + } +} + #[derive(Debug, Clone)] struct UnitInfo { definition_span: Span, @@ -63,10 +82,7 @@ struct UnitInfo { #[derive(Debug, Clone)] pub struct PrefixParser { - units: HashMap, - // This is the exact same information as in the "units" hashmap, only faster to iterate over. - // TODO: maybe use an external crate for this (e.g. indexmap?) - units_vec: Vec<(String, UnitInfo)>, + units: IndexMap, other_identifiers: HashMap, @@ -76,8 +92,7 @@ pub struct PrefixParser { impl PrefixParser { pub fn new() -> Self { Self { - units: HashMap::new(), - units_vec: Vec::new(), + units: IndexMap::new(), other_identifiers: HashMap::new(), reserved_identifiers: &["_", "ans"], } @@ -152,23 +167,23 @@ impl PrefixParser { fn ensure_name_is_available( &self, name: &str, - conflict_span: Span, + definition_span: Span, clash_with_other_identifiers: bool, ) -> Result<()> { if self.reserved_identifiers.contains(&name) { - return Err(NameResolutionError::ReservedIdentifier(conflict_span)); + return Err(NameResolutionError::ReservedIdentifier(definition_span)); } if clash_with_other_identifiers { if let Some(original_span) = self.other_identifiers.get(name) { - return Err(self.identifier_clash_error(name, conflict_span, *original_span)); + return Err(self.identifier_clash_error(name, definition_span, *original_span)); } } match self.parse(name) { PrefixParserResult::Identifier(_) => Ok(()), PrefixParserResult::UnitIdentifier(original_span, _, _, _) => { - Err(self.identifier_clash_error(name, conflict_span, original_span)) + Err(self.identifier_clash_error(name, definition_span, original_span)) } } } @@ -180,9 +195,12 @@ impl PrefixParser { metric: bool, binary: bool, full_name: &str, - definition_span: Span, + AliasSpanInfo { + name_span, + alias_span, + }: AliasSpanInfo, ) -> Result<()> { - self.ensure_name_is_available(unit_name, definition_span, true)?; + self.ensure_name_is_available(unit_name, alias_span, true)?; for (prefix_long, prefixes_short, prefix) in Self::prefixes() { if !(prefix.is_metric() && metric || prefix.is_binary() && binary) { @@ -192,7 +210,7 @@ impl PrefixParser { if accepts_prefix.long { self.ensure_name_is_available( &format!("{prefix_long}{unit_name}"), - definition_span, + alias_span, true, )?; } @@ -200,7 +218,7 @@ impl PrefixParser { for prefix_short in *prefixes_short { self.ensure_name_is_available( &format!("{prefix_short}{unit_name}"), - definition_span, + alias_span, true, )?; } @@ -208,14 +226,13 @@ impl PrefixParser { } let unit_info = UnitInfo { - definition_span, + definition_span: name_span, accepts_prefix, metric_prefixes: metric, binary_prefixes: binary, full_name: full_name.into(), }; self.units.insert(unit_name.into(), unit_info.clone()); - self.units_vec.push((unit_name.into(), unit_info)); Ok(()) } @@ -233,12 +250,12 @@ impl PrefixParser { return PrefixParserResult::UnitIdentifier( info.definition_span, Prefix::none(), - input.into(), + input.to_string(), info.full_name.clone(), ); } - for (unit_name, info) in &self.units_vec { + for (unit_name, info) in &self.units { if !input.ends_with(unit_name.as_str()) { continue; } @@ -294,7 +311,7 @@ mod tests { true, false, "meter", - Span::dummy(), + AliasSpanInfo::dummy(), ) .unwrap(); prefix_parser @@ -304,7 +321,7 @@ mod tests { true, false, "meter", - Span::dummy(), + AliasSpanInfo::dummy(), ) .unwrap(); @@ -315,7 +332,7 @@ mod tests { true, true, "byte", - Span::dummy(), + AliasSpanInfo::dummy(), ) .unwrap(); prefix_parser @@ -325,7 +342,7 @@ mod tests { true, true, "byte", - Span::dummy(), + AliasSpanInfo::dummy(), ) .unwrap(); @@ -336,7 +353,7 @@ mod tests { false, false, "me", - Span::dummy(), + AliasSpanInfo::dummy(), ) .unwrap(); diff --git a/numbat/src/prefix_transformer.rs b/numbat/src/prefix_transformer.rs index a24c3dbc..5db124c6 100644 --- a/numbat/src/prefix_transformer.rs +++ b/numbat/src/prefix_transformer.rs @@ -2,7 +2,7 @@ use crate::{ ast::{DefineVariable, Expression, Statement, StringPart}, decorator::{self, Decorator}, name_resolution::NameResolutionError, - prefix_parser::{PrefixParser, PrefixParserResult}, + prefix_parser::{AliasSpanInfo, PrefixParser, PrefixParserResult}, span::Span, }; @@ -135,20 +135,26 @@ impl Transformer { pub(crate) fn register_name_and_aliases( &mut self, name: &str, + name_span: Span, decorators: &[Decorator], - conflict_span: Span, ) -> Result<()> { let mut unit_names = vec![]; let metric_prefixes = Self::has_decorator(decorators, Decorator::MetricPrefixes); let binary_prefixes = Self::has_decorator(decorators, Decorator::BinaryPrefixes); - for (alias, accepts_prefix) in decorator::name_and_aliases(name, decorators) { + + for (alias, accepts_prefix, alias_span) in + decorator::name_and_aliases_spans(name, name_span, decorators) + { self.prefix_parser.add_unit( alias, accepts_prefix, metric_prefixes, binary_prefixes, name, - conflict_span, + AliasSpanInfo { + name_span, + alias_span, + }, )?; unit_names.push(alias.to_string()); } @@ -189,7 +195,7 @@ impl Transformer { Ok(match statement { Statement::Expression(expr) => Statement::Expression(self.transform_expression(expr)), Statement::DefineBaseUnit(span, name, dexpr, decorators) => { - self.register_name_and_aliases(name, &decorators, span)?; + self.register_name_and_aliases(name, span, &decorators)?; Statement::DefineBaseUnit(span, name, dexpr, decorators) } Statement::DefineDerivedUnit { @@ -200,7 +206,7 @@ impl Transformer { type_annotation, decorators, } => { - self.register_name_and_aliases(identifier, &decorators, identifier_span)?; + self.register_name_and_aliases(identifier, identifier_span, &decorators)?; Statement::DefineDerivedUnit { identifier_span, identifier, diff --git a/numbat/src/product.rs b/numbat/src/product.rs index 2e907dc5..ce0fb9d1 100644 --- a/numbat/src/product.rs +++ b/numbat/src/product.rs @@ -54,7 +54,7 @@ impl Product ProductIter { - ProductIter { - inner: self.factors.iter(), - } + pub fn iter(&self) -> std::slice::Iter<'_, Factor> { + self.factors.iter() } #[cfg(test)] @@ -245,13 +243,11 @@ impl Eq } impl IntoIterator for Product { - type IntoIter = ProductIntoIter; + type IntoIter = as IntoIterator>::IntoIter; type Item = Factor; fn into_iter(self) -> Self::IntoIter { - ProductIntoIter { - inner: self.factors.into_iter(), - } + self.factors.into_iter() } } @@ -277,30 +273,6 @@ impl std::iter::Pr } } -pub struct ProductIter<'a, Factor> { - inner: std::slice::Iter<'a, Factor>, -} - -impl<'a, Factor> Iterator for ProductIter<'a, Factor> { - type Item = &'a Factor; - - fn next(&mut self) -> Option { - self.inner.next() - } -} - -pub struct ProductIntoIter { - inner: std::vec::IntoIter, -} - -impl Iterator for ProductIntoIter { - type Item = Factor; - - fn next(&mut self) -> Option { - self.inner.next() - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/numbat/src/quantity.rs b/numbat/src/quantity.rs index eb3afcb3..e0509a2a 100644 --- a/numbat/src/quantity.rs +++ b/numbat/src/quantity.rs @@ -192,7 +192,8 @@ impl Quantity { let group_representative = group_as_unit .iter() .max_by(|&f1, &f2| { - // TODO: describe this heuristic + // prefer base units over non-base. if multiple base units, prefer + // those with a larger exponent (f1.unit_id.is_base().cmp(&f2.unit_id.is_base())) .then(f1.exponent.cmp(&f2.exponent)) }) diff --git a/numbat/src/registry.rs b/numbat/src/registry.rs index 7ef0e24f..2e8fa086 100644 --- a/numbat/src/registry.rs +++ b/numbat/src/registry.rs @@ -24,9 +24,6 @@ pub type Result = std::result::Result; pub type BaseEntry = String; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct BaseIndex(isize); - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct BaseRepresentationFactor(pub BaseEntry, pub Exponent); diff --git a/numbat/src/resolver.rs b/numbat/src/resolver.rs index e581ce11..5d346d31 100644 --- a/numbat/src/resolver.rs +++ b/numbat/src/resolver.rs @@ -49,7 +49,7 @@ pub struct Resolver { pub files: SimpleFiles, text_code_source_count: usize, internal_code_source_count: usize, - imported_modules: Vec, + pub imported_modules: Vec, codesources: HashMap, } diff --git a/numbat/src/tokenizer.rs b/numbat/src/tokenizer.rs index d5521a6d..eaa8e227 100644 --- a/numbat/src/tokenizer.rs +++ b/numbat/src/tokenizer.rs @@ -22,10 +22,7 @@ pub enum TokenizerErrorKind { ExpectedDigit { character: Option }, #[error("Expected base-{base} digit")] - ExpectedDigitInBase { - base: usize, - character: Option, - }, + ExpectedDigitInBase { base: u8, character: Option }, #[error("Unterminated string")] UnterminatedString, @@ -125,7 +122,7 @@ pub enum TokenKind { // Variable-length tokens Number, - IntegerWithBase(usize), + IntegerWithBase(u8), Identifier, // A normal string without interpolation: `"hello world"` @@ -378,6 +375,18 @@ impl Tokenizer { } fn scan_single_token<'a>(&mut self, input: &'a str) -> Result>> { + fn is_ascii_hex_digit(c: char) -> bool { + c.is_ascii_hexdigit() + } + + fn is_ascii_octal_digit(c: char) -> bool { + ('0'..='7').contains(&c) + } + + fn is_ascii_binary_digit(c: char) -> bool { + c == '0' || c == '1' + } + static KEYWORDS: OnceLock> = OnceLock::new(); let keywords = KEYWORDS.get_or_init(|| { let mut m = HashMap::new(); @@ -463,18 +472,17 @@ impl Tokenizer { .map(|c| c == 'x' || c == 'o' || c == 'b') .unwrap_or(false) => { - let (base, is_digit_in_base): (_, Box bool>) = - match self.peek(input).unwrap() { - 'x' => (16, Box::new(|c| c.is_ascii_hexdigit())), - 'o' => (8, Box::new(|c| ('0'..='7').contains(&c))), - 'b' => (2, Box::new(|c| c == '0' || c == '1')), - _ => unreachable!(), - }; + let (base, is_digit_in_base) = match self.peek(input).unwrap() { + 'x' => (16, is_ascii_hex_digit as fn(char) -> bool), + 'o' => (8, is_ascii_octal_digit as _), + 'b' => (2, is_ascii_binary_digit as _), + _ => unreachable!(), + }; self.advance(input); // skip over the x/o/b - // If the first character is not a digits, that's an error. - if !self.peek(input).map(&is_digit_in_base).unwrap_or(false) { + // If the first character is not a digit, that's an error. + if !self.peek(input).map(is_digit_in_base).unwrap_or(false) { return tokenizer_error( self.current, TokenizerErrorKind::ExpectedDigitInBase { diff --git a/numbat/src/traversal.rs b/numbat/src/traversal.rs index c648ba32..a7c883c1 100644 --- a/numbat/src/traversal.rs +++ b/numbat/src/traversal.rs @@ -11,7 +11,7 @@ impl ForAllTypeSchemes for StructInfo { } } -impl ForAllTypeSchemes for Expression { +impl ForAllTypeSchemes for Expression<'_> { fn for_all_type_schemes(&mut self, f: &mut dyn FnMut(&mut TypeScheme)) { match self { Expression::Scalar(_, _, type_) => f(type_), @@ -143,7 +143,7 @@ impl ForAllExpressions for Statement<'_> { } } -impl ForAllExpressions for Expression { +impl ForAllExpressions for Expression<'_> { fn for_all_expressions(&self, f: &mut dyn FnMut(&Expression)) { f(self); match self { diff --git a/numbat/src/typechecker/constraints.rs b/numbat/src/typechecker/constraints.rs index d6481cd4..24546782 100644 --- a/numbat/src/typechecker/constraints.rs +++ b/numbat/src/typechecker/constraints.rs @@ -160,6 +160,7 @@ pub enum TrivialResolution { } impl TrivialResolution { + #[allow(clippy::wrong_self_convention)] pub fn is_trivially_violated(self) -> bool { matches!(self, TrivialResolution::Violated) } @@ -296,10 +297,7 @@ impl Constraint { Constraint::EqualScalar(dtype) => match dtype.split_first_factor() { Some(((DTypeFactor::TVar(tv), k), rest)) => { let result = DType::from_factors( - &rest - .iter() - .map(|(f, j)| (f.clone(), -j / k)) - .collect::>(), + rest.iter().map(|(f, j)| (f.clone(), -j / k)).collect(), ); Some(Satisfied::with_substitution(Substitution::single( tv.clone(), diff --git a/numbat/src/typechecker/environment.rs b/numbat/src/typechecker/environment.rs index f37c2c45..0f885982 100644 --- a/numbat/src/typechecker/environment.rs +++ b/numbat/src/typechecker/environment.rs @@ -67,6 +67,7 @@ pub struct FunctionMetadata { pub name: Option, pub url: Option, pub description: Option, + pub examples: Vec<(String, Option)>, } #[derive(Clone, Debug)] diff --git a/numbat/src/typechecker/mod.rs b/numbat/src/typechecker/mod.rs index 052cc240..faa13588 100644 --- a/numbat/src/typechecker/mod.rs +++ b/numbat/src/typechecker/mod.rs @@ -65,16 +65,16 @@ pub struct TypeChecker { constraints: ConstraintSet, } -struct ElaborationDefinitionArgs<'a> { +struct ElaborationDefinitionArgs<'a, 'b> { identifier_span: Span, - expr: &'a ast::Expression<'a>, + expr: &'b ast::Expression<'a>, type_annotation_span: Option, - type_annotation: Option<&'a TypeAnnotation>, - operation: &'a str, + type_annotation: Option<&'b TypeAnnotation>, + operation: &'b str, expected_name: &'static str, actual_name: &'static str, actual_name_for_fix: &'static str, - elaboration_kind: &'a str, + elaboration_kind: &'b str, } impl TypeChecker { @@ -118,14 +118,15 @@ impl TypeChecker { } } - let mut dtype: DType = self + let mut factors = self .registry .get_base_representation(dexpr) - .map(|br| br.into()) - .map_err(TypeCheckError::RegistryError)?; + .map(DType::from) + .map_err(TypeCheckError::RegistryError)? + .into_factors(); // Replace BaseDimension("D") with TVar("D") for all type parameters - for (factor, _) in dtype.factors.iter_mut() { + for (factor, _) in &mut factors { *factor = match factor { DTypeFactor::BaseDimension(ref n) if self @@ -140,7 +141,7 @@ impl TypeChecker { } } - Ok(Type::Dimension(dtype)) + Ok(Type::Dimension(DType::from_factors(factors))) } TypeAnnotation::Bool(_) => Ok(Type::Boolean), TypeAnnotation::String(_) => Ok(Type::String), @@ -172,28 +173,28 @@ impl TypeChecker { })?) } - fn get_proper_function_reference( + fn get_proper_function_reference<'a>( &self, - expr: &ast::Expression, - ) -> Option<(String, &FunctionSignature)> { + expr: &ast::Expression<'a>, + ) -> Option<(&'a str, &FunctionSignature)> { match expr { ast::Expression::Identifier(_, name) => self .env .get_function_info(name) - .map(|(signature, _)| (name.to_string(), signature)), + .map(|(signature, _)| (*name, signature)), _ => None, } } - fn proper_function_call( + fn proper_function_call<'a>( &mut self, span: &Span, full_span: &Span, - function_name: &str, + function_name: &'a str, signature: &FunctionSignature, - arguments: Vec, + arguments: Vec>, argument_types: Vec, - ) -> Result { + ) -> Result> { let FunctionSignature { name: _, definition_span, @@ -288,13 +289,16 @@ impl TypeChecker { Ok(typed_ast::Expression::FunctionCall( *span, *full_span, - function_name.into(), + function_name, arguments, TypeScheme::concrete(return_type.as_ref().clone()), )) } - fn elaborate_expression(&mut self, ast: &ast::Expression) -> Result { + fn elaborate_expression<'a>( + &mut self, + ast: &ast::Expression<'a>, + ) -> Result> { Ok(match ast { ast::Expression::Scalar(span, n) if n.to_f64().is_zero() || n.to_f64().is_infinite() || n.to_f64().is_nan() => @@ -325,7 +329,7 @@ impl TypeChecker { } }; - typed_ast::Expression::Identifier(*span, name.to_string(), TypeScheme::concrete(ty)) + typed_ast::Expression::Identifier(*span, name, TypeScheme::concrete(ty)) } ast::Expression::UnitIdentifier(span, prefix, name, full_name) => { let type_scheme = self.identifier_type(*span, name)?.clone(); @@ -780,7 +784,7 @@ impl TypeChecker { self.proper_function_call( span, full_span, - &name, + name, &signature, arguments_checked, argument_types, @@ -880,7 +884,7 @@ impl TypeChecker { format_specifiers, } => Ok(typed_ast::StringPart::Interpolation { span: *span, - format_specifiers: format_specifiers.clone(), + format_specifiers: format_specifiers.as_ref().copied(), expr: Box::new(self.elaborate_expression(expr)?), }), }) @@ -933,7 +937,7 @@ impl TypeChecker { let name = *name; let fields_checked = fields .iter() - .map(|(_, n, v)| Ok((n.to_string(), self.elaborate_expression(v)?))) + .map(|(_, n, v)| Ok((*n, self.elaborate_expression(v)?))) .collect::>>()?; let Some(struct_info) = self.structs.get(name).cloned() else { @@ -958,12 +962,12 @@ impl TypeChecker { )); } - let Some((expected_field_span, expected_type)) = struct_info.fields.get(field) + let Some((expected_field_span, expected_type)) = struct_info.fields.get(*field) else { return Err(Box::new(TypeCheckError::UnknownFieldInStructInstantiation( *span, struct_info.definition_span, - field.clone(), + field.to_string(), struct_info.name.clone(), ))); }; @@ -986,7 +990,7 @@ impl TypeChecker { let missing_fields = { let mut fields = struct_info.fields.clone(); - fields.retain(|f, _| !seen_fields.contains_key(f)); + fields.retain(|f, _| !seen_fields.contains_key(&f.as_str())); fields.into_iter().map(|(n, (_, t))| (n, t)).collect_vec() }; @@ -1050,7 +1054,7 @@ impl TypeChecker { *ident_span, *full_span, Box::new(expr_checked), - field_name.to_owned(), + field_name, TypeScheme::concrete(type_), TypeScheme::concrete(field_type), ) @@ -1105,10 +1109,10 @@ impl TypeChecker { }) } - fn _elaborate_inner( + fn _elaborate_inner<'a>( &mut self, - definition: ElaborationDefinitionArgs, - ) -> Result<(typed_ast::Expression, typed_ast::Type)> { + definition: ElaborationDefinitionArgs<'a, '_>, + ) -> Result<(typed_ast::Expression<'a>, typed_ast::Type)> { let ElaborationDefinitionArgs { identifier_span, expr, @@ -1219,8 +1223,8 @@ impl TypeChecker { } Ok(typed_ast::DefineVariable( - identifier.to_string(), - decorators.to_owned(), + identifier, + decorators.clone(), expr_checked, type_annotation.clone(), TypeScheme::concrete(type_deduced), @@ -1284,7 +1288,7 @@ impl TypeChecker { } typed_ast::Statement::DefineBaseUnit( - unit_name.to_string(), + unit_name, decorators.clone(), type_annotation.clone().map(TypeAnnotation::TypeExpression), TypeScheme::concrete(Type::Dimension(type_specified)), @@ -1320,7 +1324,7 @@ impl TypeChecker { ); } typed_ast::Statement::DefineDerivedUnit( - identifier.to_string(), + identifier, expr_checked, decorators.clone(), type_annotation.clone(), @@ -1424,7 +1428,7 @@ impl TypeChecker { ); typed_parameters.push(( *parameter_span, - parameter.to_string(), + *parameter, parameter_type, type_annotation, )); @@ -1444,7 +1448,7 @@ impl TypeChecker { let parameters: Vec<_> = typed_parameters .iter() - .map(|(span, name, _, annotation)| (*span, name.clone(), (*annotation).clone())) + .map(|(span, name, _, annotation)| (*span, name, (*annotation).clone())) .collect(); let parameter_types = typed_parameters .iter() @@ -1463,7 +1467,10 @@ impl TypeChecker { .iter() .map(|(span, name, tpb)| (*span, name.to_string(), tpb.clone()).clone()) .collect(), - parameters, + parameters: parameters + .into_iter() + .map(|(span, s, o)| (span, s.to_string(), o)) + .collect(), return_type_annotation: return_type_annotation.clone(), fn_type: fn_type.clone(), }, @@ -1471,6 +1478,7 @@ impl TypeChecker { name: crate::decorator::name(decorators).map(ToOwned::to_owned), url: crate::decorator::url(decorators).map(ToOwned::to_owned), description: crate::decorator::description(decorators), + examples: crate::decorator::examples(decorators), }, ); @@ -1579,18 +1587,18 @@ impl TypeChecker { ); typed_ast::Statement::DefineFunction( - function_name.to_string(), + function_name, decorators.clone(), type_parameters .iter() - .map(|(_, name, bound)| (name.to_string(), bound.clone())) + .map(|(_, name, bound)| (*name, bound.clone())) .collect(), typed_parameters .iter() .map(|(span, name, _, type_annotation)| { ( *span, - name.clone(), + *name, (*type_annotation).clone(), crate::markup::empty(), ) @@ -1640,7 +1648,7 @@ impl TypeChecker { .add_base_dimension(name) .map_err(TypeCheckError::RegistryError)?; } - typed_ast::Statement::DefineDimension(name.to_string(), dexprs.clone()) + typed_ast::Statement::DefineDimension(name, dexprs.clone()) } ast::Statement::ProcedureCall(span, kind @ ProcedureKind::Type, args) => { if args.len() != 1 { @@ -1894,12 +1902,12 @@ impl TypeChecker { pub fn check<'a>( &mut self, - statements: impl IntoIterator>, + statements: &[ast::Statement<'a>], ) -> Result>> { let mut checked_statements = vec![]; - for statement in statements.into_iter() { - checked_statements.push(self.check_statement(&statement)?); + for statement in statements { + checked_statements.push(self.check_statement(statement)?); } Ok(checked_statements) diff --git a/numbat/src/typechecker/substitutions.rs b/numbat/src/typechecker/substitutions.rs index 400e4a29..2cb353f1 100644 --- a/numbat/src/typechecker/substitutions.rs +++ b/numbat/src/typechecker/substitutions.rs @@ -92,7 +92,7 @@ impl ApplySubstitution for Type { impl ApplySubstitution for DType { fn apply(&mut self, substitution: &Substitution) -> Result<(), SubstitutionError> { let mut new_dtype = self.clone(); - for (f, power) in &self.factors { + for (f, power) in self.factors() { match f { DTypeFactor::TVar(tv) => { if let Some(type_) = substitution.lookup(tv) { @@ -148,7 +148,7 @@ impl ApplySubstitution for StructInfo { } } -impl ApplySubstitution for Expression { +impl ApplySubstitution for Expression<'_> { fn apply(&mut self, s: &Substitution) -> Result<(), SubstitutionError> { match self { Expression::Scalar(_, _, type_) => type_.apply(s), diff --git a/numbat/src/typechecker/tests/mod.rs b/numbat/src/typechecker/tests/mod.rs index 7d6014c0..ae71a29c 100644 --- a/numbat/src/typechecker/tests/mod.rs +++ b/numbat/src/typechecker/tests/mod.rs @@ -60,7 +60,7 @@ fn run_typecheck(input: &str) -> Result> { .map_err(|err| Box::new(err.into()))?; TypeChecker::default() - .check(transformed_statements) + .check(&transformed_statements) .map(|mut statements_checked| statements_checked.pop().unwrap()) } diff --git a/numbat/src/typechecker/type_scheme.rs b/numbat/src/typechecker/type_scheme.rs index 246b50b4..02cf5341 100644 --- a/numbat/src/typechecker/type_scheme.rs +++ b/numbat/src/typechecker/type_scheme.rs @@ -123,7 +123,7 @@ impl TypeScheme { for type_parameter in &type_parameters { markup += m::space(); - markup += m::type_identifier(type_parameter.unsafe_name()); + markup += m::type_identifier(type_parameter.unsafe_name().to_string()); if instantiated_type.bounds.is_dtype_bound(type_parameter) { markup += m::operator(":"); @@ -219,7 +219,7 @@ impl PrettyPrint for TypeScheme { for type_parameter in &type_parameters { markup += m::keyword("forall"); markup += m::space(); - markup += m::type_identifier(type_parameter.unsafe_name()); + markup += m::type_identifier(type_parameter.unsafe_name().to_string()); if instantiated_type.bounds.is_dtype_bound(type_parameter) { markup += m::operator(":"); diff --git a/numbat/src/typed_ast.rs b/numbat/src/typed_ast.rs index ccc49895..f1221bb2 100644 --- a/numbat/src/typed_ast.rs +++ b/numbat/src/typed_ast.rs @@ -41,20 +41,26 @@ type DtypeFactorPower = (DTypeFactor, Exponent); #[derive(Clone, Debug, PartialEq, Eq)] pub struct DType { // Always in canonical form - pub factors: Vec, // TODO make this private + factors: Vec, } impl DType { - pub fn from_factors(factors: &[DtypeFactorPower]) -> DType { - let mut dtype = DType { - factors: factors.into(), - }; + pub fn factors(&self) -> &[DtypeFactorPower] { + &self.factors + } + + pub fn into_factors(self) -> Vec { + self.factors + } + + pub fn from_factors(factors: Vec) -> DType { + let mut dtype = DType { factors }; dtype.canonicalize(); dtype } pub fn scalar() -> DType { - DType::from_factors(&[]) + DType::from_factors(vec![]) } pub fn is_scalar(&self) -> bool { @@ -76,11 +82,12 @@ impl DType { names.extend(registry.get_derived_entry_names_for(&base_representation)); match &names[..] { [] => self.pretty_print(), - [single] => m::type_identifier(single), - multiple => { - Itertools::intersperse(multiple.iter().map(m::type_identifier), m::dimmed(" or ")) - .sum() - } + [single] => m::type_identifier(single.to_string()), + multiple => Itertools::intersperse( + multiple.iter().cloned().map(m::type_identifier), + m::dimmed(" or "), + ) + .sum(), } } @@ -92,11 +99,11 @@ impl DType { } pub fn from_type_variable(v: TypeVariable) -> DType { - DType::from_factors(&[(DTypeFactor::TVar(v), Exponent::from_integer(1))]) + DType::from_factors(vec![(DTypeFactor::TVar(v), Exponent::from_integer(1))]) } pub fn from_type_parameter(name: String) -> DType { - DType::from_factors(&[(DTypeFactor::TPar(name), Exponent::from_integer(1))]) + DType::from_factors(vec![(DTypeFactor::TPar(name), Exponent::from_integer(1))]) } pub fn deconstruct_as_single_type_variable(&self) -> Option { @@ -109,14 +116,14 @@ impl DType { } pub fn from_tgen(i: usize) -> DType { - DType::from_factors(&[( + DType::from_factors(vec![( DTypeFactor::TVar(TypeVariable::Quantified(i)), Exponent::from_integer(1), )]) } pub fn base_dimension(name: &str) -> DType { - DType::from_factors(&[( + DType::from_factors(vec![( DTypeFactor::BaseDimension(name.into()), Exponent::from_integer(1), )]) @@ -157,16 +164,16 @@ impl DType { pub fn multiply(&self, other: &DType) -> DType { let mut factors = self.factors.clone(); factors.extend(other.factors.clone()); - DType::from_factors(&factors) + DType::from_factors(factors) } pub fn power(&self, n: Exponent) -> DType { - let factors: Vec<_> = self + let factors = self .factors .iter() .map(|(f, m)| (f.clone(), n * m)) .collect(); - DType::from_factors(&factors) + DType::from_factors(factors) } pub fn inverse(&self) -> DType { @@ -220,7 +227,7 @@ impl DType { } } } - Self::from_factors(&factors) + Self::from_factors(factors) } pub fn to_base_representation(&self) -> BaseRepresentation { @@ -259,11 +266,11 @@ impl std::fmt::Display for DType { impl From for DType { fn from(base_representation: BaseRepresentation) -> Self { - let factors: Vec<_> = base_representation + let factors = base_representation .into_iter() .map(|BaseRepresentationFactor(name, exp)| (DTypeFactor::BaseDimension(name), exp)) .collect(); - DType::from_factors(&factors) + DType::from_factors(factors) } } @@ -325,11 +332,11 @@ impl std::fmt::Display for Type { impl PrettyPrint for Type { fn pretty_print(&self) -> Markup { match self { - Type::TVar(TypeVariable::Named(name)) => m::type_identifier(name), + Type::TVar(TypeVariable::Named(name)) => m::type_identifier(name.clone()), Type::TVar(TypeVariable::Quantified(_)) => { unreachable!("Quantified types should not be printed") } - Type::TPar(name) => m::type_identifier(name), + Type::TPar(name) => m::type_identifier(name.clone()), Type::Dimension(d) => d.pretty_print(), Type::Boolean => m::type_identifier("Bool"), Type::String => m::type_identifier("String"), @@ -349,7 +356,7 @@ impl PrettyPrint for Type { + return_type.pretty_print() + m::operator("]") } - Type::Struct(info) => m::type_identifier(&info.name), + Type::Struct(info) => m::type_identifier(info.name.clone()), Type::List(element_type) => { m::type_identifier("List") + m::operator("<") @@ -451,16 +458,16 @@ impl Type { } #[derive(Debug, Clone, PartialEq)] -pub enum StringPart { +pub enum StringPart<'a> { Fixed(String), Interpolation { span: Span, - expr: Box, - format_specifiers: Option, + expr: Box>, + format_specifiers: Option<&'a str>, }, } -impl PrettyPrint for StringPart { +impl PrettyPrint for StringPart<'_> { fn pretty_print(&self) -> Markup { match self { StringPart::Fixed(s) => m::string(escape_numbat_string(s)), @@ -472,7 +479,7 @@ impl PrettyPrint for StringPart { let mut markup = m::operator("{") + expr.pretty_print(); if let Some(format_specifiers) = format_specifiers { - markup += m::text(format_specifiers); + markup += m::text(format_specifiers.to_string()); } markup += m::operator("}"); @@ -483,23 +490,23 @@ impl PrettyPrint for StringPart { } } -impl PrettyPrint for &Vec { +impl PrettyPrint for &Vec> { fn pretty_print(&self) -> Markup { m::operator("\"") + self.iter().map(|p| p.pretty_print()).sum() + m::operator("\"") } } #[derive(Debug, Clone, PartialEq)] -pub enum Expression { +pub enum Expression<'a> { Scalar(Span, Number, TypeScheme), - Identifier(Span, String, TypeScheme), + Identifier(Span, &'a str, TypeScheme), UnitIdentifier(Span, Prefix, String, String, TypeScheme), - UnaryOperator(Span, UnaryOperator, Box, TypeScheme), + UnaryOperator(Span, UnaryOperator, Box>, TypeScheme), BinaryOperator( Option, BinaryOperator, - Box, - Box, + Box>, + Box>, TypeScheme, ), /// A special binary operator that has a DateTime as one (or both) of the operands @@ -507,32 +514,37 @@ pub enum Expression { Option, BinaryOperator, /// LHS must evaluate to a DateTime - Box, + Box>, /// RHS can evaluate to a DateTime or a quantity of type Time - Box, + Box>, TypeScheme, ), // A 'proper' function call - FunctionCall(Span, Span, String, Vec, TypeScheme), + FunctionCall(Span, Span, &'a str, Vec>, TypeScheme), // A call via a function object - CallableCall(Span, Box, Vec, TypeScheme), + CallableCall(Span, Box>, Vec>, TypeScheme), Boolean(Span, bool), - Condition(Span, Box, Box, Box), - String(Span, Vec), - InstantiateStruct(Span, Vec<(String, Expression)>, StructInfo), + Condition( + Span, + Box>, + Box>, + Box>, + ), + String(Span, Vec>), + InstantiateStruct(Span, Vec<(&'a str, Expression<'a>)>, StructInfo), AccessField( Span, Span, - Box, - String, // field name + Box>, + &'a str, // field name TypeScheme, // struct type TypeScheme, // resulting field type ), - List(Span, Vec, TypeScheme), + List(Span, Vec>, TypeScheme), TypedHole(Span, TypeScheme), } -impl Expression { +impl Expression<'_> { pub fn full_span(&self) -> Span { match self { Expression::Scalar(span, ..) => *span, @@ -570,9 +582,9 @@ impl Expression { #[derive(Debug, Clone, PartialEq)] pub struct DefineVariable<'a>( - pub String, + pub &'a str, pub Vec>, - pub Expression, + pub Expression<'a>, pub Option, pub TypeScheme, pub Markup, @@ -580,41 +592,41 @@ pub struct DefineVariable<'a>( #[derive(Debug, Clone, PartialEq)] pub enum Statement<'a> { - Expression(Expression), + Expression(Expression<'a>), DefineVariable(DefineVariable<'a>), DefineFunction( - String, - Vec>, // decorators - Vec<(String, Option)>, // type parameters + &'a str, + Vec>, // decorators + Vec<(&'a str, Option)>, // type parameters Vec<( // parameters: Span, // span of the parameter - String, // parameter name + &'a str, // parameter name Option, // parameter type annotation Markup, // readable parameter type )>, - Option, // function body + Option>, // function body Vec>, // local variables TypeScheme, // function type Option, // return type annotation Markup, // readable return type ), - DefineDimension(String, Vec), + DefineDimension(&'a str, Vec), DefineBaseUnit( - String, + &'a str, Vec>, Option, TypeScheme, ), DefineDerivedUnit( - String, - Expression, + &'a str, + Expression<'a>, Vec>, Option, TypeScheme, Markup, ), - ProcedureCall(crate::ast::ProcedureKind, Vec), + ProcedureCall(crate::ast::ProcedureKind, Vec>), DefineStruct(StructInfo), } @@ -668,9 +680,8 @@ impl Statement<'_> { return_type_annotation, readable_return_type, ) => { - let (fn_type, _) = fn_type.instantiate_for_printing(Some( - type_parameters.iter().map(|(n, _)| n.as_str()), - )); + let (fn_type, _) = + fn_type.instantiate_for_printing(Some(type_parameters.iter().map(|(n, _)| *n))); for DefineVariable(_, _, _, type_annotation, type_, readable_type) in local_variables @@ -751,7 +762,7 @@ impl Statement<'_> { } } -impl Expression { +impl Expression<'_> { pub fn get_type(&self) -> Type { match self { Expression::Scalar(_, _, type_) => type_.unsafe_as_concrete(), @@ -846,8 +857,8 @@ fn decorator_markup(decorators: &Vec) -> Markup { m::decorator("@aliases") + m::operator("(") + Itertools::intersperse( - names.iter().map(|(name, accepts_prefix)| { - m::unit(name) + accepts_prefix_markup(accepts_prefix) + names.iter().map(|(name, accepts_prefix, _)| { + m::unit(name.to_string()) + accepts_prefix_markup(accepts_prefix) }), m::operator(", "), ) @@ -855,15 +866,32 @@ fn decorator_markup(decorators: &Vec) -> Markup { + m::operator(")") } Decorator::Url(url) => { - m::decorator("@url") + m::operator("(") + m::string(url) + m::operator(")") + m::decorator("@url") + + m::operator("(") + + m::string(url.clone()) + + m::operator(")") } Decorator::Name(name) => { - m::decorator("@name") + m::operator("(") + m::string(name) + m::operator(")") + m::decorator("@name") + + m::operator("(") + + m::string(name.clone()) + + m::operator(")") } Decorator::Description(description) => { m::decorator("@description") + m::operator("(") - + m::string(description) + + m::string(description.clone()) + + m::operator(")") + } + Decorator::Example(example_code, example_description) => { + m::decorator("@example") + + m::operator("(") + + m::string(example_code.clone()) + + if let Some(example_description) = example_description { + m::operator(", ") + m::string(example_description.clone()) + } else { + m::empty() + } + m::operator(")") } } @@ -890,7 +918,7 @@ pub fn pretty_print_function_signature<'a>( m::operator("<") + Itertools::intersperse( type_parameters.iter().map(|tv| { - m::type_identifier(tv.unsafe_name()) + m::type_identifier(tv.unsafe_name().to_string()) + if fn_type.bounds.is_dtype_bound(tv) { m::operator(":") + m::space() + m::type_identifier("Dim") } else { @@ -905,7 +933,7 @@ pub fn pretty_print_function_signature<'a>( let markup_parameters = Itertools::intersperse( parameters.map(|(name, parameter_type)| { - m::identifier(name) + m::operator(":") + m::space() + parameter_type.clone() + m::identifier(name.to_string()) + m::operator(":") + m::space() + parameter_type }), m::operator(", "), ) @@ -916,7 +944,7 @@ pub fn pretty_print_function_signature<'a>( m::keyword("fn") + m::space() - + m::identifier(function_name) + + m::identifier(function_name.to_string()) + markup_type_parameters + m::operator("(") + markup_parameters @@ -937,7 +965,7 @@ impl PrettyPrint for Statement<'_> { )) => { m::keyword("let") + m::space() - + m::identifier(identifier) + + m::identifier(identifier.to_string()) + m::operator(":") + m::space() + readable_type.clone() @@ -957,9 +985,8 @@ impl PrettyPrint for Statement<'_> { _return_type_annotation, readable_return_type, ) => { - let (fn_type, type_parameters) = fn_type.instantiate_for_printing(Some( - type_parameters.iter().map(|(n, _)| n.as_str()), - )); + let (fn_type, type_parameters) = + fn_type.instantiate_for_printing(Some(type_parameters.iter().map(|(n, _)| *n))); let mut pretty_local_variables = None; let mut first = true; @@ -984,7 +1011,7 @@ impl PrettyPrint for Statement<'_> { plv += m::nl() + introducer_keyword + m::space() - + m::identifier(identifier) + + m::identifier(identifier.to_string()) + m::operator(":") + m::space() + readable_type.clone() @@ -1002,7 +1029,7 @@ impl PrettyPrint for Statement<'_> { &type_parameters, parameters .iter() - .map(|(_, name, _, type_)| (name.as_str(), type_.clone())), + .map(|(_, name, _, type_)| (*name, type_.clone())), readable_return_type, ) + body .as_ref() @@ -1012,12 +1039,12 @@ impl PrettyPrint for Statement<'_> { } Statement::Expression(expr) => expr.pretty_print(), Statement::DefineDimension(identifier, dexprs) if dexprs.is_empty() => { - m::keyword("dimension") + m::space() + m::type_identifier(identifier) + m::keyword("dimension") + m::space() + m::type_identifier(identifier.to_string()) } Statement::DefineDimension(identifier, dexprs) => { m::keyword("dimension") + m::space() - + m::type_identifier(identifier) + + m::type_identifier(identifier.to_string()) + m::space() + m::operator("=") + m::space() @@ -1031,7 +1058,7 @@ impl PrettyPrint for Statement<'_> { decorator_markup(decorators) + m::keyword("unit") + m::space() - + m::unit(identifier) + + m::unit(identifier.to_string()) + m::operator(":") + m::space() + annotation @@ -1050,7 +1077,7 @@ impl PrettyPrint for Statement<'_> { decorator_markup(decorators) + m::keyword("unit") + m::space() - + m::unit(identifier) + + m::unit(identifier.to_string()) + m::operator(":") + m::space() + readable_type.clone() @@ -1087,7 +1114,7 @@ impl PrettyPrint for Statement<'_> { m::space() + Itertools::intersperse( fields.iter().map(|(n, (_, t))| { - m::identifier(n) + m::identifier(n.clone()) + m::operator(":") + m::space() + t.pretty_print() @@ -1158,7 +1185,7 @@ fn pretty_print_binop(op: &BinaryOperator, lhs: &Expression, rhs: &Expression) - } (Expression::Scalar(_, s, _), Expression::Identifier(_, name, _type)) => { // Fuse multiplication of a scalar and identifier - pretty_scalar(*s) + m::space() + m::identifier(name) + pretty_scalar(*s) + m::space() + m::identifier(name.to_string()) } _ => { let add_parens_if_needed = |expr: &Expression| { @@ -1242,13 +1269,13 @@ fn pretty_print_binop(op: &BinaryOperator, lhs: &Expression, rhs: &Expression) - } } -impl PrettyPrint for Expression { +impl PrettyPrint for Expression<'_> { fn pretty_print(&self) -> Markup { use Expression::*; match self { Scalar(_, n, _) => pretty_scalar(*n), - Identifier(_, name, _type) => m::identifier(name), + Identifier(_, name, _type) => m::identifier(name.to_string()), UnitIdentifier(_, prefix, _name, full_name, _type) => { m::unit(format!("{}{}", prefix.as_string_long(), full_name)) } @@ -1264,7 +1291,7 @@ impl PrettyPrint for Expression { BinaryOperator(_, op, lhs, rhs, _type) => pretty_print_binop(op, lhs, rhs), BinaryOperatorForDate(_, op, lhs, rhs, _type) => pretty_print_binop(op, lhs, rhs), FunctionCall(_, _, name, args, _type) => { - m::identifier(name) + m::identifier(name.to_string()) + m::operator("(") + itertools::Itertools::intersperse( args.iter().map(|e| e.pretty_print()), @@ -1308,7 +1335,7 @@ impl PrettyPrint for Expression { m::space() + itertools::Itertools::intersperse( exprs.iter().map(|(n, e)| { - m::identifier(n) + m::identifier(n.to_string()) + m::operator(":") + m::space() + e.pretty_print() @@ -1321,7 +1348,7 @@ impl PrettyPrint for Expression { + m::operator("}") } AccessField(_, _, expr, attr, _, _) => { - expr.pretty_print() + m::operator(".") + m::identifier(attr) + expr.pretty_print() + m::operator(".") + m::identifier(attr.to_string()) } List(_, elements, _) => { m::operator("[") @@ -1410,7 +1437,7 @@ mod tests { let transformed_statements = transformer.transform(statements).unwrap().replace_spans(); crate::typechecker::TypeChecker::default() - .check(transformed_statements) + .check(&transformed_statements) .unwrap() .last() .unwrap() diff --git a/numbat/src/unicode_input.rs b/numbat/src/unicode_input.rs index ff9ce271..3e052b69 100644 --- a/numbat/src/unicode_input.rs +++ b/numbat/src/unicode_input.rs @@ -54,8 +54,8 @@ pub const UNICODE_INPUT: &[(&[&str], &str)] = &[ (&["beta"], "β"), (&["gamma"], "γ"), (&["delta"], "δ"), - (&["epsilon"], "ε"), - (&["varepsilon"], "ϵ"), + (&["epsilon"], "ϵ"), + (&["varepsilon"], "ε"), (&["zeta"], "ζ"), (&["eta"], "η"), (&["theta"], "θ"), diff --git a/numbat/src/unit.rs b/numbat/src/unit.rs index b94cd74d..c2653137 100644 --- a/numbat/src/unit.rs +++ b/numbat/src/unit.rs @@ -269,33 +269,25 @@ impl Unit { } pub fn to_base_unit_representation(&self) -> (Self, ConversionFactor) { - // TODO: reduce wrapping/unwrapping and duplication. - - let base_unit_representation = self - .iter() - .map( - |UnitFactor { - prefix: _, - unit_id: base_unit, - exponent, - }| { base_unit.base_unit_and_factor().0.power(*exponent) }, - ) - .product::() - .canonicalized(); - - let factor = self - .iter() - .map( - |UnitFactor { - prefix, - unit_id: base_unit, - exponent, - }| { - (prefix.factor() * base_unit.base_unit_and_factor().1) - .pow(&Number::from_f64(exponent.to_f64().unwrap())) // TODO do we want to use exponent.to_f64? - }, - ) - .product(); + // TODO: reduce wrapping/unwrapping + let mut base_unit_representation = Product::unity(); + let mut factor = Number::from_f64(1.0); + + for UnitFactor { + unit_id: base_unit, + prefix, + exponent, + } in self.iter() + { + base_unit_representation = + base_unit_representation * base_unit.base_unit_and_factor().0.power(*exponent); + factor = factor + * (prefix.factor() * base_unit.base_unit_and_factor().1) + // TODO do we want to use exponent.to_f64? + .pow(&Number::from_f64(exponent.to_f64().unwrap())); + } + + base_unit_representation.canonicalize(); (base_unit_representation, factor) } diff --git a/numbat/src/value.rs b/numbat/src/value.rs index adee05fb..741ddf23 100644 --- a/numbat/src/value.rs +++ b/numbat/src/value.rs @@ -156,7 +156,7 @@ impl PrettyPrint for Value { Value::String(s) => s.pretty_print(), Value::DateTime(dt) => crate::markup::string(crate::datetime::to_string(dt)), Value::FunctionReference(r) => crate::markup::string(r.to_string()), - Value::FormatSpecifiers(Some(s)) => crate::markup::string(s), + Value::FormatSpecifiers(Some(s)) => crate::markup::string(s.clone()), Value::FormatSpecifiers(None) => crate::markup::empty(), Value::StructInstance(struct_info, values) => { crate::markup::type_identifier(struct_info.name.clone()) @@ -168,7 +168,7 @@ impl PrettyPrint for Value { crate::markup::space() + itertools::Itertools::intersperse( struct_info.fields.keys().zip(values).map(|(name, val)| { - crate::markup::identifier(name) + crate::markup::identifier(name.clone()) + crate::markup::operator(":") + crate::markup::space() + val.pretty_print()