nomen-lang 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,9 @@
1
1
  /**
2
- * An ordered key-value map with `set`/`get`/`has`/`remove`
2
+ * An ordered key-value map with `set`/`get`/`has`/`remove`. Keys must be
3
+ * `Hashable` (hashed to a `uint` via `key.hash()`) and `Equatable` (compared
4
+ * with `==`); values may be any type.
3
5
  **/
4
- pub struct Map<TK, TV> {
6
+ pub struct Map<TK: Hashable + Equatable, TV> {
5
7
  var length = 0
6
8
  var Buffer<TK> keys = Buffer<TK>()
7
9
  var Buffer<TV> values = Buffer<TV>()
@@ -20,8 +22,8 @@ pub struct Map<TK, TV> {
20
22
  pub func set = (ref self, mov TK key, mov TV value) {
21
23
  var int cap = self.keys.cap
22
24
  if cap == 0 {
23
- cap = self.keys.alloc_int(8)
24
- self.values.alloc_int(8)
25
+ cap = self.keys.alloc_T(8)
26
+ self.values.alloc_T(8)
25
27
  self.used.alloc(8)
26
28
  }
27
29
  if self.length * 4 >= cap * 3 {
@@ -31,11 +33,11 @@ pub struct Map<TK, TV> {
31
33
  var int idx = self.find_slot(key, cap)
32
34
  if self.used.load(idx) == 0 {
33
35
  self.used.store(idx, 1)
34
- self.keys.store_int(idx, key)
35
- self.values.store_int(idx, value)
36
+ self.keys.store_T(idx, key)
37
+ self.values.store_T(idx, value)
36
38
  self.length += 1
37
39
  } else {
38
- self.values.store_int(idx, value)
40
+ self.values.replace_T(idx, value)
39
41
  }
40
42
  }
41
43
 
@@ -48,7 +50,7 @@ pub struct Map<TK, TV> {
48
50
  if self.used.load(idx) == 0 {
49
51
  return 0
50
52
  }
51
- return self.values.load_int(idx)
53
+ return self.values.load_T(idx)
52
54
  }
53
55
 
54
56
  pub func has = (self, TK key, out bool) {
@@ -89,7 +91,7 @@ pub struct Map<TK, TV> {
89
91
  if self.used.load(k) == 0 {
90
92
  break
91
93
  }
92
- var int home = self.keys.load_int(k) % cap
94
+ var int home = (self.keys.load_T(k).hash() as int) % cap
93
95
  if home < 0 {
94
96
  home = home + cap
95
97
  }
@@ -102,26 +104,23 @@ pub struct Map<TK, TV> {
102
104
  dk = dk + cap
103
105
  }
104
106
  if dgap < dk {
105
- self.keys.store_int(gap, self.keys.load_int(k))
106
- self.values.store_int(gap, self.values.load_int(k))
107
+ self.keys.store_T(gap, self.keys.load_T(k))
108
+ self.values.store_T(gap, self.values.load_T(k))
107
109
  self.used.store(gap, 1)
108
110
  gap = k
109
111
  }
110
112
  }
111
113
  self.used.store(gap, 0)
112
- self.keys.store_int(gap, 0)
113
- self.values.store_int(gap, 0)
114
114
  self.length = self.length - 1
115
115
  }
116
116
 
117
117
  func find_slot = (self, TK key, int cap, out int: out >= 0 && out < cap) {
118
- var int k = key
119
- var int idx = k % cap
118
+ var int idx = (key.hash() as int) % cap
120
119
  if idx < 0 {
121
120
  idx = idx + cap
122
121
  }
123
122
  while self.used.load(idx) != 0 {
124
- if self.keys.load_int(idx) == k {
123
+ if self.keys.load_T(idx) == key {
125
124
  return idx
126
125
  }
127
126
  idx += 1
@@ -138,15 +137,26 @@ pub struct Map<TK, TV> {
138
137
  var Buffer<int> old_used = mov self.used swap Buffer<int>()
139
138
  var int old_cap = old_keys.cap
140
139
 
141
- self.keys.alloc_int(new_cap)
142
- self.values.alloc_int(new_cap)
140
+ self.keys.alloc_T(new_cap)
141
+ self.values.alloc_T(new_cap)
143
142
  self.used.alloc(new_cap)
144
143
  self.length = 0
145
144
 
146
145
  var i = 0
147
146
  while i < old_cap; i += 1 {
148
147
  if old_used.load(i) != 0 {
149
- self.set(old_keys.load_int(i), old_values.load_int(i))
148
+ // move_T (not load_T) relinquishes the old slot, so each
149
+ // key/value is owned by exactly one buffer (the new self)
150
+ // afterwards. load_T would leave the old slot referencing
151
+ // the same class pointer — a double-free when old_keys is
152
+ // destroyed. The results are bound to locals so an
153
+ // owning-element copy (e.g. a `Buffer<string>` slot, which
154
+ // store_T strdup's) is freed here once the new buffer has
155
+ // made its own copy — passing them inline would orphan the
156
+ // move_T results.
157
+ var TK k = old_keys.move_T(i)
158
+ var TV v = old_values.move_T(i)
159
+ self.set(mov k, mov v)
150
160
  }
151
161
  }
152
162
  }
@@ -0,0 +1,4 @@
1
+ pub enum Option<T> {
2
+ case some(T value)
3
+ case none
4
+ }
@@ -0,0 +1,4 @@
1
+ pub enum Result<T, E> {
2
+ case ok(T value)
3
+ case error(E error)
4
+ }
@@ -1,7 +1,9 @@
1
1
  /**
2
- * An ordered set of unique values (`add`/`has`/`get`/`remove`)
2
+ * An ordered set of unique values (`add`/`has`/`get`/`remove`). Elements must
3
+ * be `Hashable` (hashed to a `uint` via `value.hash()`) and `Equatable`
4
+ * (compared with `==`).
3
5
  **/
4
- pub struct Set<T> {
6
+ pub struct Set<T: Hashable + Equatable> {
5
7
  var length = 0
6
8
  var Buffer<T> slots = Buffer<T>()
7
9
  var used = Buffer<int>()
@@ -9,7 +11,7 @@ pub struct Set<T> {
9
11
  pub func add = (ref self, mov T value) {
10
12
  var int cap = self.slots.cap
11
13
  if cap == 0 {
12
- cap = self.slots.alloc_int(8)
14
+ cap = self.slots.alloc_T(8)
13
15
  self.used.alloc(8)
14
16
  }
15
17
  if self.length * 4 >= cap * 3 {
@@ -19,7 +21,7 @@ pub struct Set<T> {
19
21
  var int idx = self.find_slot(value, cap)
20
22
  if self.used.load(idx) == 0 {
21
23
  self.used.store(idx, 1)
22
- self.slots.store_int(idx, value)
24
+ self.slots.store_T(idx, value)
23
25
  self.length += 1
24
26
  }
25
27
  }
@@ -42,7 +44,7 @@ pub struct Set<T> {
42
44
  if self.used.load(idx) == 0 {
43
45
  return 0
44
46
  }
45
- return self.slots.load_int(idx)
47
+ return self.slots.load_T(idx)
46
48
  }
47
49
 
48
50
  pub func remove = (ref self, T value) {
@@ -54,20 +56,55 @@ pub struct Set<T> {
54
56
  if self.used.load(idx) == 0 {
55
57
  return
56
58
  }
57
- self.used.store(idx, 0)
58
- self.slots.store_int(idx, 0)
59
+ // Backward-shift deletion for linear probing. `gap` is the empty slot
60
+ // walking forward through the cluster; `k` scans ahead of it. An entry
61
+ // at k shifts into the gap only if the gap still lies within its valid
62
+ // probe range ([home, k) cyclically); otherwise the entry stays put and
63
+ // we keep scanning, because a later entry may still belong behind the
64
+ // gap. This is the subtle point: k advances independently of the gap,
65
+ // so entries past a non-moveable one are not stranded behind an empty
66
+ // slot (which would make find_slot miss them). No tombstones, so
67
+ // find_slot/get/has/add are unchanged, and the cost is the local cluster
68
+ // length — not a full O(cap) rehash on every delete.
69
+ var int gap = idx
70
+ var int k = idx
71
+ while true {
72
+ k += 1
73
+ if k >= cap {
74
+ k = 0
75
+ }
76
+ if self.used.load(k) == 0 {
77
+ break
78
+ }
79
+ var int home = (self.slots.load_T(k).hash() as int) % cap
80
+ if home < 0 {
81
+ home = home + cap
82
+ }
83
+ var int dgap = gap - home
84
+ if dgap < 0 {
85
+ dgap = dgap + cap
86
+ }
87
+ var int dk = k - home
88
+ if dk < 0 {
89
+ dk = dk + cap
90
+ }
91
+ if dgap < dk {
92
+ self.slots.store_T(gap, self.slots.load_T(k))
93
+ self.used.store(gap, 1)
94
+ gap = k
95
+ }
96
+ }
97
+ self.used.store(gap, 0)
59
98
  self.length = self.length - 1
60
- self.rehash(cap)
61
99
  }
62
100
 
63
101
  func find_slot = (self, T value, int cap, out int: out >= 0 && out < cap) {
64
- var int v = value
65
- var int idx = v % cap
102
+ var int idx = (value.hash() as int) % cap
66
103
  if idx < 0 {
67
104
  idx = idx + cap
68
105
  }
69
106
  while self.used.load(idx) != 0 {
70
- if self.slots.load_int(idx) == v {
107
+ if self.slots.load_T(idx) == value {
71
108
  return idx
72
109
  }
73
110
  idx += 1
@@ -83,14 +120,23 @@ pub struct Set<T> {
83
120
  var Buffer<int> old_used = mov self.used swap Buffer<int>()
84
121
  var int old_cap = old_slots.cap
85
122
 
86
- self.slots.alloc_int(new_cap)
123
+ self.slots.alloc_T(new_cap)
87
124
  self.used.alloc(new_cap)
88
125
  self.length = 0
89
126
 
90
127
  var i = 0
91
128
  while i < old_cap; i += 1 {
92
129
  if old_used.load(i) != 0 {
93
- self.add(old_slots.load_int(i))
130
+ // move_T (not load_T) relinquishes the old slot, so each value
131
+ // is owned by exactly one buffer (the new self) afterwards.
132
+ // load_T would leave the old slot referencing the same class
133
+ // pointer — a double-free when old_slots is destroyed. The
134
+ // result is bound to a local so an owning-element copy (e.g. a
135
+ // `Buffer<string>` slot, which store_T strdup's) is freed here
136
+ // once the new buffer has made its own copy — passing it inline
137
+ // would orphan the move_T result.
138
+ var T v = old_slots.move_T(i)
139
+ self.add(mov v)
94
140
  }
95
141
  }
96
142
  }
@@ -1,7 +1,33 @@
1
1
  /**
2
2
  * A UTF-8 text string — a heap-owned, length-tracked sequence of bytes
3
3
  **/
4
- pub struct string: Stringable {
4
+ pub struct string: Stringable, Hashable, Equatable {
5
+ pub func hash = (self, out uint) {
6
+ ```
7
+ #arch: c
8
+ unsigned long h = 5381;
9
+ for (const char* p = self; *p; p++) {
10
+ h = h * 33 + (unsigned long)(unsigned char)*p;
11
+ }
12
+ return h;
13
+ ```
14
+ ```
15
+ #arch: aarch64
16
+ // x19 = self (char*), result in x0 (djb2 hash)
17
+ mov x0, #5381
18
+ mov x3, x19
19
+ .Lstring_hash_loop:
20
+ ldrb w1, [x3]
21
+ cbz w1, .Lstring_hash_done
22
+ mov x2, #33
23
+ mul x0, x0, x2
24
+ add x0, x0, x1
25
+ add x3, x3, #1
26
+ b .Lstring_hash_loop
27
+ .Lstring_hash_done:
28
+ ```
29
+ }
30
+
5
31
  pub func to_string = (self, out string) {
6
32
  ```
7
33
  #arch: c
@@ -129,9 +129,7 @@ pub class Tester {
129
129
  // Assert that a nullable value holds a value.
130
130
  //
131
131
  // Nomen has no generic methods, so there is one overload per nullable
132
- // type the compiler can compare against `null`. Nullable *struct* values
133
- // (`Point?`) are not supported by the backend yet — use
134
- // `t.expect(p != null, "...")` once that gap closes.
132
+ // type the compiler can compare against `null`.
135
133
  pub func assert = (ref self, int? value, string message) {
136
134
  self.expect(value != null, message)
137
135
  }
@@ -32,12 +32,15 @@ pub struct JsonTree {
32
32
  self.count = idx + 1
33
33
  var default = JsonNode()
34
34
  self.nodes.store_T(idx, default)
35
- // The default node's `text` points at the shared empty-string literal.
36
- // NULL it in the slab so reset() can safely free only strdup'd texts
37
- // (set_text writes a heap pointer; unset nodes stay NULL). idx is x1.
35
+ // store_T deep-copies the owning `text` field (strdup of the shared
36
+ // empty-string literal) into the slot. JsonTree manages node texts
37
+ // manually (set_text strdup's into the slab; free_text/release frees
38
+ // them; NULL means "no text"), so the slot must start NULL — free the
39
+ // strdup'd default and NULL it, or reset() would leak it. idx is x1.
38
40
  ```
39
41
  #arch: c
40
42
  JsonNode *nodes = (JsonNode*)(unsigned long long)self->nodes.data;
43
+ free(nodes[idx].text);
41
44
  nodes[idx].text = 0;
42
45
  ```
43
46
  ```
@@ -51,7 +54,12 @@ pub struct JsonTree {
51
54
  madd x1, x1, x3, xzr
52
55
  add x0, x0, x1
53
56
  add x0, x0, #16
57
+ ldr x1, [x0]
54
58
  str xzr, [x0]
59
+ cbz x1, .Lalloc_node_text_skip
60
+ mov x0, x1
61
+ bl _free
62
+ .Lalloc_node_text_skip:
55
63
  ```
56
64
  return idx
57
65
  }
@@ -254,6 +254,9 @@ func add_class_chars = (string pattern, int start, int end, ref StringBuilder sb
254
254
 
255
255
  // Match pattern[pat_pos..pat_end) against input starting at in_pos.
256
256
  // Returns the end position in input after a successful match, or -1.
257
+ // Return contract: a successful match never reads past in_len, so the result
258
+ // is always <= in_len (and -1 on failure). This lets callers prove an index
259
+ // bounded by the result is also bounded by the input length.
257
260
  func match_here = (
258
261
  string pattern,
259
262
  int pat_pos,
@@ -261,7 +264,7 @@ func match_here = (
261
264
  string input,
262
265
  int in_pos,
263
266
  int in_len,
264
- out int
267
+ out int: out <= in_len
265
268
  ) {
266
269
  // Handle top-level alternation: a '|' at bracket/paren depth 0 splits the
267
270
  // range into alternatives. Char classes and escapes are skipped so a '|' or
@@ -1,7 +1,18 @@
1
1
  /**
2
2
  * A boolean value (`true` or `false`)
3
3
  **/
4
- pub struct bool: Stringable {
4
+ pub struct bool: Stringable, Hashable, Equatable {
5
+ pub func hash = (self, out uint) {
6
+ ```
7
+ #arch: c
8
+ return (unsigned long)self;
9
+ ```
10
+ ```
11
+ #arch: aarch64
12
+ mov x0, x19
13
+ ```
14
+ }
15
+
5
16
  /**
6
17
  * Converts this boolean to a string value (`"true"` or `"false"`)
7
18
  *
@@ -1,7 +1,18 @@
1
1
  /**
2
2
  * A single character
3
3
  **/
4
- pub struct char: Stringable {
4
+ pub struct char: Stringable, Hashable, Equatable {
5
+ pub func hash = (self, out uint) {
6
+ ```
7
+ #arch: c
8
+ return (unsigned long)self;
9
+ ```
10
+ ```
11
+ #arch: aarch64
12
+ mov x0, x19
13
+ ```
14
+ }
15
+
5
16
  pub func to_string = (self, out string) {
6
17
  ```
7
18
  #arch: c
@@ -1,7 +1,18 @@
1
1
  /**
2
2
  * A signed integer — the default integer type
3
3
  **/
4
- pub struct int: Stringable {
4
+ pub struct int: Stringable, Hashable, Equatable {
5
+ pub func hash = (self, out uint) {
6
+ ```
7
+ #arch: c
8
+ return (unsigned long)self;
9
+ ```
10
+ ```
11
+ #arch: aarch64
12
+ mov x0, x19
13
+ ```
14
+ }
15
+
5
16
  pub func to_string = (self, out string) {
6
17
  ```
7
18
  #arch: c
@@ -1,7 +1,18 @@
1
1
  /**
2
2
  * A 64-bit signed integer
3
3
  **/
4
- pub struct int64: Stringable {
4
+ pub struct int64: Stringable, Hashable, Equatable {
5
+ pub func hash = (self, out uint) {
6
+ ```
7
+ #arch: c
8
+ return (unsigned long)self;
9
+ ```
10
+ ```
11
+ #arch: aarch64
12
+ mov x0, x19
13
+ ```
14
+ }
15
+
5
16
  pub func to_string = (self, out string) {
6
17
  ```
7
18
  #arch: c
@@ -1,7 +1,18 @@
1
1
  /**
2
2
  * An 8-bit signed integer
3
3
  **/
4
- pub struct int8: Stringable {
4
+ pub struct int8: Stringable, Hashable, Equatable {
5
+ pub func hash = (self, out uint) {
6
+ ```
7
+ #arch: c
8
+ return (unsigned long)self;
9
+ ```
10
+ ```
11
+ #arch: aarch64
12
+ mov x0, x19
13
+ ```
14
+ }
15
+
5
16
  pub func to_string = (self, out string) {
6
17
  ```
7
18
  #arch: c
@@ -1,7 +1,18 @@
1
1
  /**
2
2
  * An unsigned integer
3
3
  **/
4
- pub struct uint: Stringable {
4
+ pub struct uint: Stringable, Hashable, Equatable {
5
+ pub func hash = (self, out uint) {
6
+ ```
7
+ #arch: c
8
+ return (unsigned long)self;
9
+ ```
10
+ ```
11
+ #arch: aarch64
12
+ mov x0, x19
13
+ ```
14
+ }
15
+
5
16
  pub func to_string = (self, out string) {
6
17
  ```
7
18
  #arch: c
@@ -1,7 +1,18 @@
1
1
  /**
2
2
  * A 64-bit unsigned integer
3
3
  **/
4
- pub struct uint64: Stringable {
4
+ pub struct uint64: Stringable, Hashable, Equatable {
5
+ pub func hash = (self, out uint) {
6
+ ```
7
+ #arch: c
8
+ return (unsigned long)self;
9
+ ```
10
+ ```
11
+ #arch: aarch64
12
+ mov x0, x19
13
+ ```
14
+ }
15
+
5
16
  pub func to_string = (self, out string) {
6
17
  ```
7
18
  #arch: c
@@ -1,7 +1,18 @@
1
1
  /**
2
2
  * An 8-bit unsigned integer
3
3
  **/
4
- pub struct uint8: Stringable {
4
+ pub struct uint8: Stringable, Hashable, Equatable {
5
+ pub func hash = (self, out uint) {
6
+ ```
7
+ #arch: c
8
+ return (unsigned long)self;
9
+ ```
10
+ ```
11
+ #arch: aarch64
12
+ mov x0, x19
13
+ ```
14
+ }
15
+
5
16
  pub func to_string = (self, out string) {
6
17
  ```
7
18
  #arch: c
@@ -2,7 +2,8 @@
2
2
 
3
3
  ## `struct Init`
4
4
 
5
- Program startup state — the argument count and argument list passed to `main`
5
+ Program startup state — the argument count and argument list passed to `main`,
6
+ plus `is_tty` (whether stdout is a terminal, for renderer selection).
6
7
 
7
8
  ## `func parse_intparse_int(string s) -> int`
8
9
 
@@ -0,0 +1,23 @@
1
+ # Option
2
+
3
+ ## `enum Option<T>`
4
+
5
+ A generic optional type with a `some(T value)` case and a `none` case.
6
+ Monomorphized per concrete type argument (e.g. `Option<int>`).
7
+
8
+ ```
9
+ func find_first_even = (int[] xs, out Option<int>) {
10
+ for x of xs {
11
+ if x % 2 == 0 {
12
+ return .some(x)
13
+ }
14
+ }
15
+ return .none
16
+ }
17
+
18
+ const found = find_first_even([1, 3, 4])
19
+ match found {
20
+ case .some(v) -> Console.write("\\{v}")
21
+ case .none -> Console.write("none")
22
+ }
23
+ ```
@@ -0,0 +1,19 @@
1
+ # Result
2
+
3
+ ## `enum Result<T, E>`
4
+
5
+ A generic result type with an `ok(T value)` success case and an
6
+ `error(E error)` failure case. Monomorphized per concrete type-argument pair
7
+ (e.g. `Result<int, string>`).
8
+
9
+ ```
10
+ func parse_age = (string s, out Result<int, string>) {
11
+ return .error("not a number")
12
+ }
13
+
14
+ const result = parse_age("x")
15
+ match result {
16
+ case .ok(age) -> Console.write("\\{age}")
17
+ case .error(msg) -> Console.write("error: \\{msg}")
18
+ }
19
+ ```