nomen-lang 0.8.0 → 0.9.0

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.
Files changed (73) hide show
  1. package/audit_runtime.c +12 -2
  2. package/core/System/Arena.nm +8 -1
  3. package/core/System/Buffer.nm +366 -9
  4. package/core/System/ClassBuffer.nm +133 -1
  5. package/core/System/Controls/Alert.nm +2 -2
  6. package/core/System/Controls/App.nm +12 -12
  7. package/core/System/Controls/Button.nm +1 -1
  8. package/core/System/Controls/CheckBox.nm +2 -2
  9. package/core/System/Controls/ColorPicker.nm +1 -1
  10. package/core/System/Controls/ComboBox.nm +1 -1
  11. package/core/System/Controls/Container.nm +7 -1
  12. package/core/System/Controls/GroupBox.nm +1 -1
  13. package/core/System/Controls/ImageView.nm +1 -1
  14. package/core/System/Controls/LevelIndicator.nm +1 -1
  15. package/core/System/Controls/ListView.nm +1 -1
  16. package/core/System/Controls/Menu.nm +1 -1
  17. package/core/System/Controls/OutlineView.nm +1 -1
  18. package/core/System/Controls/Pagination.nm +8 -8
  19. package/core/System/Controls/Popover.nm +1 -1
  20. package/core/System/Controls/Progress.nm +1 -1
  21. package/core/System/Controls/Radio.nm +2 -2
  22. package/core/System/Controls/SearchField.nm +1 -1
  23. package/core/System/Controls/SecureField.nm +1 -1
  24. package/core/System/Controls/Segmented.nm +1 -1
  25. package/core/System/Controls/Select.nm +1 -1
  26. package/core/System/Controls/Separator.nm +1 -1
  27. package/core/System/Controls/Sheet.nm +1 -1
  28. package/core/System/Controls/Slider.nm +1 -1
  29. package/core/System/Controls/Spinner.nm +2 -2
  30. package/core/System/Controls/SplitView.nm +1 -1
  31. package/core/System/Controls/Stepper.nm +1 -1
  32. package/core/System/Controls/Switch.nm +1 -1
  33. package/core/System/Controls/Table.nm +1 -1
  34. package/core/System/Controls/Tabs.nm +1 -1
  35. package/core/System/Controls/Text.nm +1 -1
  36. package/core/System/Controls/TextBox.nm +2 -2
  37. package/core/System/Controls/TextView.nm +1 -1
  38. package/core/System/Controls/Toggle.nm +2 -2
  39. package/core/System/Controls/Toolbar.nm +1 -1
  40. package/core/System/Controls/Window.nm +1 -2
  41. package/core/System/LinkedList.nm +8 -1
  42. package/core/System/List.nm +68 -12
  43. package/core/System/Map.nm +174 -18
  44. package/core/System/Mutex.nm +4 -4
  45. package/core/System/Random.nm +3 -4
  46. package/core/System/Set.nm +94 -20
  47. package/core/System/StableList.nm +371 -0
  48. package/core/System/Stream/Directory.nm +2 -2
  49. package/core/System/Stream/File.nm +2 -2
  50. package/core/System/Stream/Http.nm +2 -2
  51. package/core/System/String.nm +10 -5
  52. package/core/System/StringBuilder.nm +213 -2
  53. package/core/System/Text/CharIndex.nm +9 -9
  54. package/core/System/Text/Json.nm +60 -46
  55. package/core/System/Text/JsonTree.nm +120 -92
  56. package/core/System/Text/Regex.nm +23 -18
  57. package/core/System/Text/Utf8.nm +5 -5
  58. package/core/System/float.nm +36 -22
  59. package/core/System/int.nm +80 -28
  60. package/core/System/int16.nm +11 -0
  61. package/core/System/int32.nm +11 -0
  62. package/core/System/int64.nm +11 -0
  63. package/core/System/int8.nm +11 -0
  64. package/core/System/uint.nm +11 -0
  65. package/core/System/uint16.nm +11 -0
  66. package/core/System/uint32.nm +11 -0
  67. package/core/System/uint64.nm +11 -0
  68. package/core/System/uint8.nm +11 -0
  69. package/core/docs/System/StringBuilder.md +6 -0
  70. package/core/docs/System/Text/JsonTree.md +3 -2
  71. package/dist/index.mjs +36575 -30747
  72. package/package.json +1 -1
  73. package/src/bench_loop.nm +9 -9
