mt-lang 0.2.0 → 0.2.1

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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/docs/index.html +4 -3
  3. data/lib/milk_tea/base.rb +1 -1
  4. data/std/asset_pack.mt +267 -0
  5. data/std/async/libuv_runtime.mt +539 -0
  6. data/std/async/mailbox.mt +191 -0
  7. data/std/async/runtime.mt +87 -0
  8. data/std/async.mt +87 -0
  9. data/std/base64.mt +185 -0
  10. data/std/behavior_tree.mt +396 -0
  11. data/std/binary.mt +314 -0
  12. data/std/binary_heap.mt +157 -0
  13. data/std/bitset.mt +239 -0
  14. data/std/box2d.mt +535 -0
  15. data/std/bytes.mt +46 -0
  16. data/std/c/box2d.mt +1254 -0
  17. data/std/c/cgltf.mt +647 -0
  18. data/std/c/cjson.mt +106 -0
  19. data/std/c/crypto.mt +11 -0
  20. data/std/c/crypto_support.h +51 -0
  21. data/std/c/ctype.mt +19 -0
  22. data/std/c/ctype_bindgen.h +29 -0
  23. data/std/c/curl.mt +1223 -0
  24. data/std/c/enet.mt +473 -0
  25. data/std/c/errno.mt +41 -0
  26. data/std/c/errno_bindgen.h +53 -0
  27. data/std/c/flecs.mt +2945 -0
  28. data/std/c/fs.linux.mt +43 -0
  29. data/std/c/fs.windows.mt +43 -0
  30. data/std/c/fs_support.h +954 -0
  31. data/std/c/gl.mt +2065 -0
  32. data/std/c/gl_registry_helpers.h +10748 -0
  33. data/std/c/glfw.mt +499 -0
  34. data/std/c/libc.mt +167 -0
  35. data/std/c/libuv.mt +1491 -0
  36. data/std/c/math.mt +22 -0
  37. data/std/c/math_bindgen.h +31 -0
  38. data/std/c/miniaudio.mt +3420 -0
  39. data/std/c/pcre2.mt +735 -0
  40. data/std/c/process.mt +58 -0
  41. data/std/c/process_support.h +1147 -0
  42. data/std/c/raygui.mt +1602 -0
  43. data/std/c/raylib.mt +1245 -0
  44. data/std/c/raymath.mt +163 -0
  45. data/std/c/rlgl.mt +434 -0
  46. data/std/c/rpng.mt +60 -0
  47. data/std/c/rres.mt +187 -0
  48. data/std/c/sdl3.mt +4132 -0
  49. data/std/c/sqlite3.mt +951 -0
  50. data/std/c/stb_image.mt +52 -0
  51. data/std/c/stb_image_resize2.mt +128 -0
  52. data/std/c/stb_image_write.mt +19 -0
  53. data/std/c/stb_rect_pack.mt +38 -0
  54. data/std/c/stb_truetype.mt +175 -0
  55. data/std/c/stb_vorbis.mt +31 -0
  56. data/std/c/stdio.mt +90 -0
  57. data/std/c/steamworks.h +5769 -0
  58. data/std/c/steamworks.mt +4771 -0
  59. data/std/c/string.mt +16 -0
  60. data/std/c/string_bindgen.h +36 -0
  61. data/std/c/sync.mt +33 -0
  62. data/std/c/sync_support.h +219 -0
  63. data/std/c/terminal.mt +24 -0
  64. data/std/c/terminal_support.h +408 -0
  65. data/std/c/time.mt +26 -0
  66. data/std/c/tls.mt +24 -0
  67. data/std/c/tls_support.h +626 -0
  68. data/std/c/tracy.mt +53 -0
  69. data/std/c/zlib.mt +20 -0
  70. data/std/c/zlib_support.h +239 -0
  71. data/std/c/zstd.mt +231 -0
  72. data/std/cell.mt +60 -0
  73. data/std/cgltf.mt +115 -0
  74. data/std/cjson.mt +90 -0
  75. data/std/cli.mt +768 -0
  76. data/std/cookie.mt +198 -0
  77. data/std/counter.mt +160 -0
  78. data/std/crypto.mt +74 -0
  79. data/std/cstring.mt +13 -0
  80. data/std/ctype.mt +52 -0
  81. data/std/curl/runtime.mt +261 -0
  82. data/std/curl.mt +38 -0
  83. data/std/deque.mt +348 -0
  84. data/std/encoding.mt +93 -0
  85. data/std/enet.mt +133 -0
  86. data/std/env.mt +41 -0
  87. data/std/errno.mt +47 -0
  88. data/std/flecs.mt +978 -0
  89. data/std/fmt.mt +271 -0
  90. data/std/fs.linux.mt +696 -0
  91. data/std/fs.windows.mt +711 -0
  92. data/std/fsm.mt +267 -0
  93. data/std/gl.mt +2062 -0
  94. data/std/glfw.mt +471 -0
  95. data/std/goap.mt +342 -0
  96. data/std/graph.mt +521 -0
  97. data/std/gzip.mt +77 -0
  98. data/std/hash.mt +429 -0
  99. data/std/http/server.mt +810 -0
  100. data/std/http.mt +1227 -0
  101. data/std/intern.mt +53 -0
  102. data/std/jobs.mt +288 -0
  103. data/std/json.mt +627 -0
  104. data/std/libc.mt +34 -0
  105. data/std/libuv.mt +554 -0
  106. data/std/linear_algebra.mt +243 -0
  107. data/std/linked_map.mt +372 -0
  108. data/std/linked_map_view.mt +49 -0
  109. data/std/linked_set.mt +117 -0
  110. data/std/log.mt +69 -0
  111. data/std/map.mt +418 -0
  112. data/std/math.mt +24 -0
  113. data/std/mem/arena.mt +137 -0
  114. data/std/mem/endian.mt +51 -0
  115. data/std/mem/heap.mt +298 -0
  116. data/std/mem/pool.mt +164 -0
  117. data/std/mem/stack.mt +47 -0
  118. data/std/mem/tracking.mt +119 -0
  119. data/std/miniaudio.mt +1299 -0
  120. data/std/multiset.mt +191 -0
  121. data/std/net/channel.mt +727 -0
  122. data/std/net/clock.mt +237 -0
  123. data/std/net/discovery.mt +277 -0
  124. data/std/net/lobby.mt +813 -0
  125. data/std/net/manager.mt +539 -0
  126. data/std/net/mux.mt +839 -0
  127. data/std/net/nat.mt +122 -0
  128. data/std/net/packet.mt +219 -0
  129. data/std/net/punch.mt +162 -0
  130. data/std/net/rpc.mt +119 -0
  131. data/std/net/session.mt +1157 -0
  132. data/std/net/stun.mt +252 -0
  133. data/std/net/sync.mt +183 -0
  134. data/std/net/turn.mt +459 -0
  135. data/std/net.mt +2944 -0
  136. data/std/oauth2.mt +408 -0
  137. data/std/option.mt +51 -0
  138. data/std/ordered_map.mt +497 -0
  139. data/std/ordered_set.mt +348 -0
  140. data/std/path.mt +368 -0
  141. data/std/pcre2/runtime.mt +35 -0
  142. data/std/pcre2.mt +78 -0
  143. data/std/priority_queue.mt +55 -0
  144. data/std/process.mt +837 -0
  145. data/std/queue.mt +55 -0
  146. data/std/random.mt +146 -0
  147. data/std/raygui.mt +99 -0
  148. data/std/raylib/debug_console.mt +71 -0
  149. data/std/raylib/easing.mt +388 -0
  150. data/std/raylib/packed_assets.mt +255 -0
  151. data/std/raylib/runtime.mt +30 -0
  152. data/std/raylib/tracy_gpu.mt +37 -0
  153. data/std/raylib.mt +1510 -0
  154. data/std/raymath.mt +153 -0
  155. data/std/result.mt +89 -0
  156. data/std/rlgl.mt +268 -0
  157. data/std/rpng.mt +46 -0
  158. data/std/rres.mt +36 -0
  159. data/std/sdl3/runtime.mt +54 -0
  160. data/std/sdl3.mt +1731 -0
  161. data/std/serialize.mt +68 -0
  162. data/std/set.mt +124 -0
  163. data/std/spatial.mt +176 -0
  164. data/std/sqlite3.mt +151 -0
  165. data/std/stack.mt +55 -0
  166. data/std/stb_image.mt +47 -0
  167. data/std/stb_image_resize2.mt +41 -0
  168. data/std/stb_image_write.mt +17 -0
  169. data/std/stb_rect_pack.mt +15 -0
  170. data/std/stb_truetype.mt +77 -0
  171. data/std/stb_vorbis.mt +16 -0
  172. data/std/stdio.mt +88 -0
  173. data/std/steamworks.mt +1542 -0
  174. data/std/str.mt +293 -0
  175. data/std/string.mt +234 -0
  176. data/std/sync.mt +194 -0
  177. data/std/tar.mt +704 -0
  178. data/std/terminal.mt +1002 -0
  179. data/std/testing.mt +266 -0
  180. data/std/thread.mt +120 -0
  181. data/std/time.mt +105 -0
  182. data/std/tls.mt +616 -0
  183. data/std/toml.mt +1310 -0
  184. data/std/tracy.mt +42 -0
  185. data/std/uri.mt +118 -0
  186. data/std/url.mt +372 -0
  187. data/std/vec.mt +433 -0
  188. data/std/zstd.mt +94 -0
  189. metadata +187 -2
