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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
# Chapter 17: Syntax Sugar
1. [Pre/Post-increment](#prepost-increment)
2. [Operator assignment](#operator-assignment)
3. [var/val](#var--val)
- [Example(1)](#example1)
- [Example(2)](#example2)
4. [Mutability](#mutability)
5. [Reference immutability](#reference-variables-with-an-initializer-are-deeply-immutable)
6. [Trinary](#trinary)
7. [For Loops](#for-loops)
## Pre/Post-increment
Allow `arg++` and `arg--` with the usual meanings; the value is updated and the
expression is the pre-update value. Greedy match is used so `arg---arg` parses
as `(arg--)-arg`.
Also allows `s.fld++` and `ary[idx]++`, and their `--` variants.
Allow pre-increment for identifiers only, for now: `--pc`.
## Operator assignment
Along the same lines as post-increment, we now allow `op=` assignment
on local variables with semantics similar to other languages. The supported
operators are `+=`, `-=`, `*=`, `/=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, and `>>>=`.
Bitwise and shift assignments require integer operands; `>>=` preserves the sign
while `>>>=` shifts in zeros. The assignment evaluates its right-hand side once,
stores the result narrowed to the variable's type, and yields that stored value.
Assignments associate right-to-left, as in `x |= y <<= 2`.
`x += y`
## var & val
`var` can be used in the `type` position to indicate a "variable" (mutable
value), whose type will be inferred from the required initalizing expression.
`val` is the same as `var`, except it is a "value" (not mutable).
The inferred value is the Greatest Lower Bound `glb` type of the peephole type,
which means `var` and `val` will not infer types like `u8` or `f32`, instead
inferring `int` and `flt` respectively. Reference types will always infer as
nullable, so e.g.
`var s = new S;`
infers as
`S s? = new S;`
## Mutability
Typed primitive fields are always mutable. Typed reference fields without an
initializer must be mutable to get a value. Initialized reference variables
are immutable by default and can be made mutable with a leading `!`. Contrast
this with what [chapter16](https://github.com/SeaOfNodes/Simple/tree/chapter16)
did with `!`. `var` and `val` keep their current sense and can be used to make
any field mutable or immutable. Fields are always mutable during construction,
but will become immutable at the end of either constructor.
`int x; x=3; x++; // OK, primitive so mutable`
`int x =3; x++; // Ok, primitive so mutable, despite initializer`
Assume in the next examples this struct exists:
`struct S { int x; };`
Then:
`S s; s = new S ; s.x++; // OK, no initializer so s.x is mutable`
`S s; s = new S{x=3}; s.x++; // OK, no initializer so s.x is mutable`
`S s = new S ; s.x++; // Error, initializer so s.x is immutable`
`S s = new S{x=3}; s.x++; // Error, initializer so s.x is immutable`
Leading `!` makes mutable:
`S !s = new S; s.x++; // Ok, has '!' so mutable`
`var` is variable, i.e. mutable:
`var s = new S; s.x++; // Ok, has var so mutable`
`val` is a "value": not mutable through this reference, but may be mutable
through other references.
`val s = new S; s.x++; // Error, val so s.x is immutable`
### Reference variables with an initializer are deeply immutable
Initialized reference variables are deeply immutable, but a mutable version of the same
reference can exist. This works for `val` and normal type declarations: `val s
= new S;` and `S s = new S` both make `s` an immutable reference to a `struct S`.
```cpp
struct Bar { int x; }
Bar !bar = new Bar; // '!' so bar is mutable
bar.x = 3; // Ok, bar is mutable
struct Foo { Bar !bar; int y; }
Foo !foo = new Foo; // '!' so foo is mutable
foo.bar = bar; // Ok foo is mutable
foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable
val xfoo = foo; // Throw away mutability deeply on foo
xfoo.bar.x++; // Error, cannot mutate through xfoo
print(xfoo.bar.x); // Ok to read through xfoo, prints 4
foo.bar.x++; // Bumps to 5
print(xfoo.bar.x); // Ok to read through xfoo, prints 5
```
## Trinary
Allow `pred ? e_true : e_false`. Also allow `pred ? e_true`, where the false result
is the zero type version of the true result.
## For Loops
Allow C/C++ style `for` loops:
`for( init; test; next ) body`
Example:
```cpp
int sum=0;
for( int i=0; i<arg; i++ )
sum += i;
return sum;
```
Any of `init`, `test` and `next` can be empty. `init` allows for declaration
of a new variable, with scope limited to the `for` expression.
```cpp
int sum=0;
for( int i=0; i<arg; i++ )
sum += i;
return i; // ERROR: Undefined name 'i'
```
A broken `find` call:
```cpp
for( int i=0; i<ary#; i++ )
if( ary[i]==e )
break;
do_stuff(i); // ERROR: undefined name 'i'
```
An example `find` call:
```cpp
for( int i=0; i<ary#; i++ )
if( ary[i]==e )
return i;
return -1;
```
## Memory effects
The lazy memory representation from [Chapter 10b](../chapter10b/README.md) and
constructor layout from [Chapter 16](../chapter16/README.md#memory-through-constructors)
continue here. ScopeNode tracks one `$mem` binding. Field and array writes,
including increments, update their alias in a MemMerge. Branches, conditional
expressions, and loops merge whole memory with BulkMemPhi; MemPhis are discovered
as field uses require them.
Readonly references retain their deep immutability when Loads fold through
Stores or allocations. Forward reference types still resolve lazily. Phi
factoring uses the same one-step Load safety check. Allocation memory remains
shared across the struct's aliases, so the sole-use Store-to-New check may
leave a Load that separate memory projections allowed to fold.
|