From 467bf334f7646b4b3aaf4af6eec15c0d6baa53d1 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 11 Jan 2025 02:31:13 +0200 Subject: [PATCH 1/9] document classes --- doc/classes.md | 295 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 doc/classes.md diff --git a/doc/classes.md b/doc/classes.md new file mode 100644 index 00000000..ec4fe859 --- /dev/null +++ b/doc/classes.md @@ -0,0 +1,295 @@ +# Classes + +TL;DR: + +```python +import "stdlib/io.jou" + +class Person: + name: byte* + + def greet(self) -> None: + printf("Hello %s\n", self->name) # self is a pointer (Person*) + +def main() -> int: + instance = Person{name="world"} + instance.greet() # Output: Hello world + return 0 +``` + + +## Fields + +A Jou class is a chunk of memory that is large enough to store multiple values. +For example, the following class has two integers: + +```python +class Point: + x: int + y: int +``` + +Now every instance of `Point` will be at least 64 bits in size: +32 bits for `x`, and 32 bits for `y`. +(In reality, instances may be bigger than expected due to +[padding](https://stackoverflow.com/questions/4306186/structure-padding-and-packing), +but this can be almost always ignored.) + +You can use e.g. `Point{x=12, y=34}` to instantiate the class, +and the usual `.` syntax to access its fields: + +```python +import "stdlib/io.jou" + +class Point: + x: int + y: int + +def main() -> int: + p = Point{x=12, y=34} + printf("%d,%d\n", p.x, p.y) # Output: 12,34 + p.y++ + printf("%d,%d\n", p.x, p.y) # Output: 12,35 + return 0 +``` + +This does not allocate any heap memory (TODO: document heap allocations). +In fact, it's basically same as creating two variables `x` and `y` in the `main()` function: + +```python +import "stdlib/io.jou" + +def main() -> int: + x = 12 + y = 34 + printf("%d,%d\n", x, y) # Output: 12,34 + return 0 +``` + +This means that if you pass an instance of a class to a function, you get a copy, +as if you had just passed the two integers: + +```python +import "stdlib/io.jou" + +class Point: + x: int + y: int + +def increment_y(instance: Point) -> None: + instance.y++ # Doesn't work as expected + +def main() -> int: + p = Point{x = 12, y = 34} + increment_y(p) + printf("%d\n", p.y) # Output: 34 + return 0 +``` + +For this reason, instances of classes are often passed around as [pointers](tutorial.md#pointers). +This way the `increment_y()` function knows where the original instance is in the computer's memory, +so that it can place the new value there instead of its own copy of the instance. +Like this: + +```python +import "stdlib/io.jou" + +class Point: + x: int + y: int + +def increment_y(ptr: Point*) -> None: + ptr->y++ + +def main() -> int: + p = Point{x = 12, y = 34} + increment_y(&p) + printf("%d\n", p.y) # Output: 35 + return 0 +``` + +Here `ptr->y` does the same thing as `(*ptr).y`: +it accesses the `y` member of the instance located wherever the pointer `ptr` is pointing. + + +## Methods + +The above `increment_y()` function does something with a point, +so instead of a function, it would be better to write it as a method in the `Point` class: + +```python +import "stdlib/io.jou" + +class Point: + x: int + y: int + + def increment_y(self) -> None: + self->y++ + +def main() -> int: + p = Point{x=12, y=34} + p.increment_y() + printf("%d\n", p.y) # Output: 35 + return 0 +``` + +By default, methods take the instance as a pointer. +In the above example, the type of `self` is `Point*`, +which means that `self` is a pointer to an instance of `Point`. + +If, for some reason, you want to pass the instance by value instead of a pointer, +you can use an explicit type annotation: + +```python +import "stdlib/io.jou" + +class Point: + x: int + y: int + + def increment_y(self: Point) -> None: # pass self by value + self.y++ + printf("incremented to %d\n", self.y) + +def main() -> int: + p = Point{x=12, y=34} + p.increment_y() # Output: incremented to 35 + printf("still %d\n", p.y) # Output: still 34 + return 0 +``` + +To call a method on a pointer (such as `self` by default), +use `->`, just like with accessing attributes: + +```python +import "stdlib/io.jou" + +class Point: + x: int + y: int + + def increment_x(self) -> None: + self->x++ + + def increment_y(self) -> None: + self->y++ + + def increment_both(self) -> None: + self->increment_x() + self->increment_y() + +def main() -> int: + p = Point{x=12, y=34} + p.increment_both() + printf("%d %d\n", p.x, p.y) # Output: 13 35 + return 0 +``` + + +## Instantiating + +If you omit some class fields, they will be initialized to zero. +Specifically, the memory used for the instance will be all zero bytes. +This means that boolean fields are set to `False`, +pointer fields are `NULL`, +and fixed size strings appear as empty: + +```python +import "stdlib/io.jou" + + +class Person: + name: byte* + country: byte[50] + + def introduce(self) -> None: + if self->name == NULL: + printf("I'm an anonymous person from '%s'\n", self->country) + else: + printf("I'm %s person from '%s'\n", self->name, self->country) + + +def main() -> int: + akuli = Person{name="Akuli", country="Finland"} + akuli.introduce() # Output: I'm Akuli from 'Finland' + + akuli = Person{name="Akuli"} + akuli.introduce() # Output: I'm Akuli from '' + + akuli = Person{} + akuli.introduce() # Output: I'm an anonymous person from '' + + return 0 +``` + +You can achieve the same thing by setting the memory used by the instance to zero bytes. +This is often done with the `memset()` function from [stdlib/mem.jou](../stdlib/mem.jou). +It takes in three parameters, so that `memset(ptr, 0, n)` sets `n` bytes starting at pointer `ptr` to zero. +To calculate the correct `n`, you can use the `sizeof` operator (TODO: document sizeof): + +```python +import "stdlib/io.jou" + + +class Person: + name: byte* + country: byte[50] + + def introduce(self) -> None: + if self->name == NULL: + printf("I'm an anonymous person from '%s'\n", self->country) + else: + printf("I'm %s person from '%s'\n", self->name, self->country) + + +def main() -> int: + akuli = Person{name="Akuli", country="Finland"} + memset(&akuli, 0, sizeof(akuli)) + akuli.introduce() # Output: I'm an anonymous person from '' + return 0 +``` + +This may be easier if you have multiple instances next to each other (**contiguously**) in memory, +such as in an array, and you want to reset all of them: + +```python +import "stdlib/io.jou" +import "stdlib/mem.jou" + +class Person: + name: byte* + country: byte[50] + + def introduce(self) -> None: + if self->name == NULL: + printf("I'm an anonymous person from '%s'\n", self->country) + else: + printf("I'm %s person from '%s'\n", self->name, self->country) + +def main() -> int: + contributors = [ + Person{name="Akuli", country="Finland"}, + Person{name="littlewhitecloud", country="China"}, + Person{name="Moosems", country="USA"}, + ] + + # Output: I'm Akuli from 'Finland' + # Output: I'm littlewhitecloud from 'China' + # Output: I'm Moosems from 'USA' + for i = 0; i < 3; i++: + contributors[i].introduce() + + memset(&contributors, 0, sizeof(contributors)) + + # Output: I'm an anonymous person from '' + # Output: I'm an anonymous person from '' + # Output: I'm an anonymous person from '' + for i = 0; i < 3; i++: + contributors[i].introduce() + + return 0 +``` + +Here `sizeof(contributors)` is the size of the entire array in bytes, +which is 3 times the size of a `Person`. From 5ffe1e0d37eb9a0d21de29201fe9f09cc7440e5d Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 11 Jan 2025 02:42:17 +0200 Subject: [PATCH 2/9] fixenings --- doc/classes.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/classes.md b/doc/classes.md index ec4fe859..09416803 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -207,7 +207,7 @@ class Person: if self->name == NULL: printf("I'm an anonymous person from '%s'\n", self->country) else: - printf("I'm %s person from '%s'\n", self->name, self->country) + printf("I'm %s from '%s'\n", self->name, self->country) def main() -> int: @@ -230,6 +230,7 @@ To calculate the correct `n`, you can use the `sizeof` operator (TODO: document ```python import "stdlib/io.jou" +import "stdlib/mem.jou" class Person: @@ -240,7 +241,7 @@ class Person: if self->name == NULL: printf("I'm an anonymous person from '%s'\n", self->country) else: - printf("I'm %s person from '%s'\n", self->name, self->country) + printf("I'm %s from '%s'\n", self->name, self->country) def main() -> int: @@ -265,7 +266,7 @@ class Person: if self->name == NULL: printf("I'm an anonymous person from '%s'\n", self->country) else: - printf("I'm %s person from '%s'\n", self->name, self->country) + printf("I'm %s from '%s'\n", self->name, self->country) def main() -> int: contributors = [ From b29aa311873587f93719a033ab0f7e4f9e0ee408 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 11 Jan 2025 02:48:42 +0200 Subject: [PATCH 3/9] mention classes in README --- README.md | 19 +++++++++++++++++++ doctest.sh | 6 +++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e75567d..5fb0151f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,25 @@ def main() -> int: return 0 ``` +Jou has classes. They can have methods, but that's about it: +otherwise Jou classes are just like structs in C. + +```python +import "stdlib/io.jou" + + +class Greeting: + target: byte* + + def show(self) -> None: + printf("Hello %s\n", self->target) + + +def main() -> int: + g = Greeting{target="World"} + g.show() # Output: Hello World +``` + See the [examples](./examples/) and [tests](./tests/) directories for more example programs or read [the Jou tutorial](./doc/tutorial.md). diff --git a/doctest.sh b/doctest.sh index 46fad451..17ff9e21 100755 --- a/doctest.sh +++ b/doctest.sh @@ -1,18 +1,18 @@ #!/usr/bin/env bash # -# This file runs code snippets in doc/*.md files. +# This file runs code snippets in markdown files. set -e -o pipefail for arg in "$@"; do if [[ "$arg" =~ ^- ]]; then - echo "Usage: $0 [doc/file1.md doc/file2.md ...]" >&2 + echo "Usage: $0 [file1.md file2.md ...]" >&2 exit 2 fi done if [ $# == 0 ]; then - files=(doc/*.md) + files=(README.md doc/*.md) else files=("$@") fi From 7a06fd60468b3e75d3e0e601b19793e02e6c203f Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 11 Jan 2025 02:54:15 +0200 Subject: [PATCH 4/9] reduce ws --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 5fb0151f..f759843a 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,12 @@ otherwise Jou classes are just like structs in C. ```python import "stdlib/io.jou" - class Greeting: target: byte* def show(self) -> None: printf("Hello %s\n", self->target) - def main() -> int: g = Greeting{target="World"} g.show() # Output: Hello World From ecc7d2c2f7a168a6affe4eb1f2267d2c1e33a219 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 11 Jan 2025 03:06:01 +0200 Subject: [PATCH 5/9] tweaks --- doc/classes.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/doc/classes.md b/doc/classes.md index 09416803..27f34481 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -31,9 +31,9 @@ class Point: Now every instance of `Point` will be at least 64 bits in size: 32 bits for `x`, and 32 bits for `y`. -(In reality, instances may be bigger than expected due to +In reality, instances may be bigger than expected due to [padding](https://stackoverflow.com/questions/4306186/structure-padding-and-packing), -but this can be almost always ignored.) +but this can be almost always ignored. You can use e.g. `Point{x=12, y=34}` to instantiate the class, and the usual `.` syntax to access its fields: @@ -47,9 +47,9 @@ class Point: def main() -> int: p = Point{x=12, y=34} - printf("%d,%d\n", p.x, p.y) # Output: 12,34 + printf("%d, %d\n", p.x, p.y) # Output: 12, 34 p.y++ - printf("%d,%d\n", p.x, p.y) # Output: 12,35 + printf("%d, %d\n", p.x, p.y) # Output: 12, 35 return 0 ``` @@ -62,7 +62,7 @@ import "stdlib/io.jou" def main() -> int: x = 12 y = 34 - printf("%d,%d\n", x, y) # Output: 12,34 + printf("%d, %d\n", x, y) # Output: 12, 34 return 0 ``` @@ -138,8 +138,8 @@ By default, methods take the instance as a pointer. In the above example, the type of `self` is `Point*`, which means that `self` is a pointer to an instance of `Point`. -If, for some reason, you want to pass the instance by value instead of a pointer, -you can use an explicit type annotation: +To call a method on a pointer (such as `self` by default), +use `->`, just like with accessing fields: ```python import "stdlib/io.jou" @@ -148,19 +148,25 @@ class Point: x: int y: int - def increment_y(self: Point) -> None: # pass self by value - self.y++ - printf("incremented to %d\n", self.y) + def increment_x(self) -> None: + self->x++ + + def increment_y(self) -> None: + self->y++ + + def increment_both(self) -> None: + self->increment_x() + self->increment_y() def main() -> int: p = Point{x=12, y=34} - p.increment_y() # Output: incremented to 35 - printf("still %d\n", p.y) # Output: still 34 + p.increment_both() + printf("%d %d\n", p.x, p.y) # Output: 13 35 return 0 ``` -To call a method on a pointer (such as `self` by default), -use `->`, just like with accessing attributes: +If, for some reason, you want to pass the instance by value instead of a pointer, +you can specify the type of `self` like this: ```python import "stdlib/io.jou" @@ -169,20 +175,14 @@ class Point: x: int y: int - def increment_x(self) -> None: - self->x++ - - def increment_y(self) -> None: - self->y++ - - def increment_both(self) -> None: - self->increment_x() - self->increment_y() + def increment_y(self: Point) -> None: # pass self by value + self.y++ + printf("incremented to %d\n", self.y) def main() -> int: p = Point{x=12, y=34} - p.increment_both() - printf("%d %d\n", p.x, p.y) # Output: 13 35 + p.increment_y() # Output: incremented to 35 + printf("still %d\n", p.y) # Output: still 34 return 0 ``` From 5c73d8de9c27bb964754052ef825536779c7a81a Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 11 Jan 2025 04:51:50 +0200 Subject: [PATCH 6/9] moar --- doc/classes.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/classes.md b/doc/classes.md index 27f34481..104b4bcb 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -20,7 +20,7 @@ def main() -> int: ## Fields -A Jou class is a chunk of memory that is large enough to store multiple values. +An instance of a Jou class is a chunk of memory that is large enough to store multiple values. For example, the following class has two integers: ```python @@ -138,7 +138,7 @@ By default, methods take the instance as a pointer. In the above example, the type of `self` is `Point*`, which means that `self` is a pointer to an instance of `Point`. -To call a method on a pointer (such as `self` by default), +To call a method on a pointer (such as `self`), use `->`, just like with accessing fields: ```python @@ -189,6 +189,9 @@ def main() -> int: ## Instantiating +As we have seen, the instantiating syntax is `ClassName{field=value}`. +The curly braces are used to distinguish instantiating from function calls. + If you omit some class fields, they will be initialized to zero. Specifically, the memory used for the instance will be all zero bytes. This means that boolean fields are set to `False`, From 872c08e8f8766bc8b2e93101a6220bd16f876b50 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 11 Jan 2025 04:54:21 +0200 Subject: [PATCH 7/9] more tweaking --- doc/classes.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/classes.md b/doc/classes.md index 104b4bcb..81e0c998 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -193,9 +193,10 @@ As we have seen, the instantiating syntax is `ClassName{field=value}`. The curly braces are used to distinguish instantiating from function calls. If you omit some class fields, they will be initialized to zero. -Specifically, the memory used for the instance will be all zero bytes. +Specifically, the memory used for the fields will be all zero bytes. This means that boolean fields are set to `False`, -pointer fields are `NULL`, +numbers are set to zero, +pointer fields become `NULL`, and fixed size strings appear as empty: ```python @@ -254,7 +255,8 @@ def main() -> int: return 0 ``` -This may be easier if you have multiple instances next to each other (**contiguously**) in memory, +This may be easier if you have multiple instances next to each other +(that is, **contiguously**) in memory, such as in an array, and you want to reset all of them: ```python From aa633c05eb44a025588a67dc870cbe60eb64b213 Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 11 Jan 2025 04:57:28 +0200 Subject: [PATCH 8/9] better explanation --- doc/classes.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/classes.md b/doc/classes.md index 81e0c998..6b647ea4 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -255,9 +255,8 @@ def main() -> int: return 0 ``` -This may be easier if you have multiple instances next to each other -(that is, **contiguously**) in memory, -such as in an array, and you want to reset all of them: +This works the same way if you have multiple instances next to each other in memory, +such as in an array, and you want to zero-initialize all of them: ```python import "stdlib/io.jou" From 91cba1d000d0d0561a3bf43f6d625e2e16ff0f8c Mon Sep 17 00:00:00 2001 From: Akuli Date: Sat, 11 Jan 2025 17:15:13 +0200 Subject: [PATCH 9/9] Try to clarify --- doc/classes.md | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/doc/classes.md b/doc/classes.md index 6b647ea4..e5ae04a1 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -166,26 +166,18 @@ def main() -> int: ``` If, for some reason, you want to pass the instance by value instead of a pointer, +so that the method gets a copy of it, you can specify the type of `self` like this: ```python -import "stdlib/io.jou" - class Point: - x: int - y: int - - def increment_y(self: Point) -> None: # pass self by value - self.y++ - printf("incremented to %d\n", self.y) - -def main() -> int: - p = Point{x=12, y=34} - p.increment_y() # Output: incremented to 35 - printf("still %d\n", p.y) # Output: still 34 - return 0 + def do_something(self: Point) -> None: + ... ``` +This means that the type of `self` is `Point`, not `Point*` (the default), +so `self` is not a pointer. + ## Instantiating