@@ -0,0 +1,810 @@
1
+ import std.bytes as bytes
2
+ import std.fmt as fmt
3
+ import std.fs as fs
4
+ import std.net as net
5
+ import std.path as path_ops
6
+ import std.stdio as stdio
7
+ import std.str as text
8
+ import std.string as string
9
+ import std.url as url
10
+ import std.vec as vec
11
+
12
+ public struct Request:
13
+ method: string.String
14
+ path: string.String
15
+ query: str
16
+ headers: vec.Vec[HttpHeader]
17
+ body: bytes.Bytes
18
+ raw_url: string.String
19
+
20
+ public struct HttpHeader:
21
+ name: string.String
22
+ value: string.String
23
+
24
+ public struct Response:
25
+ status_code: int
26
+ reason: string.String
27
+ headers: vec.Vec[HttpHeader]
28
+ body: bytes.Bytes
29
+
30
+
31
+ public function request_method(request: Request) -> str:
32
+ return request.method.as_str()
33
+
34
+
35
+ public function request_path(request: Request) -> str:
36
+ return request.path.as_str()
37
+
38
+
39
+ public function request_query(request: Request) -> str:
40
+ return request.query
41
+
42
+
43
+ public function request_query_param(request: Request, key: str) -> Option[str]:
44
+ if request.query.len == 0:
45
+ return Option[str].none
46
+
47
+ let query_str = request.query
48
+ var start: ptr_uint = 0
49
+ while start < query_str.len:
50
+ let amp = find_byte_from(query_str, 38, start)
51
+ var end = query_str.len
52
+ if amp < query_str.len:
53
+ end = amp
54
+
55
+ let pair = query_str.slice(start, end - start)
56
+
57
+ let eq = pair.find_byte(61)
58
+ match eq:
59
+ Option.none:
60
+ if pair.equal(key):
61
+ return Option[str].some(value = "")
62
+ Option.some as e:
63
+ let param_key = pair.slice(0, e.value)
64
+ if param_key.equal(key):
65
+ let param_value = pair.slice(e.value + 1, pair.len - e.value - 1)
66
+
67
+ let decoded = url.percent_decode(param_value) else:
68
+ return Option[str].some(value = "")
69
+
70
+ return Option[str].some(value = decoded.as_str())
71
+
72
+ if amp >= query_str.len:
73
+ start = query_str.len
74
+ else:
75
+ start = amp + 1
76
+
77
+ return Option[str].none
78
+
79
+
80
+ public function request_header(request: Request, name: str) -> Option[str]:
81
+ var index: ptr_uint = 0
82
+ while index < request.headers.len():
83
+ let header = request.headers.get(index) else:
84
+ fatal(c"http.server.request_header missing entry")
85
+
86
+ unsafe:
87
+ let h = read(header)
88
+ if ascii_case_equal(h.name.as_str(), name):
89
+ return Option[str].some(value = h.value.as_str())
90
+
91
+ index += 1
92
+
93
+ return Option[str].none
94
+
95
+
96
+ public function request_body_as_str(request: Request) -> Option[str]:
97
+ return request.body.as_str()
98
+
99
+
100
+ public function create_response() -> Response:
101
+ return Response(
102
+ status_code = 200,
103
+ reason = string.String.from_str("OK"),
104
+ headers = vec.Vec[HttpHeader].create(),
105
+ body = bytes.Bytes.empty()
106
+ )
107
+
108
+
109
+ public function response_set_status(response: ref[Response], code: int, reason: str) -> void:
110
+ response.status_code = code
111
+ response.reason.assign(reason)
112
+
113
+
114
+ public function response_set_body(response: ref[Response], content: str) -> void:
115
+ let body_data = bytes.Bytes.copy(text.as_byte_span(content))
116
+ response.body.release()
117
+ response.body = body_data
118
+
119
+
120
+ public function response_set_body_json(response: ref[Response], json_text: str) -> void:
121
+ response_set_body(response, json_text)
122
+ response_set_header(response, "Content-Type", "application/json")
123
+
124
+
125
+ public function response_set_header(response: ref[Response], name: str, value: str) -> void:
126
+ var index: ptr_uint = 0
127
+ while index < response.headers.len():
128
+ let header = response.headers.get(index) else:
129
+ break
130
+
131
+ unsafe:
132
+ if ascii_case_equal(read(header).name.as_str(), name):
133
+ var existing = read(header).value
134
+ existing.release()
135
+ read(header).value = string.String.from_str(value)
136
+ return
137
+
138
+ index += 1
139
+
140
+ response.headers.push(HttpHeader(
141
+ name = string.String.from_str(name),
142
+ value = string.String.from_str(value)
143
+ ))
144
+
145
+
146
+ public function parse_request(raw_data: span[ubyte]) -> Result[Request, string.String]:
147
+ let _ = text.utf8_byte_span_as_str(raw_data) else:
148
+ return Result[Request, string.String].failure(error = string.String.from_str("request is not valid UTF-8"))
149
+
150
+ let end_pos = find_header_terminator(raw_data) else:
151
+ return Result[Request, string.String].failure(error = string.String.from_str("request headers incomplete"))
152
+ let inner = text.utf8_byte_span_as_str(span[ubyte](data = raw_data.data, len = end_pos)) else:
153
+ return Result[Request, string.String].failure(
154
+ error = string.String.from_str("request headers not valid UTF-8")
155
+ )
156
+ let first_line_end = find_byte(inner, 10)
157
+ var request_line = inner.slice(0, first_line_end)
158
+ if request_line.ends_with("\r"):
159
+ request_line = inner.slice(0, request_line.len - 1)
160
+
161
+ let method_end = find_byte(request_line, 32)
162
+ if method_end >= request_line.len:
163
+ return Result[Request, string.String].failure(error = string.String.from_str("malformed request line"))
164
+
165
+ let method = request_line.slice(0, method_end)
166
+
167
+ var path_start = method_end + 1
168
+ while path_start < request_line.len and request_line.byte_at(path_start) == 32:
169
+ path_start += 1
170
+
171
+ let path_end = find_byte_from(request_line, 32, path_start)
172
+ let raw_url = request_line.slice(path_start, path_end - path_start)
173
+
174
+ var path = raw_url
175
+ var query: str = zero[str]
176
+ let question = raw_url.find_byte(63)
177
+ if question.is_some():
178
+ let q = question.unwrap()
179
+ path = raw_url.slice(0, q)
180
+ query = raw_url.slice(q + 1, raw_url.len - q - 1)
181
+
182
+ var headers = vec.Vec[HttpHeader].create()
183
+
184
+ let first_crlf = find_byte_from(inner, 13, first_line_end)
185
+ if first_crlf < inner.len:
186
+ var index = first_crlf + 2
187
+ while index < inner.len:
188
+ if inner.byte_at(index) == 13:
189
+ break
190
+
191
+ let line_end = find_byte_from(inner, 13, index)
192
+ if line_end >= inner.len:
193
+ break
194
+
195
+ let line = inner.slice(index, line_end - index)
196
+
197
+ let colon = line.find_byte(58)
198
+ if colon.is_none():
199
+ break
200
+ let c = colon.unwrap()
201
+ let name_text = line.slice(0, c)
202
+ var value_start = c + 1
203
+ while value_start < line.len and line.byte_at(value_start) == 32:
204
+ value_start += 1
205
+ let value_text = line.slice(value_start, line.len - value_start)
206
+
207
+ headers.push(HttpHeader(
208
+ name = string.String.from_str(name_text),
209
+ value = string.String.from_str(value_text)
210
+ ))
211
+
212
+ index = line_end + 2
213
+
214
+ var body_data = bytes.Bytes.empty()
215
+ let body_start = end_pos + 4
216
+ if body_start < raw_data.len:
217
+ let body_span = unsafe: span[ubyte](data = raw_data.data + body_start, len = raw_data.len - body_start)
218
+ body_data = bytes.Bytes.copy(body_span)
219
+
220
+ return Result[Request, string.String].success(value = Request(
221
+ method = string.String.from_str(method),
222
+ path = string.String.from_str(path),
223
+ query = query,
224
+ headers = headers,
225
+ body = body_data,
226
+ raw_url = string.String.from_str(raw_url)
227
+ ))
228
+
229
+
230
+ function find_header_terminator(data: span[ubyte]) -> Option[ptr_uint]:
231
+ if data.len < 4:
232
+ return Option[ptr_uint].none
233
+
234
+ var index: ptr_uint = 0
235
+ while index + 3 < data.len:
236
+ if (
237
+ unsafe: read(data.data + index) == 13
238
+ and read(data.data + index + 1) == 10
239
+ and read(data.data + index + 2) == 13
240
+ and read(data.data + index + 3) == 10
241
+ ):
242
+ return Option[ptr_uint].some(value = index)
243
+ index += 1
244
+
245
+ return Option[ptr_uint].none
246
+
247
+
248
+ function find_byte(data: str, target: ubyte) -> ptr_uint:
249
+ var index: ptr_uint = 0
250
+ while index < data.len:
251
+ if data.byte_at(index) == target:
252
+ return index
253
+ index += 1
254
+ return data.len
255
+
256
+
257
+ function find_byte_from(data: str, target: ubyte, start: ptr_uint) -> ptr_uint:
258
+ var index = start
259
+ while index < data.len:
260
+ if data.byte_at(index) == target:
261
+ return index
262
+ index += 1
263
+ return data.len
264
+
265
+
266
+ function ascii_case_equal(left: str, right: str) -> bool:
267
+ if left.len != right.len:
268
+ return false
269
+
270
+ var index: ptr_uint = 0
271
+ while index < left.len:
272
+ let a = left.byte_at(index)
273
+ let b = right.byte_at(index)
274
+
275
+ var lower_a = a
276
+ if lower_a >= 65 and lower_a <= 90:
277
+ lower_a += 32
278
+
279
+ var lower_b = b
280
+ if lower_b >= 65 and lower_b <= 90:
281
+ lower_b += 32
282
+
283
+ if lower_a != lower_b:
284
+ return false
285
+ index += 1
286
+
287
+ return true
288
+
289
+
290
+ public async function write_response(stream: net.TcpStream, response: ref[Response]) -> Result[ptr_uint, net.Error]:
291
+ var header_text = string.String.with_capacity(256)
292
+ defer header_text.release()
293
+
294
+ let reason_str = response.reason.as_str()
295
+ var status_text = string.String.create()
296
+ defer status_text.release()
297
+ append_ptr_uint(ref_of(status_text), ptr_uint<-response.status_code)
298
+
299
+ header_text.append("HTTP/1.1 ")
300
+ header_text.append(status_text.as_str())
301
+ header_text.push_byte(32)
302
+ header_text.append(reason_str)
303
+ header_text.append("\r\n")
304
+
305
+ var body_len_text = string.String.create()
306
+ defer body_len_text.release()
307
+ append_ptr_uint(ref_of(body_len_text), response.body.len)
308
+
309
+ var has_content_type = false
310
+ var index: ptr_uint = 0
311
+ while index < response.headers.len():
312
+ let header = response.headers.get(index) else:
313
+ fatal(c"http.server.write_response missing header")
314
+
315
+ unsafe:
316
+ let h = read(header)
317
+ header_text.append(h.name.as_str())
318
+ header_text.append(": ")
319
+ header_text.append(h.value.as_str())
320
+ header_text.append("\r\n")
321
+
322
+ if ascii_case_equal(h.name.as_str(), "Content-Type"):
323
+ has_content_type = true
324
+
325
+ index += 1
326
+
327
+ if not has_content_type:
328
+ header_text.append("Content-Type: text/plain\r\n")
329
+
330
+ header_text.append("Content-Length: ")
331
+ header_text.append(body_len_text.as_str())
332
+ header_text.append("\r\nConnection: close\r\n\r\n")
333
+
334
+ let header_bytes = text.as_byte_span(header_text.as_str())
335
+ (await stream.write_bytes(header_bytes))?
336
+ if response.body.len > 0:
337
+ (await stream.write_bytes(response.body.as_span()))?
338
+ return Result[ptr_uint, net.Error].success(value = header_bytes.len + response.body.len)
339
+
340
+
341
+ function append_ptr_uint(target: ref[string.String], value: ptr_uint) -> void:
342
+ var digits: array[ubyte, 32]
343
+ var count: ptr_uint = 0
344
+ if value == 0:
345
+ target.push_byte(48)
346
+ return
347
+
348
+ var remaining = value
349
+ while remaining != 0:
350
+ let digit = remaining % 10
351
+ digits[count] = ubyte<-(48z + digit)
352
+ remaining = remaining / 10
353
+ count += 1
354
+
355
+ while count > 0:
356
+ count -= 1
357
+ target.push_byte(digits[count])
358
+
359
+
360
+ extending Request:
361
+ public editable function release() -> void:
362
+ this.method.release()
363
+ this.path.release()
364
+ this.raw_url.release()
365
+
366
+ var index: ptr_uint = 0
367
+ while index < this.headers.len():
368
+ let header = this.headers.get(index) else:
369
+ fatal(c"http.server.Request.release missing header")
370
+
371
+ unsafe:
372
+ var h = read(header)
373
+ h.release()
374
+
375
+ index += 1
376
+
377
+ this.headers.release()
378
+ this.body.release()
379
+
380
+
381
+ extending HttpHeader:
382
+ public editable function release() -> void:
383
+ this.name.release()
384
+ this.value.release()
385
+
386
+
387
+ extending Response:
388
+ public editable function release() -> void:
389
+ this.reason.release()
390
+
391
+ var index: ptr_uint = 0
392
+ while index < this.headers.len():
393
+ let header = this.headers.get(index) else:
394
+ fatal(c"http.server.Response.release missing header")
395
+
396
+ unsafe:
397
+ var h = read(header)
398
+ h.release()
399
+
400
+ index += 1
401
+
402
+ this.headers.release()
403
+ this.body.release()
404
+
405
+ const DEFAULT_PORT: int = 8080
406
+ const READ_BUFFER_SIZE: ptr_uint = 4096
407
+
408
+
409
+ function parse_port(args: span[str]) -> int:
410
+ var index: ptr_uint = 0
411
+ while index < args.len:
412
+ let arg = unsafe: read(args.data + index)
413
+ if arg.equal("--port") or arg.equal("-p"):
414
+ index += 1
415
+ if index >= args.len:
416
+ return 0
417
+
418
+ let port_arg = unsafe: read(args.data + index)
419
+ var port: int = 0
420
+ var digit_index: ptr_uint = 0
421
+ while digit_index < port_arg.len:
422
+ let digit = int<-(port_arg.byte_at(digit_index)) - 48
423
+ if digit < 0 or digit > 9:
424
+ return 0
425
+ port = port * 10 + digit
426
+ digit_index += 1
427
+
428
+ if port > 0 and port < 65536:
429
+ return port
430
+
431
+ return 0
432
+
433
+ index += 1
434
+
435
+ return DEFAULT_PORT
436
+
437
+
438
+ function parse_serve_dir(args: span[str]) -> str:
439
+ var index: ptr_uint = 0
440
+ while index < args.len:
441
+ let arg = unsafe: read(args.data + index)
442
+ if arg.equal("--root") or arg.equal("-r"):
443
+ index += 1
444
+ if index >= args.len:
445
+ break
446
+
447
+ return unsafe: read(args.data + index)
448
+
449
+ if arg.equal("--port") or arg.equal("-p"):
450
+ index += 1
451
+ if index >= args.len:
452
+ break
453
+
454
+ index += 1
455
+ continue
456
+
457
+ if not arg.starts_with("-"):
458
+ return arg
459
+
460
+ index += 1
461
+
462
+ return "."
463
+
464
+
465
+ function guess_mime(file_path: str) -> str:
466
+ let e = path_ops.extension(file_path).unwrap_or("")
467
+ if e.len == 0:
468
+ return "application/octet-stream"
469
+ if e.equal(".html") or e.equal(".htm"):
470
+ return "text/html"
471
+ if e.equal(".css"):
472
+ return "text/css"
473
+ if e.equal(".js"):
474
+ return "application/javascript"
475
+ if e.equal(".json"):
476
+ return "application/json"
477
+ if e.equal(".md"):
478
+ return "text/markdown"
479
+ if e.equal(".png"):
480
+ return "image/png"
481
+ if e.equal(".jpg") or e.equal(".jpeg"):
482
+ return "image/jpeg"
483
+ if e.equal(".gif"):
484
+ return "image/gif"
485
+ if e.equal(".svg"):
486
+ return "image/svg+xml"
487
+ if e.equal(".ico"):
488
+ return "image/x-icon"
489
+ if e.equal(".wasm"):
490
+ return "application/wasm"
491
+ if e.equal(".txt"):
492
+ return "text/plain"
493
+ if e.equal(".xml"):
494
+ return "application/xml"
495
+ if e.equal(".pdf"):
496
+ return "application/pdf"
497
+ if e.equal(".mp3"):
498
+ return "audio/mpeg"
499
+ if e.equal(".mp4"):
500
+ return "video/mp4"
501
+ return "application/octet-stream"
502
+
503
+
504
+ function has_header_end(data: span[ubyte]) -> bool:
505
+ if data.len < 4:
506
+ return false
507
+
508
+ var index: ptr_uint = 0
509
+ while index + 3 < data.len:
510
+ if (
511
+ unsafe: read(data.data + index) == 13
512
+ and read(data.data + index + 1) == 10
513
+ and read(data.data + index + 2) == 13
514
+ and read(data.data + index + 3) == 10
515
+ ):
516
+ return true
517
+ index += 1
518
+
519
+ return false
520
+
521
+
522
+ async function send_error(stream: net.TcpStream, code: int, message: str) -> void:
523
+ var code_str = fmt.to_string_int(code)
524
+ defer code_str.release()
525
+ let body_text = f"<html><body><h1>#{code_str.as_str()} #{message}</h1></body></html>\r\n"
526
+ var body_len_str = fmt.to_string_ptr_uint(body_text.len)
527
+ defer body_len_str.release()
528
+ let header_text = f"HTTP/1.1 #{code_str.as_str()} #{message}\r\nContent-Type: text/html\r\nContent-Length: #{body_len_str.as_str()}\r\nConnection: close\r\n\r\n"
529
+ await stream.write_bytes(text.as_byte_span(header_text))
530
+ await stream.write_bytes(text.as_byte_span(body_text))
531
+
532
+
533
+ async function send_file_response(
534
+ stream: net.TcpStream,
535
+ code: int,
536
+ message: str,
537
+ content_type: str,
538
+ body: span[ubyte],
539
+ head_only: bool
540
+ ) -> void:
541
+ var code_str = fmt.to_string_int(code)
542
+ defer code_str.release()
543
+ var body_len_str = fmt.to_string_ptr_uint(body.len)
544
+ defer body_len_str.release()
545
+ let header_text = f"HTTP/1.1 #{code_str.as_str()} #{message}\r\nContent-Type: #{content_type}\r\nContent-Length: #{body_len_str.as_str()}\r\nConnection: close\r\n\r\n"
546
+ await stream.write_bytes(text.as_byte_span(header_text))
547
+ if not head_only:
548
+ await stream.write_bytes(body)
549
+
550
+
551
+ function sorted_insert(
552
+ names: ref[vec.Vec[string.String]],
553
+ dir_flags: ref[vec.Vec[bool]],
554
+ value: string.String,
555
+ is_directory: bool
556
+ ) -> void:
557
+ var index: ptr_uint = 0
558
+ while index < names.len():
559
+ let existing_ptr = names.get(index) else:
560
+ break
561
+ let existing_flag_ptr = dir_flags.get(index) else:
562
+ break
563
+
564
+ unsafe:
565
+ let existing_is_dir = read(existing_flag_ptr)
566
+ let existing_name = read(existing_ptr).as_str()
567
+
568
+ if is_directory and not existing_is_dir:
569
+ break
570
+ if not is_directory and existing_is_dir:
571
+ index += 1
572
+ continue
573
+
574
+ let cmp = value.as_str().compare(existing_name)
575
+ if cmp < 0:
576
+ break
577
+
578
+ index += 1
579
+
580
+ if not names.insert(index, value):
581
+ fatal(c"sorted_insert names insert failed")
582
+ if not dir_flags.insert(index, is_directory):
583
+ fatal(c"sorted_insert flags insert failed")
584
+
585
+
586
+ function html_escape(text_value: str) -> string.String:
587
+ var result = string.String.with_capacity(text_value.len * 2)
588
+ var index: ptr_uint = 0
589
+ while index < text_value.len:
590
+ let ch = text_value.byte_at(index)
591
+ if ch == 38:
592
+ result.append("&amp;")
593
+ else if ch == 60:
594
+ result.append("&lt;")
595
+ else if ch == 62:
596
+ result.append("&gt;")
597
+ else if ch == 34:
598
+ result.append("&quot;")
599
+ else:
600
+ result.push_byte(ch)
601
+ index += 1
602
+
603
+ return result
604
+
605
+
606
+ function release_string_vec(values: ref[vec.Vec[string.String]]) -> void:
607
+ var index: ptr_uint = 0
608
+ while index < values.len():
609
+ let value_ptr = values.get(index) else:
610
+ break
611
+ unsafe:
612
+ var v = read(value_ptr)
613
+ v.release()
614
+ index += 1
615
+
616
+ values.release()
617
+
618
+
619
+ async function serve_directory_index(stream: net.TcpStream, dir_path: str, url_prefix: str, head_only: bool) -> void:
620
+ var entries = fs.list_entries(dir_path) else:
621
+ await send_error(stream, 500, "Internal Server Error")
622
+ return
623
+ defer entries.release()
624
+
625
+ var names = vec.Vec[string.String].create()
626
+ defer release_string_vec(ref_of(names))
627
+
628
+ var is_dir = vec.Vec[bool].create()
629
+ defer is_dir.release()
630
+
631
+ var index: ptr_uint = 0
632
+ while index < entries.len():
633
+ let entry_ptr = entries.get(index)
634
+ if entry_ptr.is_some():
635
+ let entry_name = entry_ptr.unwrap()
636
+ let name_str = entry_name
637
+ var child_path = path_ops.join(dir_path, name_str)
638
+ defer child_path.release()
639
+ let dir_flag = fs.is_directory(child_path.as_str())
640
+ sorted_insert(ref_of(names), ref_of(is_dir), string.String.from_str(name_str), dir_flag)
641
+ index += 1
642
+
643
+ var body = string.String.with_capacity(4096)
644
+ defer body.release()
645
+
646
+ var escaped_path = html_escape(url_prefix)
647
+ defer escaped_path.release()
648
+
649
+ body.append("<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\"><title>Index of ")
650
+ body.append(escaped_path.as_str())
651
+ body.append("</title></head>\n<body>\n<h1>Index of ")
652
+ body.append(escaped_path.as_str())
653
+ body.append("</h1>\n<hr>\n<pre>\n")
654
+
655
+ if url_prefix.len > 0 and not url_prefix.equal("/"):
656
+ body.append("<a href=\"../\">../</a>\n")
657
+
658
+ index = 0
659
+ while index < names.len():
660
+ let name_ptr = names.get(index) else:
661
+ break
662
+ let dir_flag_ptr = is_dir.get(index) else:
663
+ break
664
+
665
+ unsafe:
666
+ let name_str = read(name_ptr).as_str()
667
+ let is_directory = read(dir_flag_ptr)
668
+
669
+ var line = string.String.with_capacity(name_str.len + 64)
670
+ defer line.release()
671
+
672
+ var escaped_name = html_escape(name_str)
673
+ defer escaped_name.release()
674
+
675
+ if is_directory:
676
+ line.append("<a href=\"")
677
+ line.append(escaped_name.as_str())
678
+ line.append("/\">")
679
+ line.append(escaped_name.as_str())
680
+ line.append("/</a>")
681
+ else:
682
+ line.append("<a href=\"")
683
+ line.append(escaped_name.as_str())
684
+ line.append("\">")
685
+ line.append(escaped_name.as_str())
686
+ line.append("</a>")
687
+
688
+ line.append("\n")
689
+ body.append(line.as_str())
690
+
691
+ index += 1
692
+
693
+ body.append("</pre>\n<hr>\n</body>\n</html>")
694
+
695
+ let body_span = text.as_byte_span(body.as_str())
696
+ await send_file_response(stream, 200, "OK", "text/html; charset=utf-8", body_span, head_only)
697
+
698
+
699
+ async function handle_connection(stream: net.TcpStream, serve_dir: str) -> void:
700
+ var conn = stream
701
+ defer conn.release()
702
+
703
+ var buffer = vec.Vec[ubyte].with_capacity(READ_BUFFER_SIZE)
704
+ defer buffer.release()
705
+
706
+ var headers_done = false
707
+ while not headers_done:
708
+ var chunk = await conn.read_once(READ_BUFFER_SIZE) else:
709
+ return
710
+ buffer.append_span(chunk.as_span())
711
+ chunk.release()
712
+ headers_done = has_header_end(buffer.as_span())
713
+
714
+ let raw = buffer.as_span()
715
+ let header_text = text.utf8_byte_span_as_str(raw) else:
716
+ await send_error(conn, 400, "Bad Request")
717
+ return
718
+
719
+ let first_line_end = find_byte_from_str(header_text, 10, 0)
720
+ var request_line = header_text.slice(0, first_line_end)
721
+ if request_line.ends_with("\r"):
722
+ request_line = header_text.slice(0, request_line.len - 1)
723
+
724
+ let method_end = find_byte_from_str(request_line, 32, 0)
725
+ if method_end >= request_line.len:
726
+ await send_error(conn, 400, "Bad Request")
727
+ return
728
+
729
+ let method = request_line.slice(0, method_end)
730
+
731
+ var path_start = method_end + 1
732
+ while path_start < request_line.len and request_line.byte_at(path_start) == 32:
733
+ path_start += 1
734
+
735
+ let path_end = find_byte_from_str(request_line, 32, path_start)
736
+ var url_path = request_line.slice(path_start, path_end - path_start)
737
+
738
+ if not method.equal("GET") and not method.equal("HEAD"):
739
+ await send_error(conn, 405, "Method Not Allowed")
740
+ return
741
+
742
+ let url_prefix = url_path
743
+ var path_is_root = false
744
+ if url_path.starts_with("/"):
745
+ let stripped = url_path.slice(1, url_path.len - 1)
746
+ if stripped.len == 0:
747
+ path_is_root = true
748
+ url_path = "index.html"
749
+ else:
750
+ url_path = stripped
751
+
752
+ var file_path = path_ops.join(serve_dir, url_path)
753
+ defer file_path.release()
754
+
755
+ let content_result = fs.read_bytes(file_path.as_str())
756
+ match content_result:
757
+ Result.success as content_ok:
758
+ var content = content_ok.value
759
+ defer content.release()
760
+
761
+ let mime = guess_mime(file_path.as_str())
762
+ await send_file_response(conn, 200, "OK", mime, content.as_span(), method.equal("HEAD"))
763
+ Result.failure:
764
+ if fs.is_directory(file_path.as_str()):
765
+ await serve_directory_index(conn, file_path.as_str(), url_prefix, method.equal("HEAD"))
766
+ else if path_is_root:
767
+ await serve_directory_index(conn, serve_dir, url_prefix, method.equal("HEAD"))
768
+ else:
769
+ await send_error(conn, 404, "Not Found")
770
+
771
+
772
+ function find_byte_from_str(data: str, target: ubyte, start: ptr_uint) -> ptr_uint:
773
+ var index = start
774
+ while index < data.len:
775
+ if data.byte_at(index) == target:
776
+ return index
777
+ index += 1
778
+ return data.len
779
+
780
+
781
+ async function main(args: span[str]) -> void:
782
+ var port = parse_port(args)
783
+ if port <= 0:
784
+ port = DEFAULT_PORT
785
+
786
+ let serve_dir = parse_serve_dir(args)
787
+
788
+ var port_str = fmt.to_string_int(port)
789
+ defer port_str.release()
790
+
791
+ stdio.print_format("Serving HTTP on http://0.0.0.0:%d (dir: %s)\n", port, serve_dir)
792
+
793
+ let address = net.ipv4("0.0.0.0", port) else:
794
+ stdio.print_format("failed to resolve address\n")
795
+ return
796
+
797
+ var listener = net.listen(address, 128) else:
798
+ stdio.print_format("failed to listen\n")
799
+ return
800
+
801
+ defer listener.release()
802
+
803
+ while true:
804
+ let accept_result = await listener.accept()
805
+ if accept_result.is_failure():
806
+ var err = accept_result.unwrap_error()
807
+ stdio.print_format("accept error: %s\n", err.message.as_str())
808
+ err.release()
809
+ else:
810
+ await handle_connection(accept_result.unwrap(), serve_dir)