Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/numbat.sublime-syntax
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ file_extensions:
scope: source.nbt
contexts:
main:
- match: \b(per|to|let|fn|where|and|dimension|unit|use|struct|long|short|both|none|if|then|else|true|false|print|assert|assert_eq|type)\b
- match: \b(per|to|let|fn|where|and|dimension|unit|use|struct|impl|self|long|short|both|none|if|then|else|true|false|print|assert|assert_eq|type)\b
scope: keyword.control.nbt
- match: '#(.*)'
scope: comment.line.nbt
Expand Down
2 changes: 1 addition & 1 deletion assets/numbat.vim
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ if exists("b:current_syntax")
endif

" Numbat Keywords
syn keyword numbatKeywords per to let fn where and dimension unit use struct long short both none if then else true false NaN inf print assert assert_eq type
syn keyword numbatKeywords per to let fn where and dimension unit use struct impl self long short both none if then else true false NaN inf print assert assert_eq type
highlight default link numbatKeywords Keyword

" Physical dimensions (every capitalized word)
Expand Down
37 changes: 34 additions & 3 deletions book/src/basics/structs.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ let side_length = cbrt(mass / tungsten.density) -> cm
print("A tungsten cube with a mass of {mass} has a side length of {side_length:.2}.")
```

## Methods

You can define methods on structs using `impl` blocks. Methods take `self` as their first parameter and are called using dot notation:

```nbt
impl Element {
fn cube_side_length(self, mass: Mass) -> Length =
cbrt(mass / self.density)
}