@@ -1,156 +1,184 @@
1
- // JsonTree: a flat pool of JSON tree nodes backed by a Buffer<JsonNode> slab.
2
- // Each node is a value struct stored at index N in the buffer; grow_T/store_T/
3
- // load_T handle the slab arithmetic. The caller creates a JsonTree once and
4
- // calls reset() per document to reuse the slab without reallocation.
1
+ // JsonTree: a flat pool of JSON tree nodes backed by a StableList<JsonNode>.
2
+ // Each node is a value struct appended to the list; node indices are list
3
+ // indices, and list slots never move. The caller creates a JsonTree once and
4
+ // calls reset() per document. The pool owns its node texts end to end —
5
+ // set_text/take_text's interior field write frees the displaced text and the
6
+ // list's own destroy reclaims every node — so there is no manual text
7
+ // lifecycle. (The pre-StableList version kept a raw Buffer slab with a
8
+ // hand-encoded element layout and a strdup/free dance; see the stable grow-only
9
+ // arrays".)
5
10
  //
6
- // set_text is the one exception to the load/store pattern: it calls strdup
7
- // directly into the slab via raw code, because routing the strdup'd pointer
8
- // through an Nomen local would subject it to auto-free at scope exit, leaving
9
- // the slab's text pointer dangling.
11
+ // Accessors read and write through the slot alias (`ref JsonNode n =
12
+ // self.nodes.at(idx)`), the checked interior-mutation form: the alias binds
13
+ // the slot itself, so field writes land in the list and reads copy nothing.
10
14
 
11
15
  // A node in a parsed JSON tree, stored by value in the pool.
12
16
  // kind: 0=null, 1=true, 2=number, 3=string, 4=array, 5=object, 6=false
13
17
  // child/next/val are node indices, -1 = none.
18
+ //
19
+ // `text` is nullable and seeds as `null` (the in-band zero pair): the auto
20
+ // constructor stores no pointer for it, so a fresh node costs NO allocation
21
+ // — every parsed node used to pay a strdup-of-"" seed plus the free when
22
+ // take_text displaced it (or at destroy for kinds that never carry text).
23
+ // set_text/take_text install the owned value; until then the field holds
24
+ // null and every read (get_text/get_text_view) answers the empty string.
14
25
  mv struct JsonNode {
15
26
  var kind = 0
16
- var text = ""
27
+ var string? text = null
17
28
  var int child = -1
18
29
  var int next = -1
19
30
  var int val = -1
20
31
  }
21
32
 
22
33
  /**
23
- * A flat, buffer-backed pool of JSON tree nodes — the parsed representation `Json.parse` produces
34
+ * A flat, pool-backed set of JSON tree nodes — the parsed representation `Json.parse` produces
24
35
  **/
