From 2aaad8a423d97f4e316938910b3f8109264fe0d6 Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 14 Jan 2025 01:06:06 +0200 Subject: [PATCH 1/5] doc/classes.md: explain instantiation better --- doc/classes.md | 130 +++++++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 59 deletions(-) diff --git a/doc/classes.md b/doc/classes.md index e5ae04a1..db6bc1a6 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -35,8 +35,18 @@ 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: +To create an instance of `Point`, +we simply need enough memory to hold the two `int`s, +and we need to tell the compiler to treat it as a `Point` instance. +In other words, we simply create a variable whose type is `Point`: + +```python +p: Point +``` + +Because the memory is [uninitialized](tutorial.md#undefined-behavior-ub), +we still need to assign values to the fields, which we can access as `p.x` and `p.y`. +Like this: ```python import "stdlib/io.jou" @@ -46,30 +56,26 @@ class Point: y: int def main() -> int: - p = Point{x=12, y=34} + p: Point + p.x = 12 + p.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: +Alternatively, we could create the instance in one line with `p = Point{x = 12, y = 34}`. +This syntax is explained in detail [below](#instantiating). -```python -import "stdlib/io.jou" -def main() -> int: - x = 12 - y = 34 - printf("%d, %d\n", x, y) # Output: 12, 34 - return 0 -``` +## Pointers -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: +Instances of classes are often passed around as pointers. +To understand why, let's try to make a function +that increments the `x` coordinate of a `Point`: -```python + ```python import "stdlib/io.jou" class Point: @@ -86,6 +92,27 @@ def main() -> int: return 0 ``` +The problem is that when we do `increment_y()`, +we simply pass the 64 (or more) bytes of the struct to the `increment_y()` method. +This is very similar to creating two variables `x` and `y` in the `main()` function: + +```python +import "stdlib/io.jou" + +def increment_y(x: int, y: int) -> None: + y++ # Doesn't work as expected + +def main() -> int: + x = 12 + y = 34 + increment_y(x, y) + printf("%d, %d\n", x, y) # Output: 12, 34 + return 0 +``` + +In either case, the `increment_y()` function gets a copy of the coordinates, +so `instance.y++` or `y++` only increments the `y` coordinate of the copy. + 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. @@ -110,6 +137,9 @@ def main() -> int: 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. +This arrow syntax feels weird at first, +but it's convenient once you get used to it, +and the C programming language uses the same syntax. ## Methods @@ -181,8 +211,13 @@ so `self` is not a pointer. ## Instantiating -As we have seen, the instantiating syntax is `ClassName{field=value}`. -The curly braces are used to distinguish instantiating from function calls. +As we have seen, "instantiating" simply means taking a chunk of memory of the correct size, +but it's often done with the `ClassName{field=value}` syntax. +Let's look at this syntax in more detail. + +The curly braces are used to distinguish instantiating syntax from function calls. +This makes the Jou compiler simpler, but if you don't like this syntax, +feel free to [create an issue](https://github,com/Akuli/jou/issues/new) to discuss it. If you omit some class fields, they will be initialized to zero. Specifically, the memory used for the fields will be all zero bytes. @@ -222,38 +257,23 @@ def main() -> int: 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" -import "stdlib/mem.jou" - - -class Person: - name: byte* - country: byte[50] +To calculate the correct `n`, you can use `sizeof(instance)`, where `instance` is any instance of the class. +It doesn't matter which instance you use, because all instances of the class are of the same size. +In general, the value of `sizeof(x)` only depends on the type of `x`, +and it doesn't even evaluate `x` when the program runs. - def introduce(self) -> None: - if self->name == NULL: - printf("I'm an anonymous person from '%s'\n", self->country) - else: - printf("I'm %s 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 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: +For example, the following program creates an array of three uninitialized instances of `Person`, +and then zero-initializes all of them using `memset()`. +Array elements are simply next to each other in memory, +so it's enough to do one `memset()` that is big enough to set all of them to zero. +Like this: ```python import "stdlib/io.jou" import "stdlib/mem.jou" + class Person: name: byte* country: byte[50] @@ -264,29 +284,21 @@ class Person: else: printf("I'm %s 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)) +def main() -> int: + people: Person[3] + memset(&people, 0, sizeof(people[0]) * 3) # 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() + people[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`. +Instead of `sizeof(people[0]) * 3`, you could just as well use `sizeof(people)`. +The size of an array of 3 elements is simply 3 times the size of one element. +You could also use `people = [Person{}, Person{}, Person{}]` to create and zero-initialize the array, +but this becomes annoying if the array contains many instances. From ec46433941b02f0a12f060b379f24a2e92bab520 Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 14 Jan 2025 01:17:35 +0200 Subject: [PATCH 2/5] Apply suggestions from code review --- doc/classes.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/classes.md b/doc/classes.md index db6bc1a6..d5b16b5d 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -36,9 +36,9 @@ In reality, instances may be bigger than expected due to but this can be almost always ignored. To create an instance of `Point`, -we simply need enough memory to hold the two `int`s, -and we need to tell the compiler to treat it as a `Point` instance. -In other words, we simply create a variable whose type is `Point`: +we simply need to take enough memory to hold the two `int`s, +and we need to tell the compiler to treat that memory as a `Point` instance. +In other words, we create a variable whose type is `Point`: ```python p: Point @@ -60,6 +60,7 @@ def main() -> int: p.x = 12 p.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 @@ -73,9 +74,9 @@ This syntax is explained in detail [below](#instantiating). Instances of classes are often passed around as pointers. To understand why, let's try to make a function -that increments the `x` coordinate of a `Point`: +that increments the `y` coordinate of a `Point`: - ```python +```python import "stdlib/io.jou" class Point: @@ -92,8 +93,8 @@ def main() -> int: return 0 ``` -The problem is that when we do `increment_y()`, -we simply pass the 64 (or more) bytes of the struct to the `increment_y()` method. +The problem is that when we do `increment_y(p)`, +we simply pass the 64 (or more) bytes of the instance `p` to the `increment_y()` function. This is very similar to creating two variables `x` and `y` in the `main()` function: ```python @@ -209,7 +210,7 @@ This means that the type of `self` is `Point`, not `Point*` (the default), so `self` is not a pointer. -## Instantiating +## Instantiating Syntax As we have seen, "instantiating" simply means taking a chunk of memory of the correct size, but it's often done with the `ClassName{field=value}` syntax. From f7860c19ca991ed487a024dfca41f008c002c751 Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 14 Jan 2025 01:27:00 +0200 Subject: [PATCH 3/5] Fix typo in URL --- doc/classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/classes.md b/doc/classes.md index d5b16b5d..4c959007 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -218,7 +218,7 @@ Let's look at this syntax in more detail. The curly braces are used to distinguish instantiating syntax from function calls. This makes the Jou compiler simpler, but if you don't like this syntax, -feel free to [create an issue](https://github,com/Akuli/jou/issues/new) to discuss it. +feel free to [create an issue](https://github.com/Akuli/jou/issues/new) to discuss it. If you omit some class fields, they will be initialized to zero. Specifically, the memory used for the fields will be all zero bytes. From e3f34b6f68a74247df238b24778451106f674155 Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 14 Jan 2025 01:36:46 +0200 Subject: [PATCH 4/5] Fix link checker script --- check-markdown-links.py | 6 +++++- doc/classes.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/check-markdown-links.py b/check-markdown-links.py index 0ad950e1..977e1fb9 100644 --- a/check-markdown-links.py +++ b/check-markdown-links.py @@ -81,7 +81,11 @@ def check_link(markdown_file_path, link_target, offline_mode=False): if "\\" in link_target: return "use forward slashes instead of backslashes" - path = markdown_file_path.parent / link_target.split("#")[0] + if link_target.startswith('#'): + # Link to within same file + path = markdown_file_path + else: + path = markdown_file_path.parent / link_target.split("#")[0] if PROJECT_ROOT not in path.resolve().parents: return "link points outside of the Jou project folder" diff --git a/doc/classes.md b/doc/classes.md index 4c959007..2f9e99bc 100644 --- a/doc/classes.md +++ b/doc/classes.md @@ -67,7 +67,7 @@ def main() -> int: ``` Alternatively, we could create the instance in one line with `p = Point{x = 12, y = 34}`. -This syntax is explained in detail [below](#instantiating). +This syntax is explained in detail [below](#instantiating-syntax). ## Pointers From 30268a83b9f7946194f905ff6d7d509b06a62355 Mon Sep 17 00:00:00 2001 From: Akuli Date: Tue, 14 Jan 2025 09:17:56 +0200 Subject: [PATCH 5/5] Try to trigger ci