1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
# Basic Usage of Puck
```puck
```
Mutable variables are declared with `var`.
Immutable variables are declared with `let`.
Compile-time evaluated immutable variables are declared with `const`.
Comments are declared with `#`.
Documentation comments are declared with `##`.
Multi-line comments are declared with `#[ ]#` and may be nested.
Type annotations on variable declarations follow the name with `: Type` and are typically optional. The compiler is quite capable of variable inference.
The type system is comprehensive, and complex enough to [warrant its own document](TYPES.md).
```puck
```
Functions are declared with the `func` keyword, followed by the function name, followed by an (optional) list of parameters surrounded in parenthesis, followed by a type annotation. Functions may be prefixed with one or more of the following modifiers:
- `pub`: exports the function for use by external files
- `pure`: denotes a function as a "pure function", lacking side effects, i.e. IO or nondeterminism or parameter mutability
- `yeet`: denotes a function as a "throwing function", meaning it may raise exceptions.
- `async`: marks a function as asynchronous which may only be called by other asynchronous functions or with the `await` keyword
<!-- - `total`: idk -->
<!-- - more?? converter?? idk todo -->
<!-- There is an explicit ordering of these prefixes: everything << `pub` << `pure`. `pub` and `pure` are idk what i'm going for here is `async` and everything else should be macros providable by libraries. so `async` functions can be rewritten to use cps, etc depending on what you do, `await` is just a function that converts an `async T` to a `T`, `suspend`, `resume` are functions, etc. -->
A list of parameters, surrounded by parentheses and separated by commas, may follow the function name. These are optional and a function with no parameters may be followed with `()` or simply nothing at all. More information on function parameters (and return types) is available in the [type system overview](TYPES.md).
Type annotations on function declarations follow the name and parameters (if any) with `: Type` and are typically required. The compiler is not particularly capable of function type inference (and it is good practice to annotate them anyway).
Uniform function call syntax (UFCS) is supported: and so arbitrary functions with compatible types may be chained with no more than the `.` operator.
<!-- All assignments may be overridden, but overriding values outside of shadowing (assigning the value of an immutable parameter to a mutable variable) will be a compiler warning. -->
```puck
```
Boolean logic and integer operations are standard and as one would expect out of a typed language: `and`, `or`, `xor`, `not`, `shl`, `shr`, `+`, `-`, `*`, `/`, `<`, `>`, `<=`, `>=`, `div`, `mod`, `rem`. Notably:
- the words `and`/`or`/`not`/`shl`/`shr` are used instead of the symbolic `&&`/`||`/`!`/`<<`/`>>`
- integer division is expressed with the keyword `div` while floating point division uses `/`
- `%` is absent and replaced with distinct modulus and remainder operators
- boolean operators are bitwise and also apply to integers and floats
- more operators are available via the standard library
Term in/equality is expressed with `==` and `!=`. Type in/equality is expressed with `is` and `isnot` (more on this in the [types document](TYPES.md)). Set logic is expressed with `in` and `notin`, and is applicable to not just sets but collections of any sort.
String concatenation uses `&` rather than overloading the `+` operator (as the complement `-` has no natural meaning for strings). Strings are also unified and mutable. More details can be found in the [type system overview](TYPES.md).
```puck
```
Basic conditional control flow is standard via `if`, `elif`, and `else` statements.
There is a distinction between statements, which do not produce a value but rather only execute computations, and expressions, which evaluate to a value. Several control flow constructs - conditionals, block statements, and pattern matches - may be used as both statements and expressions.
The special `discard` statement allows for throwing an expression's value away. On its own, it provides a no-op. All (non-void) expressions must be handled: however, a non-discarded expression at the end of a scope functions as an implicit return. This allows for significant syntactic reduction.
```puck
```
Three types of loops are available: `while` loops, `for` loops, and infinite loops (`loop` loops). While loops take a condition that is executed upon the beginning of each iteration to determine whether to keep looping. For loops take a binding (which may be structural, see pattern matching) and an iterable object and will loop until the iterable object is spent. Infinite loops are, well, infinite and must be manually broken out of.
There is no special concept of iterators: iterable objects are any object that implements the Iterable interface (more on those in [the type system document](TYPES.md)), that is, provides a `self.next()` function returning an Optional type. For loops desugar to while loops that unwrap the result of the `next()` function and end iteration upon a `None` value. While loops, in turn, desugar to infinite loops with an explicit conditional break.
The `break` keyword immediately breaks out of the current loop.
The `continue` keyword immediately jumps to the next iteration of the current loop.
Loops may be used in conjunction with blocks for more fine-grained control flow manipulation.
```puck
```
Blocks provide arbitrary scope manipulation. They may be labelled or unlabelled. The `break` keyword additionally functions inside of blocks and without any parameters will jump out of the current enclosing block (or loop). It may also take a block label as a parameter for fine-grained scope control.
All forms of control flow ultimately desugar to continuations: https://github.com/nim-works/cps/tree/master/docs
```puck
```
Exhaustive structural pattern matching is available and particularly useful for tagged unions. This is frequently a better alternative to a series of `if` statements.
```puck
```
I am undecided on how the import/module system will work and particularly how it will play into the type system. UFCS *will* be supported. todo
More details may be found in the [modules document](MODULES.md).
```puck
```
Compile-time programming may be done via the previously-mentioned `const` keyword: or via `static` blocks. All code within a `static` block is evaluated at compile-time and all assignments made are propagated to the compiled binary. As a result, `static` blocks are only available in the global context (not within functions).
Compile-time programming may also be intertwined in the codebase with the use of the `when` statement. It functions similarly to `if`, but may only take a static operation as its parameter, and will directly replace code accordingly at compile-time. The `else` statement is overloaded to complement this.
Further compile-time programming may be done via metaprogramming: compile-time introspection on the abstract syntax tree.
Two distinct language constructs of differing complexity are provided: templates for raw substitution, and macros for direct manipulation of the abstract syntax tree. These are complex, and more details may be found in the [metaprogramming document](METAPROGRAMMING.md).
```puck
```
Error handling is typically done via explicitly matching upon Optional and Result values (with the help of the `?` operator), but such functions can be made to explicitly throw exceptions (which may then be caught via `try`/`catch`/`finally` or thrown with `raise`) with the help of the `!` operator. This is complex and necessarily verbose, although a bevy of helper functions and syntactic sugar are available to ease usage. More details may be found in [error handling overview](ERRORS.md).
```puck
```
Threading support is complex and regulated to external libraries (with native syntax via macros). OS-provided primitives will likely provide a `spawn` function, and there will be substantial restrictions for memory safety. I haven't thought much about this.
Async support is complex and relegated to external libraries (with native syntax via macros). More details may be found in the [async document](ASYNC.md). It is likely that this will look like Zig, with `async`/`await`/`suspend`/`resume`.
Effects are complex and relegated to external libraries (with native syntax via macros). More details may be found in the [effects document](EFFECTS.md).
```puck
```
Details on memory safety, references and pointers, and deep optimizations may be found in the [memory management overview](MEMORY_MANAGEMENT.md).
The memory model intertwines deeply with the type system.
|