25
36
  pub mv struct JsonTree {
26
- var nodes = Buffer<JsonNode>()
27
- var count = 0
37
+ var nodes = StableList<JsonNode>()
28
38
 
39
+ // Append a fresh node and return its index.
29
40
  func alloc_node = (ref self, out int) {
30
- var int idx = self.count
31
- self.nodes.grow(idx + 1)
32
- self.count = idx + 1
33
- var default = JsonNode()
34
- self.nodes.store(idx, default)
35
- // store_T deep-copies the mv `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. The slab
40
- // slot layout: 56-byte JsonNode elements — 7 words — with the text
41
- // pair at words 2 and 3 of each element.
42
- unsafe {
43
- var ptr uint64 slots = self.nodes.data as ptr uint64
44
- var int t = idx * 7 + 2
45
- var uint64 old = slots[t]
46
- if old != 0 {
47
- free(old)
48
- }
49
- slots[t] = 0
50
- slots[t + 1] = 0
51
- }
52
- return idx
53
- }
54
-
55
- // Free a node's strdup'd text pointer (set_text writes a heap pointer via
56
- // raw strdup; this mirrors it). idx is x1, following set_text's layout.
57
- func free_text = (ref self, int idx) {
58
- unsafe {
59
- var ptr uint64 slots = self.nodes.data as ptr uint64
60
- var int t = idx * 7 + 2
61
- var uint64 p = slots[t]
62
- if p != 0 {
63
- free(p)
64
- slots[t] = 0
65
- slots[t + 1] = 0
66
- }
67
- }
41
+ self.nodes.push(JsonNode())
42
+ return self.nodes.length - 1
68
43
  }
69
44
 
70
- // Free every strdup'd text pointer before reusing the slab, otherwise
71
- // re-parsing a document leaks the previous document's string/number texts.
45
+ // Drop the current document: the fresh StableList displaces the old one,
46
+ // whose destroy frees every node (texts included) and its slabs. The
47
+ // next parse starts from a clean pool.
72
48
  func reset = (ref self) {
73
- var i = 0
74
- while i < self.count; i += 1 {
75
- self.free_text(i)
76
- }
77
- self.count = 0
49
+ self.nodes = StableList<JsonNode>()
78
50
  }
79
51
 
80
- // Runs when the tree instance is destroyed (before its `nodes` buffer field
81
- // is freed): release the current document's strdup'd node texts, which live
82
- // raw in the slab and are otherwise invisible to the auto-destroy pass.
83
- func #destroy = () {
84
- var i = 0
85
- while i < self.count; i += 1 {
86
- self.free_text(i)
52
+ // Install node `idx`'s text, TAKING ownership of the argument (the D3
53
+ // mv convention): `set_text(n, mv s)` transfers the caller's buffer
54
+ // into the pool with no copy anywhere — the field store moves the pair
55
+ // raw and the caller's variable is invalidated. (Pass `.copy()` for the
56
+ // keep-the-original spelling: `set_text(n, s.copy())`.) The pool owns
57
+ // its node texts end to end, so the transferred buffer is freed exactly
58
+ // once, by the list's destroy or the next text store. For strings
59
+ // produced by a StringBuilder, `take_text` installs the builder's
60
+ // buffer with no copy at all.
61
+ func set_text = (ref self, int idx, mv string text) {
62
+ if idx < 0 || idx >= self.nodes.length {
63
+ panic("index out of range")
87
64
  }
88
- self.count = 0
65
+ ref JsonNode n = self.nodes.at(idx)
66
+ n.text = mv text
89
67
  }
90
68
 
91
- // strdup directly into the slab — see file header for why this can't
92
- // go through load_T/store_T. (Under the fat-string ABI the C body is
93
- // emitted behind the _raw_ adapter, so `text` arrives as a thin char*;
94
- // aarch64 receives the pair x2 = ptr, x3 = len.)
95
- func set_text = (ref self, int idx, string text) {
96
- unsafe {
97
- var int len = text.length
98
- var uint64 buf = malloc(len + 1)
99
- memcpy(buf, (text as ptr char) as uint64, len)
100
- memset(buf + len, 0, 1)
101
- var ptr uint64 slots = self.nodes.data as ptr uint64
102
- var int t = idx * 7 + 2
103
- slots[t] = buf
104
- slots[t + 1] = len
69
+ // Install node `idx`'s text by TAKING the builder's buffer — the parse
70
+ // path's zero-copy store. `to_string` is a take (the builder hands its
71
+ // heap buffer over and resets), and a fresh-call RHS stores into the
72
+ // field directly, so per parsed string/key the only allocation in the
73
+ // whole node lifecycle is the builder's own buffer — the node's null
74
+ // seed costs nothing and there is no copy malloc, no seed free, no
75
+ // temp free. (A `mv string` parameter cannot do this: the string-arg
76
+ // ownership convention makes mv string arguments caller-owned
77
+ // borrows, forcing the callee-side copy this method exists to skip.)
78
+ func take_text = (ref self, int idx, ref StringBuilder sb) {
79
+ if idx < 0 || idx >= self.nodes.length {
80
+ panic("index out of range")
105
81
  }
82
+ ref JsonNode n = self.nodes.at(idx)
83
+ n.text = sb.to_string()
106
84
  }
107
85
 
108
86
  func set_kind = (ref self, int idx, int val) {
109
- var JsonNode n = self.nodes.load(idx)
87
+ if idx < 0 || idx >= self.nodes.length {
88
+ panic("index out of range")
89
+ }
90
+ ref JsonNode n = self.nodes.at(idx)
110
91
  n.kind = val
111
- self.nodes.store(idx, n)
112
92
  }
113
93
 
114
94
  func set_child = (ref self, int idx, int val) {
115
- var JsonNode n = self.nodes.load(idx)
95
+ if idx < 0 || idx >= self.nodes.length {
96
+ panic("index out of range")
97
+ }
98
+ ref JsonNode n = self.nodes.at(idx)
116
99
  n.child = val
117
- self.nodes.store(idx, n)
118
100
  }
119
101
 
120
102
  func set_next = (ref self, int idx, int val) {
121
- var JsonNode n = self.nodes.load(idx)
103
+ if idx < 0 || idx >= self.nodes.length {
104
+ panic("index out of range")
105
+ }
106
+ ref JsonNode n = self.nodes.at(idx)
122
107
  n.next = val
123
- self.nodes.store(idx, n)
124
108
  }
125
109
 
126
110
  func set_val = (ref self, int idx, int val) {
127
- var JsonNode n = self.nodes.load(idx)
111
+ if idx < 0 || idx >= self.nodes.length {
112
+ panic("index out of range")
113
+ }
114
+ ref JsonNode n = self.nodes.at(idx)
128
115
  n.val = val
129
- self.nodes.store(idx, n)
130
116
  }
131
117
 
132
118
  func get_kind = (self, int idx, out int) {
133
- var JsonNode n = self.nodes.load(idx)
119
+ if idx < 0 || idx >= self.nodes.length {
120
+ panic("index out of range")
121
+ }
122
+ ref JsonNode n = self.nodes.at(idx)
134
123
  return n.kind
135
124
  }
136
125
 
137
126
  func get_text = (self, int idx, out string) {
138
- var JsonNode n = self.nodes.load(idx)
127
+ if idx < 0 || idx >= self.nodes.length {
128
+ panic("index out of range")
129
+ }
130
+ ref JsonNode n = self.nodes.at(idx)
131
+ // The aarch64 `??` yields the left pair AS-IS (no dup), so the
132
+ // coalesce spelling would return a borrow of the node's text — the
133
+ // caller's free would take the pool's buffer. Spell the owned copy:
134
+ // a null text answers a fresh "" and a non-null one takes
135
+ // `.copy()` (the return contract — the caller owns the result —
136
+ // is the `.copy()` shape the method always had).
137
+ if n.text == null {
138
+ return ""
139
+ }
139
140
  return n.text.copy()
140
141
  }
141
142
 
143
+ // A non-mv (ptr, len) view over node `idx`'s text — the zero-copy read.
144
+ // The borrow roots at `self` and re-roots at the call-site receiver
145
+ // (the `slice` convention), so it is invalidated by `reset()`/re-parse
146
+ // like any pool borrow. Callers that immediately append, hash, or
147
+ // inspect the text take this instead of paying `get_text`'s per-read
148
+ // copy allocation. A node that never had text installed views the
149
+ // empty string (a rodata-rooted empty borrow).
150
+ func get_text_view = (self, int idx, out view string) {
151
+ if idx < 0 || idx >= self.nodes.length {
152
+ panic("index out of range")
153
+ }
154
+ ref JsonNode n = self.nodes.at(idx)
155
+ if n.text == null {
156
+ return "".slice(0, 0)
157
+ }
158
+ return n.text.slice(0, n.text.length)
159
+ }
160
+
142
161
  func get_child = (self, int idx, out int) {
143
- var JsonNode n = self.nodes.load(idx)
162
+ if idx < 0 || idx >= self.nodes.length {
163
+ panic("index out of range")
164
+ }
165
+ ref JsonNode n = self.nodes.at(idx)
144
166
  return n.child
145
167
  }
146
168
 
147
169
  func get_next = (self, int idx, out int) {
148
- var JsonNode n = self.nodes.load(idx)
170
+ if idx < 0 || idx >= self.nodes.length {
171
+ panic("index out of range")
172
+ }
173
+ ref JsonNode n = self.nodes.at(idx)
149
174
  return n.next
150
175
  }
151
176
 
152
177
  func get_val = (self, int idx, out int) {
153
- var JsonNode n = self.nodes.load(idx)
178
+ if idx < 0 || idx >= self.nodes.length {
179
+ panic("index out of range")
180
+ }
181
+ ref JsonNode n = self.nodes.at(idx)
154
182
  return n.val
155
183
  }
156
184
  }
@@ -33,10 +33,9 @@ pub struct Regex {
33
33
  }
34
34
 
35
35
  // Return the first substring of input matching pattern, or "" if none.
36
- // `mv out string`: the built match (or the copied empty literal) is
37
- // handed to the caller as an owned value.
36
+ // `mv out string`: the matched span (or the empty literal) is handed to
37
+ // the caller as an owned value.
38
38
  pub func match = (string pattern, string input, mv out string) {
39
- var match_result = ""
40
39
  var int pat_len = pattern.length
41
40
  var int len = input.length
42
41
  var string charset = first_byte_set(pattern, pat_len)
@@ -52,14 +51,14 @@ pub struct Regex {
52
51
  var caps = List<int>()
53
52
  var int end = match_here(pattern, 0, pat_len, input, i, len, ref caps)
54
53
  if end >= 0 {
55
- var int j = i
56
- while j < end; j += 1 {
57
- match_result = match_result + "\{input.at(j)}"
58
- }
59
- break
54
+ // One owned substring copy. The old accumulator
55
+ // (`match_result = match_result + "\{input.at(j)}"` per
56
+ // matched byte) paid an interpolation malloc PLUS a
57
+ // whole-accumulator re-copy per byte — O(n²) in the span.
58
+ return input.substring(i, end)
60
59
  }
61
60
  }
62
- return match_result
61
+ return ""
63
62
  }
64
63
 
65
64
  // Try to match pattern anywhere in input. On a match, append the whole
@@ -173,25 +172,27 @@ pub struct Regex {
173
172
  }
174
173
 
175
174
  // Replace all non-overlapping occurrences of pattern in input with replacement.
175
+ // Literal bytes between matches are copied in bulk runs (one
176
+ // `append_string_view` per run) — the old loop appended per byte.
176
177
  pub func replace_all = (
177
178
  string pattern,
178
179
  string input,
179
180
  string replacement,
180
181
  out string
181
- ) { var sb = StringBuilder()
182
+ ) {
183
+ var sb = StringBuilder()
182
184
  var int pat_len = pattern.length
183
185
  var int len = input.length
184
186
  var string charset = first_byte_set(pattern, pat_len)
185
187
  var int clen = charset.length
186
188
  var pos = 0
189
+ var run_start = 0
187
190
  while pos < len {
188
191
  if clen > 0 {
189
- var int next = find_next_byte(input, pos, len, charset)
190
- // Copy the bytes we're skipping over.
191
- while pos < next {
192
- sb.append_char(input.at(pos))
193
- pos += 1
194
- }
192
+ // Prefilter: jump to the next candidate byte. The skipped
193
+ // run stays pending — it is copied in bulk at the next
194
+ // match, or by the tail copy below.
195
+ pos = find_next_byte(input, pos, len, charset)
195
196
  if pos >= len {
196
197
  break
197
198
  }
@@ -199,18 +200,22 @@ pub struct Regex {
199
200
  var caps = List<int>()
200
201
  var int end = match_here(pattern, 0, pat_len, input, pos, len, ref caps)
201
202
  if end >= 0 {
203
+ sb.append_string_view(input.slice(run_start, pos))
202
204
  sb.append_string(replacement)
203
205
  if end == pos {
204
- sb.append_char(input.at(pos))
206
+ // Empty match: the byte at pos is not consumed — it
207
+ // starts the next literal run.
208
+ run_start = pos
205
209
  pos += 1
206
210
  } else {
207
211
  pos = end
212
+ run_start = pos
208
213
  }
209
214
  } else {
210
- sb.append_char(input.at(pos))
211
215
  pos += 1
212
216
  }
213
217
  }
218
+ sb.append_string_view(input.slice(run_start, len))
214
219
  return sb.to_string()
215
220
  }
216
221
 
@@ -126,11 +126,11 @@ pub struct Utf8 {
126
126
  sb.append_char(((0x80 | (code & 0x3F)) as char))
127
127
  }
128
128
  } else {
129
- sb.append_char(((0xF0 | (code >> 18)) as char))
130
- sb.append_char(((0x80 | ((code >> 12) & 0x3F)) as char))
131
- sb.append_char(((0x80 | ((code >> 6) & 0x3F)) as char))
132
- sb.append_char(((0x80 | (code & 0x3F)) as char))
133
- }
129
+ sb.append_char(((0xF0 | (code >> 18)) as char))
130
+ sb.append_char(((0x80 | ((code >> 12) & 0x3F)) as char))
131
+ sb.append_char(((0x80 | ((code >> 6) & 0x3F)) as char))
132
+ sb.append_char(((0x80 | (code & 0x3F)) as char))
133
+ }
134
134
  return sb.to_string()
135
135
  }
136
136
 
@@ -5,37 +5,51 @@ pub struct float: Stringable {
5
5
  pub func to_string = (self, mv out string) {
6
6
  ```
7
7
  #arch: c
8
- int length = snprintf(NULL, 0, "%f", self);
9
- char* str = malloc(length + 1);
10
- snprintf(str, length + 1, "%f", self);
11
- // Fat-string ABI: snprintf already counted the bytes.
8
+ // Single snprintf into a stack scratch (360 bytes covers "%f" of the
9
+ // largest double: 309 integer digits + '.' + 6 decimals + sign), then
10
+ // one malloc + memcpy — no sizing pass, no second format.
11
+ char tmp[360];
12
+ int length = snprintf(tmp, sizeof(tmp), "%f", self);
13
+ char* str = (char*)malloc((size_t)length + 1);
14
+ memcpy(str, tmp, (size_t)length);
15
+ str[length] = 0;
12
16
  return (nomen_string){ str, length };
13
17
  ```
14
18
  ```
15
19
  #arch: aarch64
16
- sub sp, sp, #128
17
- str x0, [sp, #0]
18
- str x1, [sp, #8]
19
- str x2, [sp, #16]
20
- str x3, [sp, #24]
21
- str d0, [sp, #64]
22
- mov x0, xzr
23
- mov x1, xzr
20
+ // x19 = self (the double). Render into a 360-byte scratch at
21
+ // [sp+16]; [sp+0] is snprintf's variadic stack slot (Darwin passes
22
+ // variadic args on the stack), reused after the call for the
23
+ // length; [sp+8] holds buf.
24
+ sub sp, sp, #400
25
+ add x0, sp, #16
26
+ mov x1, #360
24
27
  adr x2, .Lfmt_float_f
25
28
  fmov d0, x19
29
+ str d0, [sp, #0] // variadic double slot
26
30
  bl _snprintf
31
+ str x0, [sp, #0] // length
27
32
  add x0, x0, #1
28
- str x0, [sp, #64]
29
33
  bl _malloc
30
- str x0, [sp, #72]
31
- ldr x0, [sp, #72]
32
- ldr x1, [sp, #64]
33
- adr x2, .Lfmt_float_f
34
- fmov d0, x19
35
- bl _snprintf
36
- mov x1, x0
37
- ldr x0, [sp, #72]
38
- add sp, sp, #128
34
+ str x0, [sp, #8] // buf
35
+ ldr x1, [sp, #0]
36
+ add x2, sp, #16 // src
37
+ mov x3, x1
38
+ .Lft_cl:
39
+ cbz x3, .Lft_done
40
+ ldrb w4, [x2]
41
+ strb w4, [x0]
42
+ add x2, x2, #1
43
+ add x0, x0, #1
44
+ sub x3, x3, #1
45
+ b .Lft_cl
46
+ .Lft_done:
47
+ ldr x0, [sp, #8]
48
+ add x2, x0, x1
49
+ strb wzr, [x2] // buf[length] = 0
50
+ ldr x0, [sp, #8]
51
+ ldr x1, [sp, #0]
52
+ add sp, sp, #400
39
53
  b .Lend_float_to_string
40
54
  .Lfmt_float_f: .asciz "%f"
41
55
  .p2align 2
@@ -2,6 +2,17 @@
2
2
  * A signed integer — the default integer type
3
3
  **/
4
4
  pub struct int: Stringable, Hashable, Equatable {
5
+
6
+ // The divisor contract (SPEC, Division and Modulo by Zero): scalar
7
+ // arithmetic lowers natively, so these declarations are the contract
8
+ // signatures the checker enforces at every `/` and `%` site.
9
+ func #op_div = (self, int divisor: divisor != 0, out int) {
10
+ return self / divisor
11
+ }
12
+
13
+ func #op_mod = (self, int divisor: divisor != 0, out int) {
14
+ return self % divisor
15
+ }
5
16
  pub func hash = (self, out uint) {
6
17
  return self as uint
7
18
  }
@@ -9,39 +20,80 @@ pub struct int: Stringable, Hashable, Equatable {
9
20
  pub func to_string = (self, mv out string) {
10
21
  ```
11
22
  #arch: c
12
- int length = snprintf(NULL, 0, "%ld", self);
13
- char* str = malloc(length + 1);
14
- snprintf(str, length + 1, "%ld", self);
15
- // Fat-string ABI: snprintf already counted the bytes.
16
- return (nomen_string){ str, length };
23
+ // Single pass: render the digits into a stack scratch (24 bytes
24
+ // covers INT64_MIN's sign + 19 digits + slack), then one malloc and
25
+ // one memcpy. The old body paid two snprintf passes over the value.
26
+ long v = self;
27
+ char tmp[24];
28
+ int pos = 24;
29
+ // Magnitude via the two's-complement negate (correct for INT64_MIN,
30
+ // whose positive counterpart is not representable).
31
+ unsigned long long u = v < 0 ? (unsigned long long)(-(v + 1)) + 1ULL : (unsigned long long)v;
32
+ do {
33
+ tmp[--pos] = (char)('0' + (int)(u % 10));
34
+ u /= 10;
35
+ } while (u != 0);
36
+ if (v < 0) tmp[--pos] = '-';
37
+ long len = 24 - pos;
38
+ char* str = (char*)malloc((size_t)len + 1);
39
+ memcpy(str, tmp + pos, (size_t)len);
40
+ str[len] = 0;
41
+ return (nomen_string){ str, len };
17
42
  ```
18
43
  ```
19
44
  #arch: aarch64
20
- sub sp, sp, #128
21
- str x0, [sp, #0]
22
- str x1, [sp, #8]
23
- str x2, [sp, #16]
24
- str x3, [sp, #24]
25
- mov x0, xzr
26
- mov x1, xzr
27
- adr x2, .Lfmt_int_ld
28
- mov x3, x19
29
- bl _snprintf
30
- add x0, x0, #1
31
- str x0, [sp, #64]
45
+ // x19 = self (the value). Render digits into a 24-byte scratch, then
46
+ // malloc(len + 1) + copy. [sp]=len, [sp+8]=buf, [sp+16]=pos across
47
+ // the malloc call.
48
+ sub sp, sp, #48
49
+ mov x9, x19
50
+ mov x10, x9
51
+ cmp x9, #0
52
+ b.ge .Lit_pos
53
+ neg x10, x10 // magnitude (wraps correctly for INT64_MIN)
54
+ .Lit_pos:
55
+ add x11, sp, #48
56
+ mov x12, x11
57
+ .Lit_digit:
58
+ mov x13, #10
59
+ udiv x14, x10, x13
60
+ msub x15, x14, x13, x10
61
+ add x15, x15, #48
62
+ sub x12, x12, #1
63
+ strb w15, [x12]
64
+ mov x10, x14
65
+ cbnz x10, .Lit_digit
66
+ cmp x9, #0
67
+ b.ge .Lit_store
68
+ sub x12, x12, #1
69
+ mov w15, #45 // '-'
70
+ strb w15, [x12]
71
+ .Lit_store:
72
+ sub x1, x11, x12 // len
73
+ str x12, [sp, #16] // pos
74
+ str x1, [sp, #0] // len
75
+ add x0, x1, #1
32
76
  bl _malloc
33
- str x0, [sp, #72]
34
- ldr x0, [sp, #72]
35
- ldr x1, [sp, #64]
36
- adr x2, .Lfmt_int_ld
37
- mov x3, x19
38
- bl _snprintf
39
- mov x1, x0
40
- ldr x0, [sp, #72]
41
- add sp, sp, #128
77
+ str x0, [sp, #8] // buf
78
+ ldr x1, [sp, #0]
79
+ ldr x12, [sp, #16]
80
+ mov x3, x1
81
+ .Lit_cl:
82
+ cbz x3, .Lit_done
83
+ ldrb w4, [x12]
84
+ strb w4, [x0]
85
+ add x12, x12, #1
86
+ add x0, x0, #1
87
+ sub x3, x3, #1
88
+ b .Lit_cl
89
+ .Lit_done:
90
+ ldr x0, [sp, #8]
91
+ add x2, x0, x1
92
+ strb wzr, [x2] // buf[len] = 0
93
+ ldr x0, [sp, #8]
94
+ ldr x1, [sp, #0]
95
+ add sp, sp, #48
42
96
  b .Lend_int_to_string
43
- .Lfmt_int_ld: .asciz "%ld"
44
- .p2align 2
45
97
  .Lend_int_to_string:
46
98
  ```
47
99
  }
@@ -2,6 +2,17 @@
2
2
  * A 16-bit signed integer
3
3
  **/
4
4
  pub struct int16: Stringable, Hashable, Equatable {
5
+
6
+ // The divisor contract (SPEC, Division and Modulo by Zero): scalar
7
+ // arithmetic lowers natively, so these declarations are the contract
8
+ // signatures the checker enforces at every `/` and `%` site.
9
+ func #op_div = (self, int16 divisor: divisor != 0, out int16) {
10
+ return self / divisor
11
+ }
12
+
13
+ func #op_mod = (self, int16 divisor: divisor != 0, out int16) {
14
+ return self % divisor
15
+ }
5
16
  pub func hash = (self, out uint) {
6
17
  return self as uint
7
18
  }
@@ -2,6 +2,17 @@
2
2
  * A 32-bit signed integer
3
3
  **/
4
4
  pub struct int32: Stringable, Hashable, Equatable {
5
+
6
+ // The divisor contract (SPEC, Division and Modulo by Zero): scalar
7
+ // arithmetic lowers natively, so these declarations are the contract
8
+ // signatures the checker enforces at every `/` and `%` site.
9
+ func #op_div = (self, int32 divisor: divisor != 0, out int32) {
10
+ return self / divisor
11
+ }
12
+
13
+ func #op_mod = (self, int32 divisor: divisor != 0, out int32) {
14
+ return self % divisor
15
+ }
5
16
  pub func hash = (self, out uint) {
6
17
  return self as uint
7
18
  }
@@ -2,6 +2,17 @@
2
2
  * A 64-bit signed integer
3
3
  **/
4
4
  pub struct int64: Stringable, Hashable, Equatable {
5
+
6
+ // The divisor contract (SPEC, Division and Modulo by Zero): scalar
7
+ // arithmetic lowers natively, so these declarations are the contract
8
+ // signatures the checker enforces at every `/` and `%` site.
9
+ func #op_div = (self, int64 divisor: divisor != 0, out int64) {
10
+ return self / divisor
11
+ }
12
+
13
+ func #op_mod = (self, int64 divisor: divisor != 0, out int64) {
14
+ return self % divisor
15
+ }
5
16
  pub func hash = (self, out uint) {
6
17
  return self as uint
7
18
  }