tungsten.cube_side_length(1 kg) -> cm # 3.72 cm
```

## Generic structs

Structs can be generic over type parameters. Type parameters are declared in angle brackets after the struct name:
Expand All @@ -49,11 +62,29 @@ let t = Tuple { first: "hello", second: 42 }
If you want to constrain a type parameter to be a dimension type, use the `Dim` bound:

```nbt
struct Vec<X: Dim> {
x: X,
y: X,
struct Vec<D: Dim> {
x: D,
y: D,
}

let position = Vec { x: 1 m, y: 2 m }
let velocity: Vec<Velocity> = Vec { x: 1 m/s, y: 2 m/s }
```

For generic structs, the type parameters are declared on the `impl` block. Methods can also be generic over additional type parameters:

```nbt
impl<D: Dim> Vec<D> {
fn norm(self) -> D² = self.x² + self.y²

fn multiply<S: Dim>(self, factor: S) -> Vec<S × D> =
Vec {
x: factor × self.x,
y: factor × self.y,
}
}

let p = Vec { x: 3 m, y: 4 m }
p.norm() # 25 m²
p.multiply(10 N) # Vec { x: 30 N·m, y: 40 N·m }
```
13 changes: 13 additions & 0 deletions book/src/examples/example-numbat_syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,17 @@ struct Vec2<D: Dim> { # A generic struct with type parameter
x: D,
y: D,
}

impl<D: Dim> Vec2<D> { # Define methods for a struct
fn norm(self) -> D² = self.x² + self.y²

fn scale<S: Dim>(self, scalar: S) -> Vec2<S × D> =
Vec2 {
x: scalar × self.x,
y: scalar × self.y,
}
}

let v = Vec2 { x: 3 meter, y: 4 meter }
v.norm() # Call a method
```
31 changes: 14 additions & 17 deletions book/src/examples/example-paper_size.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Paper sizes

[:material-play-circle: Run this example](https://numbat.dev/?q=%23+Compute+ISO+216+paper+sizes+for+the+A+series%0A%23%0A%23+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FISO_216%0A%0Astruct+PaperSize+%7B%0A++++width%3A+Length%2C%0A++++height%3A+Length%2C%0A%7D%0A%0Afn+paper_size_A%28n%3A+Scalar%29+-%3E+PaperSize+%3D%0A++if+n+%3D%3D+0%0A++++then%0A++++++PaperSize+%7B%0A++++++++width%3A+841+mm%2C%0A++++++++height%3A+1189+mm%0A++++++%7D%0A++++else%0A++++++PaperSize+%7B%0A++++++++width%3A+floor_in%28mm%2C+paper_size_A%28n+-+1%29.height+%2F+2%29%2C%0A++++++++height%3A+paper_size_A%28n+-+1%29.width%2C%0A++++++%7D%0A%0A%0Afn+paper_area%28size%3A+PaperSize%29+-%3E+Area+%3D%0A++++size.width+%2A+size.height%0A%0A%0Afn+size_as_string%28size%3A+PaperSize%29+%3D+%22%7Bsize.width%3A%3E4%7D+%C3%97+%7Bsize.height%3A%3E5%7D+++%7Bpaper_area%28size%29+-%3E+cm%C2%B2%3A%3E6.1f%7D%22%0Afn+row%28n%29+%3D+%22A%7Bn%3A%3C3%7D+++%7Bsize_as_string%28paper_size_A%28n%29%29%7D%22%0A%0Aprint%28%22Name++++Width+++++Height++++++++Area++%22%29%0Aprint%28%22----+++-------+++--------+++----------%22%29%0Aprint%28join%28map%28row%2C+range%280%2C+10%29%29%2C+%22%5Cn%22%29%29%0A){ .md-button .md-button--primary }
[:material-play-circle: Run this example](https://numbat.dev/?q=%23+Compute+ISO+216+paper+sizes+for+the+A+series%0A%23%0A%23+https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FISO_216%0A%0Astruct+PaperSize+%7B%0A++++width%3A+Length%2C%0A++++height%3A+Length%2C%0A%7D%0A%0Aimpl+PaperSize+%7B%0A++++fn+area%28self%29+-%3E+Area+%3D+self.width+%C3%97+self.height%0A%0A++++fn+to_string%28self%29+-%3E+String+%3D%0A++++++++%22%7Bself.width%3A%3E4%7D+%C3%97+%7Bself.height%3A%3E5%7D+++%7Bself.area%28%29+-%3E+cm%C2%B2%3A%3E6.1f%7D%22%0A%7D%0A%0Afn+paper_size_A%28n%3A+Scalar%29+-%3E+PaperSize+%3D%0A++++if+n+%3D%3D+0%0A++++++++then+PaperSize+%7B+width%3A+841+mm%2C+height%3A+1189+mm+%7D%0A++++++++else+PaperSize+%7B%0A++++++++++++width%3A+floor_in%28mm%2C+paper_size_A%28n+-+1%29.height+%2F+2%29%2C%0A++++++++++++height%3A+paper_size_A%28n+-+1%29.width%2C%0A++++++++%7D%0A%0A%0Afn+row%28n%29+%3D+%22A%7Bn%3A%3C3%7D+++%7Bpaper_size_A%28n%29.to_string%28%29%7D%22%0A%0Aprint%28%22Name++++Width+++++Height++++++++Area++%22%29%0Aprint%28%22----+++-------+++--------+++----------%22%29%0Aprint%28join%28map%28row%2C+range%280%2C+10%29%29%2C+%22%5Cn%22%29%29%0A){ .md-button .md-button--primary }

```numbat
# Compute ISO 216 paper sizes for the A series
Expand All @@ -14,26 +14,23 @@ struct PaperSize {
height: Length,
}

fn paper_size_A(n: Scalar) -> PaperSize =
if n == 0
then
PaperSize {
width: 841 mm,
height: 1189 mm
}
else
PaperSize {
width: floor_in(mm, paper_size_A(n - 1).height / 2),
height: paper_size_A(n - 1).width,
}
impl PaperSize {
fn area(self) -> Area = self.width × self.height

fn to_string(self) -> String =
"{self.width:>4} × {self.height:>5} {self.area() -> cm²:>6.1f}"
}

fn paper_area(size: PaperSize) -> Area =
size.width * size.height
fn paper_size_A(n: Scalar) -> PaperSize =
if n == 0
then PaperSize { width: 841 mm, height: 1189 mm }
else PaperSize {
width: floor_in(mm, paper_size_A(n - 1).height / 2),
height: paper_size_A(n - 1).width,
}


fn size_as_string(size: PaperSize) = "{size.width:>4} × {size.height:>5} {paper_area(size) -> cm²:>6.1f}"
fn row(n) = "A{n:<3} {size_as_string(paper_size_A(n))}"
fn row(n) = "A{n:<3} {paper_size_A(n).to_string()}"

print("Name Width Height Area ")
print("---- ------- -------- ----------")
Expand Down
48 changes: 0 additions & 48 deletions book/src/prelude/functions/other.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,51 +528,3 @@ fn color(rgb_hex: Scalar) -> Color
```
[:material-play-circle: Run this example](https://numbat.dev/?q=use%20extra%3A%3Acolor%0Acolor%280xff7700%29){ .md-button }

### `color_rgb`
Convert a color to its RGB representation.

```nbt
fn color_rgb(color: Color) -> String
```

!!! example "Example"
```nbt
use extra::color
cyan -> color_rgb

= "rgb(0, 255, 255)" [String]
```
[:material-play-circle: Run this example](https://numbat.dev/?q=use%20extra%3A%3Acolor%0Acyan%20%2D%3E%20color%5Frgb){ .md-button }
Comment on lines -531 to -545

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo: document struct methods


### `color_rgb_float`
Convert a color to its RGB floating point representation.

```nbt
fn color_rgb_float(color: Color) -> String
```

!!! example "Example"
```nbt
use extra::color
cyan -> color_rgb_float

= "rgb(0.000, 1.000, 1.000)" [String]
```
[:material-play-circle: Run this example](https://numbat.dev/?q=use%20extra%3A%3Acolor%0Acyan%20%2D%3E%20color%5Frgb%5Ffloat){ .md-button }

### `color_hex`
Convert a color to its hexadecimal representation.

```nbt
fn color_hex(color: Color) -> String
```

!!! example "Example"
```nbt
use extra::color
rgb(225, 36, 143) -> color_hex

= "#e1248f" [String]
```
[:material-play-circle: Run this example](https://numbat.dev/?q=use%20extra%3A%3Acolor%0Argb%28225%2C%2036%2C%20143%29%20%2D%3E%20color%5Fhex){ .md-button }

21 changes: 11 additions & 10 deletions examples/3d_printing.nbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,25 @@ struct Material {
price: Money / Mass,
}

impl Material {
fn cost(self, mass: Mass) -> Money = self.price × mass

fn filament_length(self, mass: Mass) -> Length = volume / cross_section -> meter
where r = self.diameter / 2
and cross_section: Area = π r²
and volume: Volume = mass / self.density
}

let PLA = Material {
diameter: 1.75 mm,
density: 1.27 g/cm^3,
price: 16.99 €/kg,
}

fn print_cost(material: Material, mass: Mass) -> Money = material.price × mass

fn filament_length(material: Material, mass: Mass) -> Length = volume / cross_section -> meter
where r = material.diameter / 2
and cross_section: Area = π r²
and volume: Volume = mass / material.density


# Print parameters
let mass_model = 80 g
let material = PLA

print("Mass of model: {mass_model}")
print("Filament length: {filament_length(material, mass_model):.2}")
print("Cost of model: {print_cost(material, mass_model):.2}")
print("Filament length: {material.filament_length(mass_model):.2}")
print("Cost of model: {material.cost(mass_model):.2}")
8 changes: 5 additions & 3 deletions examples/interactive/tidal_chart.nbt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ struct Constituent {
phase: Angle,
}

fn height(c: Constituent, t: Time) -> Length =
c.amplitude cos(2π t / c.period + c.phase)
impl Constituent {
fn height(self, t: Time) -> Length =
self.amplitude × cos(2π × t / self.period + self.phase)
}

# The Gulf of Thailand has predominantly DIURNAL tides, meaning
# K1 and O1 dominate over the semidiurnal M2 and S2 components.
Expand Down Expand Up @@ -48,7 +50,7 @@ let O1 = Constituent {

let mean_sea_level = 0.5 m

fn tide_height(t: Time) -> Length = mean_sea_level + height(M2, t) + height(S2, t) + height(K1, t) + height(O1, t)
fn tide_height(t: Time) -> Length = mean_sea_level + M2.height(t) + S2.height(t) + K1.height(t) + O1.height(t)

let t_start = 0 days
let duration = 30 days
Expand Down
13 changes: 13 additions & 0 deletions examples/numbat_syntax.nbt
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,16 @@ struct Vec2<D: Dim> { # A generic struct with type parameter
x: D,
y: D,
}

impl<D: Dim> Vec2<D> { # Define methods for a struct
fn norm(self) -> D² = self.x² + self.y²

fn scale<S: Dim>(self, scalar: S) -> Vec2<S × D> =
Vec2 {
x: scalar × self.x,
y: scalar × self.y,
}
}

let v = Vec2 { x: 3 meter, y: 4 meter }
v.norm() # Call a method
32 changes: 15 additions & 17 deletions examples/paper_size.nbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,26 @@ struct PaperSize {
height: Length,
}

impl PaperSize {
fn area(self) -> Area = self.width × self.height

fn to_string(self) -> String =
"{self.width:>4} × {self.height:>5} {self.area() -> cm²:>6.1f}"
}

fn paper_size_A(n: Scalar) -> PaperSize =
if n == 0
then
PaperSize {
width: 841 mm,
height: 1189 mm
}
else
PaperSize {
width: floor_in(mm, paper_size_A(n - 1).height / 2),
height: paper_size_A(n - 1).width,
}
if n == 0
then PaperSize { width: 841 mm, height: 1189 mm }
else PaperSize {
width: floor_in(mm, paper_size_A(n - 1).height / 2),
height: paper_size_A(n - 1).width,
}

assert_eq(paper_size_A(4).width, 210 mm)
assert_eq(paper_size_A(4).height, 297 mm)
assert_eq(paper_size_A(4).area(), 62370 mm²)

fn paper_area(size: PaperSize) -> Area =
size.width * size.height


fn size_as_string(size: PaperSize) = "{size.width:>4} × {size.height:>5} {paper_area(size) -> cm²:>6.1f}"
fn row(n) = "A{n:<3} {size_as_string(paper_size_A(n))}"
fn row(n) = "A{n:<3} {paper_size_A(n).to_string()}"

print("Name Width Height Area ")
print("---- ------- -------- ----------")
Expand Down
46 changes: 23 additions & 23 deletions examples/tests/color.nbt
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
use extra::color

assert_eq(0x000000 -> color, black)
assert_eq(0xffffff -> color, white)
assert_eq(0x123456 -> color, Color { red: 0x12, green: 0x34, blue: 0x56 })
assert_eq(color(0x000000), black)
assert_eq(color(0xffffff), white)
assert_eq(color(0x123456), Color { red: 0x12, green: 0x34, blue: 0x56 })

assert_eq(black -> color_rgb, "rgb(0, 0, 0)")
assert_eq(white -> color_rgb, "rgb(255, 255, 255)")
assert_eq(red -> color_rgb, "rgb(255, 0, 0)")
assert_eq(green -> color_rgb, "rgb(0, 255, 0)")
assert_eq(blue -> color_rgb, "rgb(0, 0, 255)")
assert_eq(0x123456 -> color -> color_rgb, "rgb(18, 52, 86)")
assert_eq(black.to_rgb(), "rgb(0, 0, 0)")
assert_eq(white.to_rgb(), "rgb(255, 255, 255)")
assert_eq(red.to_rgb(), "rgb(255, 0, 0)")
assert_eq(green.to_rgb(), "rgb(0, 255, 0)")
assert_eq(blue.to_rgb(), "rgb(0, 0, 255)")
assert_eq(color(0x123456).to_rgb(), "rgb(18, 52, 86)")

assert_eq(black -> color_rgb_float, "rgb(0.000, 0.000, 0.000)")
assert_eq(white -> color_rgb_float, "rgb(1.000, 1.000, 1.000)")
assert_eq(red -> color_rgb_float, "rgb(1.000, 0.000, 0.000)")
assert_eq(green -> color_rgb_float, "rgb(0.000, 1.000, 0.000)")
assert_eq(blue -> color_rgb_float, "rgb(0.000, 0.000, 1.000)")
assert_eq(0x123456 -> color -> color_rgb_float, "rgb(0.071, 0.204, 0.337)")
assert_eq(black.to_rgb_float(), "rgb(0.000, 0.000, 0.000)")
assert_eq(white.to_rgb_float(), "rgb(1.000, 1.000, 1.000)")
assert_eq(red.to_rgb_float(), "rgb(1.000, 0.000, 0.000)")
assert_eq(green.to_rgb_float(), "rgb(0.000, 1.000, 0.000)")
assert_eq(blue.to_rgb_float(), "rgb(0.000, 0.000, 1.000)")
assert_eq(color(0x123456).to_rgb_float(), "rgb(0.071, 0.204, 0.337)")

assert_eq(black -> color_hex, "#000000")
assert_eq(white -> color_hex, "#ffffff")
assert_eq(red -> color_hex, "#ff0000")
assert_eq(green -> color_hex, "#00ff00")
assert_eq(blue -> color_hex, "#0000ff")
assert_eq(0x123456 -> color -> color_hex, "#123456")
assert_eq(black.to_hex(), "#000000")
assert_eq(white.to_hex(), "#ffffff")
assert_eq(red.to_hex(), "#ff0000")
assert_eq(green.to_hex(), "#00ff00")
assert_eq(blue.to_hex(), "#0000ff")
assert_eq(color(0x123456).to_hex(), "#123456")

# Examples:

assert_eq(rgb(225, 36, 143) -> color_hex, "#e1248f")
assert_eq(0xe1248f -> color -> color_rgb, "rgb(225, 36, 143)")
assert_eq(rgb(225, 36, 143).to_hex(), "#e1248f")
assert_eq(color(0xe1248f).to_rgb(), "rgb(225, 36, 143)")
20 changes: 15 additions & 5 deletions examples/tests/vector3.nbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@ let r = vec(3 m, 4 m, 0 m)
let v = vec(-10 m / s, 0, 0)

let mass = 5 kg
let p = multiply(mass, v)
let p = v.multiply(mass)

let l_z = cross(r, p).z
let l_z = r.cross(p).z

# Check values
assert_eq(norm(r), 25 m²)
assert_eq(length(r), 5 m)
# Check values using methods
assert_eq(r.norm(), 25 m²)
assert_eq(r.len(), 5 m)
assert_eq(l_z, 200 kg m² / s)

# Test method chaining
let v1 = vec(1 m, 2 m, 3 m)
let v2 = vec(4 m, 5 m, 6 m)
assert_eq(v1.add(v2).x, 5 m)
assert_eq(v1.sub(v2).x, -3 m)
assert_eq(v1.neg().x, -1 m)

# Test dot product
assert_eq(v1.dot(v2), 32 m²)

# Check types
let _r: Vec<Length> = r
let _v: Vec<Velocity> = v
Expand Down
Loading