Skip to content

Commit 95e2910

Browse files
authored
Add array_end() built-in (#1266)
1 parent 4a9ccb2 commit 95e2910

File tree

12 files changed

+111
-8
lines changed

12 files changed

+111
-8
lines changed

compiler/ast.jou

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ enum AstExpressionKind:
255255
EnumCount
256256
# unary operators
257257
SizeOf # sizeof(x)
258-
ArrayCount # array_count x
258+
ArrayCount # array_count(x)
259+
ArrayEnd # array_end(x)
259260
EmbedFile # embed_file("./foo.bar")
260261
AddressOf # &x
261262
Dereference # *x
@@ -368,6 +369,8 @@ class AstExpression:
368369
printf("embed_file\n")
369370
case AstExpressionKind.ArrayCount:
370371
printf("array_count\n")
372+
case AstExpressionKind.ArrayEnd:
373+
printf("array_end\n")
371374
case AstExpressionKind.AddressOf:
372375
printf("address of\n")
373376
case AstExpressionKind.Dereference:
@@ -466,6 +469,7 @@ class AstExpression:
466469
AstExpressionKind.SizeOf
467470
| AstExpressionKind.EmbedFile
468471
| AstExpressionKind.ArrayCount
472+
| AstExpressionKind.ArrayEnd
469473
| AstExpressionKind.AddressOf
470474
| AstExpressionKind.Dereference
471475
| AstExpressionKind.Negate

compiler/builders/ast_to_builder.jou

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,13 @@ class AstToBuilder:
487487
array_type = expr.operands[0].types.implicit_cast_type
488488
assert array_type.kind == TypeKind.Array
489489
return self.builder.integer(int_type(32), array_type.array.count)
490+
case AstExpressionKind.ArrayEnd:
491+
array_type = expr.operands[0].types.implicit_cast_type
492+
assert array_type.kind == TypeKind.Array
493+
ptr_to_array = self.build_address_of_expression(&expr.operands[0])
494+
ptr_to_start = self.builder.cast(ptr_to_array, array_type.array.item_type.pointer_type())
495+
count = self.builder.integer(int_type(64), array_type.array.count)
496+
return self.builder.indexed_pointer(ptr_to_start, count)
490497
case AstExpressionKind.AddressOf:
491498
return self.build_address_of_expression(&expr.operands[0])
492499
case AstExpressionKind.Dereference:

compiler/parser.jou

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -688,12 +688,15 @@ class Parser:
688688
case "array_count":
689689
expr.kind = AstExpressionKind.ArrayCount
690690
error = "value after array_count must be in parentheses, e.g. array_count(foo)"
691-
case "enum_count":
692-
expr.kind = AstExpressionKind.EnumCount
693-
error = "enum type after enum_count must be in parentheses, e.g. enum_count(Foo)"
691+
case "array_end":
692+
expr.kind = AstExpressionKind.ArrayEnd
693+
error = "value after array_end must be in parentheses, e.g. array_end(foo)"
694694
case "embed_file":
695695
expr.kind = AstExpressionKind.EmbedFile
696696
error = "file name after embed_file must be in parentheses, e.g. embed_file(\"foo.txt\")"
697+
case "enum_count":
698+
expr.kind = AstExpressionKind.EnumCount
699+
error = "enum type after enum_count must be in parentheses, e.g. enum_count(Foo)"
697700
case _:
698701
assert False
699702

@@ -767,6 +770,7 @@ class Parser:
767770
elif (
768771
self.tokens.is_keyword("sizeof")
769772
or self.tokens.is_keyword("array_count")
773+
or self.tokens.is_keyword("array_end")
770774
or self.tokens.is_keyword("embed_file")
771775
or self.tokens.is_keyword("enum_count")
772776
):

compiler/tokenizer.jou

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def is_keyword(word: byte*) -> bool:
3434
or strcmp(word, "as") == 0
3535
or strcmp(word, "assert") == 0
3636
or strcmp(word, "array_count") == 0
37+
or strcmp(word, "array_end") == 0
3738
)
3839
case 'b':
3940
return (

compiler/typecheck/step3_function_and_method_bodies.jou

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def short_expression_description(expr: AstExpression*) -> byte[200]:
6060
return "a sizeof expression"
6161
case AstExpressionKind.ArrayCount:
6262
return "an array_count expression"
63+
case AstExpressionKind.ArrayEnd:
64+
return "an array_end expression"
6365
case AstExpressionKind.EmbedFile:
6466
return "an embed_file expression"
6567
case AstExpressionKind.EnumCount:
@@ -1178,6 +1180,13 @@ def typecheck_expression(state: State*, expr: AstExpression*, type_hint: Type*)
11781180
fail(expr.location, msg)
11791181
result = int_type(32)
11801182

1183+
case AstExpressionKind.ArrayEnd:
1184+
array_type = typecheck_expression_not_void(state, &expr.operands[0], NULL)
1185+
if array_type.kind != TypeKind.Array:
1186+
snprintf(msg, sizeof(msg), "array_end must be called on an array, not %s", array_type.name)
1187+
fail(expr.location, msg)
1188+
result = array_type.array.item_type.pointer_type()
1189+
11811190
case AstExpressionKind.Instantiate:
11821191
result = typecheck_instantiation(state, &expr.instantiation, expr.location)
11831192

doc/keywords.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,39 @@ def main() -> int:
4343
```
4444

4545

46+
## `array_end`
47+
48+
Use `array_end(array)` to get a pointer just beyond the last element of an array.
49+
This is same as `&array[array_count(array)]`.
50+
51+
For example:
52+
53+
```python
54+
import "stdlib/io.jou"
55+
56+
def main() -> int:
57+
array = [1, 2, 3, 4]
58+
printf("%d\n", array_end(array)[-1]) # Output: 4
59+
return 0
60+
```
61+
62+
The most common use for `array_end()` is [looping through an array](loops.md#looping-through-an-array) with pointers:
63+
64+
```python
65+
import "stdlib/io.jou"
66+
67+
def main() -> int:
68+
array = [1, 2, 3, 4]
69+
# Output: 1
70+
# Output: 2
71+
# Output: 3
72+
# Output: 4
73+
for p = &array[0]; p < array_end(array); p++:
74+
printf("%d\n", *p)
75+
return 0
76+
```
77+
78+
4679
## `as`
4780

4881
The `as` keyword does an explicit cast. See [the documentation on casts](types.md#casts).

doc/loops.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def main() -> int:
253253
return 0
254254
```
255255
256-
Looping through an array with pointers:
256+
Looping through an array with pointers (see [`array_end` documentation](keywords.md#array_end)):
257257
258258
```python
259259
import "stdlib/io.jou"
@@ -264,7 +264,7 @@ def main() -> int:
264264
# Output: 123
265265
# Output: 456
266266
# Output: 789
267-
for p = &array[0]; p < &array[array_count(array)]; p++:
267+
for p = &array[0]; p < array_end(array); p++:
268268
printf("%d\n", *p)
269269
270270
return 0
@@ -304,6 +304,24 @@ def main() -> int:
304304
return 0
305305
```
306306
307+
Instead of `&array[array_count(array) - 1]`, you can also use `&array_end(array)[-1]`.
308+
This may be convenient, because you need to write `array` only once.
309+
310+
```python
311+
import "stdlib/io.jou"
312+
313+
def main() -> int:
314+
array = [123, 456, 789]
315+
316+
# Output: 789
317+
# Output: 456
318+
# Output: 123
319+
for p = &array_end(array)[-1]; p >= &array[0]; p--:
320+
printf("%d\n", *p)
321+
322+
return 0
323+
```
324+
307325
If you don't use all elements of the array, and you instead store the number of used elements in a variable,
308326
you can use that instead of `array_count(array)` above.
309327
For example:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def blah() -> None:
2+
x: int[10]
3+
array_end(x) = &x[20] # Error: cannot assign to an array_end expression

tests/should_succeed/array.jou

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@ def main() -> int:
5353
increment(foo as int*) # same cast explicitly
5454
printf("%d %d %d\n", foo[0], foo[1], foo[2]) # Output: 7 5 6
5555

56+
# Looping through array with indexes
57+
# Output: 756
58+
for i = 0; i < array_count(foo); i++:
59+
printf("%d", foo[i])
60+
printf("\n")
61+
62+
# Looping through array with pointers (old way)
63+
# Output: 756
64+
for p = &foo[0]; p < &foo[array_count(foo)]; p++:
65+
printf("%d", *p)
66+
printf("\n")
67+
68+
# Looping through array with pointers (new way)
69+
# Output: 756
70+
for p = &foo[0]; p < array_end(foo); p++:
71+
printf("%d", *p)
72+
printf("\n")
73+
5674
# corner case: byte* <--> void* can be converted both ways, use byte*
5775
strings = ["hello", NULL, "world", NULL]
5876
printf("%s %s\n", strings[0], strings[2]) # Output: hello world

tests/should_succeed/math_test.jou

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def main() -> int:
5050
# Output: 0 0 1
5151
# Output: 0 0 1
5252
# Output: 0 0 1
53-
for d = &test_doubles[0]; d < &test_doubles[array_count(test_doubles)]; d++:
53+
for d = &test_doubles[0]; d < array_end(test_doubles); d++:
5454
printf("%d %d %d\n", isfinite(*d), isinf(*d), isnan(*d))
5555

5656
# Output: 1 0 0
@@ -65,7 +65,7 @@ def main() -> int:
6565
# Output: 0 0 1
6666
# Output: 0 0 1
6767
# Output: 0 0 1
68-
for d = &test_doubles[0]; d < &test_doubles[array_count(test_doubles)]; d++:
68+
for d = &test_doubles[0]; d < array_end(test_doubles); d++:
6969
printf("%d %d %d\n", isfinite(*d as float), isinf(*d as float), isnan(*d as float))
7070

7171
printf("%d\n", abs(-1)) # Output: 1

0 commit comments

Comments
 (0)