mt-lang 0.3.27 → 0.3.30

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 802d8ba551d2f4712c3dc174ade901063ef797baf33a87ad8a2b902db2916b24
4
- data.tar.gz: 322d7781ae7fcbf67108bf260b63caf8889facf0382c3d7e248e124868a0bbd0
3
+ metadata.gz: d6d13cde9fca875b324ff3e0cb3b892c371e2445ac5e061d138bd0dc8b284b88
4
+ data.tar.gz: 2a91eedc02120e0d042bc6bafbfc783a4eefb5bbc44c34940e1ee9be6917405e
5
5
  SHA512:
6
- metadata.gz: afd649faf27c7327ab60c9050c56eb66416fd022fbb11a2d8b8f16438a021732fdec0a65e76a16649f6b35ec7c070d2e0873b56a02ff0d8aa8c1505ffb72d685
7
- data.tar.gz: eee26037932660079c5b1352eac96ef923766930c42d611895206c66276ef31a313213cc3b104046f4ff0048af7035bb77a2aa4cf270c79a08aa0e203e31bb85
6
+ metadata.gz: 478dc59b852c703b5cf63ce3cd77a92d6de305424c96cfa7dbeda97ed1882f2d8c52f5770893dc99a3077cb1599cbf0602c6e54b773f219ce859b8b9275c7258
7
+ data.tar.gz: d221d15fe3c3192ee0b10b61101f5234e8b92830f878051c6e2641aca334b7d165c9668bf410a727b4964f02d45805322044470a9e406b971ab7508650d60a00
data/README.md CHANGED
@@ -94,7 +94,7 @@ Supported literals:
94
94
  - floats: `3.14`, `1.2e-3`, `1.0f` (float suffix), `1.0d` (double suffix)
95
95
  - character: `'a'`, `'\n'`, `'\t'`, `'\\'`, `'\''`, `'\0'`, `'\x41'`. Type is `ubyte`. Escape sequences: `\n`, `\r`, `\t`, `\\`, `\'`, `\"`, `\0` (null byte), `\xNN` (hex byte).
96
96
  - booleans: `true`, `false`
97
- - string: `"hello"` -> `str`
97
+ - string: `"hello"` -> `str`. The `+` operator concatenates `str` values: `"hello" + " " + "world"` produces `"hello world"`. For loops or repeated concatenation, prefer `string.String`.
98
98
  - cstring: `c"hello"` -> `cstr`
99
99
  - heredoc string: `<<-TAG ... TAG`
100
100
  - heredoc cstring: `c<<-TAG ... TAG`
@@ -106,7 +106,7 @@ Common punctuation and operators:
106
106
  - delimiters: `(` `)` `[` `]`
107
107
  - access and separators: `:` `,` `.`
108
108
  - type markers: `->` `?`
109
- - arithmetic: `+ - * / %`
109
+ - arithmetic: `+ - * / %` (additionally, `+` on `str` concatenates)
110
110
  - bitwise: `~ & | ^ << >>`
111
111
  - comparison: `== != < <= > >=`
112
112
  - assignment: `= += -= *= /= %= &= |= ^= <<= >>=`
@@ -249,6 +249,10 @@ struct Vec2:
249
249
  x: float
250
250
  y: float
251
251
 
252
+ # Methods may also be defined directly inside the struct body:
253
+ function length_sq() -> float:
254
+ return this.x * this.x + this.y * this.y
255
+
252
256
  @[packed]
253
257
  struct Header:
254
258
  tag: ubyte
@@ -310,6 +314,7 @@ variant Token:
310
314
  Rules:
311
315
 
312
316
  - `struct` and `opaque` may declare nominal interface conformance with `implements`.
317
+ - `struct` bodies may contain `function`, `editable function`, and `static function` declarations directly — they desugar to `extending` blocks targeting the enclosing struct. This is pure syntactic sugar; the compiler emits identical code as a separate `extending` block.
313
318
  - `attribute[target, ...]` declares reusable declaration attributes for `struct`, `field`, `callable`, `const`, `event`, `enum`, `flags`, `union`, and `variant` targets.
314
319
  - Attributes are applied with one or more leading `@[name(...)]` blocks. Built-in `packed`, `align(bytes)`, and `deprecated(message)` are predefined attributes.
315
320
  - `variant` arms may carry named payload fields.
@@ -358,11 +363,31 @@ Method kinds:
358
363
  - `editable function` -> editable receiver
359
364
  - `static function` -> no receiver
360
365
 
366
+ Methods may appear inside a struct body (desugared to an `extending` block) or in a separate
367
+ `extending` declaration:
368
+
369
+ ```mt
370
+ struct Counter:
371
+ value: int
372
+
373
+ function read() -> int:
374
+ return this.value
375
+
376
+ editable function bump() -> void:
377
+ this.value += 1
378
+
379
+ static function zero() -> Counter:
380
+ return Counter(value = 0)
381
+ ```
382
+
383
+ Both inline and `extending` forms lower to identical C code.
384
+
361
385
  Method notes:
362
386
 
363
387
  - Async methods are supported.
364
388
  - Generic methods are supported.
365
389
  - There is no constructor keyword. Names like `init` and `default` are ordinary static methods.
390
+ - Methods may be defined inside struct bodies directly (as syntactic sugar for `extending`) or in separate `extending` blocks. Both forms lower to the same C code; the inline form is preferred when the struct and its core methods appear in the same file.
366
391
 
367
392
  ## 7. Functions, Externals, And Foreign Functions
368
393
 
@@ -912,7 +937,7 @@ See module source for full method surface. Iterator forms:
912
937
 
913
938
  Text categories:
914
939
 
915
- - `str` -> string view
940
+ - `str` -> string view. The `+` operator concatenates two `str` values, allocating a new heap-backed string. For loops or repeated concatenation, prefer `string.String` for amortized performance.
916
941
  - `cstr` -> C ABI string
917
942
  - `str_buffer[N]` -> fixed-capacity mutable UTF-8 text buffer
918
943
 
data/docs/index.html CHANGED
@@ -961,7 +961,7 @@ let total = (
961
961
  <tr><td>Delimiters</td><td><code>( ) [ ]</code></td></tr>
962
962
  <tr><td>Separators / Access</td><td><code>: , .</code></td></tr>
963
963
  <tr><td>Type markers</td><td><code>-&gt;</code> <code>?</code></td></tr>
964
- <tr><td>Arithmetic</td><td><code>+ - * / %</code></td></tr>
964
+ <tr><td>Arithmetic</td><td><code>+ - * / %</code> (the <code>+</code> operator on <code>str</code> concatenates, allocating a new heap-backed string)</td></tr>
965
965
  <tr><td>Bitwise</td><td><code>~ &amp; | ^ &lt;&lt; &gt;&gt;</code></td></tr>
966
966
  <tr><td>Comparison</td><td><code>== != &lt; &lt;= &gt; &gt;=</code></td></tr>
967
967
  <tr><td>Assignment</td><td><code>= += -= *= /= %= &amp;= |= ^= &lt;&lt;= &gt;&gt;=</code></td></tr>
@@ -1243,7 +1243,7 @@ let d: dyn[Drawable] = adapt[Drawable](ref_of(entity))</code></pre>
1243
1243
  <tr><td><code>ptr_int</code> <code>ptr_uint</code></td><td>Pointer-sized integers</td></tr>
1244
1244
  <tr><td><code>float</code> <code>double</code></td><td>Floating-point</td></tr>
1245
1245
  <tr><td><code>void</code></td><td>No value</td></tr>
1246
- <tr><td><code>str</code></td><td>UTF-8 string view (borrowed)</td></tr>
1246
+ <tr><td><code>str</code></td><td>UTF-8 string view (borrowed). The <code>+</code> operator concatenates two <code>str</code> values, allocating a new heap-backed string.</td></tr>
1247
1247
  <tr><td><code>cstr</code></td><td>NUL-terminated C string</td></tr>
1248
1248
  <tr><td><code>vec2</code> <code>vec3</code> <code>vec4</code></td><td>Float vectors with <code>.x .y .z .w</code></td></tr>
1249
1249
  <tr><td><code>ivec2</code> <code>ivec3</code> <code>ivec4</code></td><td>Integer vectors</td></tr>
@@ -38,7 +38,7 @@ If code allocates, takes an address, dereferences a raw pointer, performs an FFI
38
38
 
39
39
  FFI visibility belongs at the declaration site. Raw `external` files expose exact ABI types. Imported foreign declarations may project those raw types into ordinary Milk Tea types, but the projection rule, temporary-storage rule, and ownership rule must be declared there instead of repeated at every call site.
40
40
 
41
- The same rule applies to text construction. Plain string literals and format string literals are borrowed `str` values. Any surface that builds owned text must say so explicitly, for example `std.fmt.format(f"...")` when ownership must escape.
41
+ The same rule applies to text construction. Plain string literals and format string literals are borrowed `str` values. The `+` operator on `str` allocates a new heap-backed `str` — the one everyday convenience that does allocate. For loops or amortized building, `string.String` and `str_buffer[N]` remain the explicit surfaces with visible cost. Any other surface that builds owned text must say so explicitly, for example `std.fmt.format(f"...")` when ownership must escape.
42
42
 
43
43
  ### 2. C is the ABI ground truth
44
44
 
@@ -755,7 +755,7 @@ Explicit specialization arguments may be type references like `bytes_for[int](4)
755
755
 
756
756
  Built-in operators should match familiar C behavior where possible:
757
757
 
758
- - arithmetic: `+ - * / %`
758
+ - arithmetic: `+ - * / %` (the `+` operator on `str` concatenates, allocating a new heap-backed string)
759
759
  - comparison: `== != < <= > >=`
760
760
  - boolean: `and or not`
761
761
  - bitwise: `& | ^ ~ << >>`
@@ -124,7 +124,7 @@ Supported literals:
124
124
  - integer: `42`, `0xff`, `0b1010`, with `_` separators. Integer type suffixes: `42u` (`uint`), `0xFFub` (`ubyte`), `100z` (`ptr_uint`), `7i` (`int`), `-1l` (`long`), etc.
125
125
  - float: `3.14`, `1.2e-3`, `1.1920929E-7`, `1.0f` (float suffix), `1.0d` (double suffix)
126
126
  - character: `'a'`, `'\n'`, `'\t'`, `'\\'`, `'\''`, `'\0'`, `'\x41'`. Type is `ubyte`. Escape sequences: `\n`, `\r`, `\t`, `\\`, `\'`, `\"`, `\0` (null), `\xNN` (hex byte).
127
- - string: `"hello"` (`str`). Supported string escapes are `\n`, `\r`, `\t`, `\0` (null), `\"`, `\'`, and `\\`; any other `\x` sequence is taken literally. Hex byte escapes (`\xNN`) are character-literal only, not string-literal.
127
+ - string: `"hello"` (`str`). Supported string escapes are `\n`, `\r`, `\t`, `\0` (null), `\"`, `\'`, and `\\`; any other `\x` sequence is taken literally. Hex byte escapes (`\xNN`) are character-literal only, not string-literal. The `+` operator concatenates `str` values: `"hello" + " " + "world"` produces `"hello world"`. Each `+` allocates a new heap-backed `str`; for loops or repeated concatenation, prefer `string.String` for amortized performance.
128
128
  - cstring: `c"hello"` (`cstr`)
129
129
  - heredoc string: `<<-TAG ... TAG` (`str`)
130
130
  - heredoc cstring: `c<<-TAG ... TAG` (`cstr`)
@@ -300,7 +300,25 @@ Callable and `ref[...]` rules:
300
300
  struct Vec2:
301
301
  x: float
302
302
  y: float
303
+ ```
304
+
305
+ `struct` bodies may also contain `function`, `editable function`, and `static function` declarations directly — they desugar to `extending` blocks targeting the enclosing struct. This is pure syntactic sugar and produces identical C code. See §3.6 for the method kind rules.
306
+
307
+ ```mt
308
+ struct Counter:
309
+ value: int
310
+
311
+ function read() -> int:
312
+ return this.value
313
+
314
+ editable function bump() -> void:
315
+ this.value += 1
316
+
317
+ static function zero() -> Counter:
318
+ return Counter(value = 0)
319
+ ```
303
320
 
321
+ ```mt
304
322
  union Number:
305
323
  i: int
306
324
  f: float
@@ -417,7 +435,25 @@ Rules:
417
435
 
418
436
  ### 3.6 Methods
419
437
 
438
+ Methods may appear directly inside a struct body (desugared to an `extending` block) or in a separate `extending` declaration. Both forms lower to identical C code.
439
+
440
+ ```mt
441
+ # Inline form (sugar)
442
+ struct Counter:
443
+ value: int
444
+
445
+ function read() -> int:
446
+ return this.value
447
+
448
+ editable function bump() -> void:
449
+ this.value += 1
450
+
451
+ static function zero() -> Counter:
452
+ return Counter(value = 0)
453
+ ```
454
+
420
455
  ```mt
456
+ # Equivalent extending form
421
457
  extending Counter:
422
458
  function read() -> int:
423
459
  return this.value
@@ -979,7 +1015,7 @@ Rules:
979
1015
  8. `==`, `!=`
980
1016
  9. `<`, `<=`, `>`, `>=`
981
1017
  10. `<<`, `>>`
982
- 11. `+`, `-`
1018
+ 11. `+`, `-` (additionally, `+` on `str` concatenates; each `+` allocates a new heap-backed `str`)
983
1019
  12. `*`, `/`, `%`
984
1020
 
985
1021
  ### 5.4 Assignment operators
data/lib/milk_tea/base.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "pathname"
4
4
 
5
5
  module MilkTea
6
- VERSION = "0.3.27"
6
+ VERSION = "0.3.30"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -289,6 +289,14 @@ module MilkTea
289
289
  emitted_functions.any? { |function| function_uses_str_equality?(function) }
290
290
  end
291
291
 
292
+ def uses_str_concat_helper?
293
+ return @uses_str_concat if defined?(@uses_str_concat)
294
+
295
+ @uses_str_concat = emitted_functions.any? do |function|
296
+ function_uses_named_call?(function, %w[mt_str_concat])
297
+ end
298
+ end
299
+
292
300
  def uses_variant_equality_helper?
293
301
  emitted_functions.any? { |function| function_uses_variant_equality?(function) }
294
302
  end
@@ -42,6 +42,27 @@ module MilkTea
42
42
  ]
43
43
  end
44
44
 
45
+ def emit_str_concat_helper
46
+ [
47
+ "#define MT_STR_CONCAT_BUF_SIZE 65536",
48
+ "",
49
+ "static _Thread_local char mt_str_concat_buf[MT_STR_CONCAT_BUF_SIZE];",
50
+ "static _Thread_local uintptr_t mt_str_concat_offset = 0;",
51
+ "",
52
+ "static mt_str mt_str_concat(mt_str a, mt_str b) {",
53
+ "#{INDENT}uintptr_t total = a.len + b.len;",
54
+ "#{INDENT}if (mt_str_concat_offset + total > MT_STR_CONCAT_BUF_SIZE) {",
55
+ "#{INDENT * 2}mt_str_concat_offset = 0;",
56
+ "#{INDENT}}",
57
+ "#{INDENT}char* buf = mt_str_concat_buf + mt_str_concat_offset;",
58
+ "#{INDENT}if (a.len > 0) memcpy(buf, a.data, a.len);",
59
+ "#{INDENT}if (b.len > 0) memcpy(buf + a.len, b.data, b.len);",
60
+ "#{INDENT}mt_str_concat_offset += total;",
61
+ "#{INDENT}return (mt_str){ .data = buf, .len = total };",
62
+ "}",
63
+ ]
64
+ end
65
+
45
66
  def emit_variant_equality_helpers
46
67
  emitted_aggregate_variants.flat_map { |variant_decl| emit_variant_equality_helper(variant_decl) }
47
68
  end
@@ -146,6 +146,10 @@ module MilkTea
146
146
  lines.concat(emit_str_equality_helper)
147
147
  lines << ""
148
148
  end
149
+ if uses_str_concat_helper?
150
+ lines.concat(emit_str_concat_helper)
151
+ lines << ""
152
+ end
149
153
  if uses_str_buffer_helpers?
150
154
  lines.concat(emit_utf8_validation_helpers)
151
155
  lines << ""
@@ -1387,6 +1387,10 @@ module MilkTea
1387
1387
  left = cast_expression(left, operand_type) if operand_type
1388
1388
  right = cast_expression(right, operand_type) if operand_type
1389
1389
 
1390
+ if expression.operator == "+" && left_type == @ctx.types.fetch("str") && right_type == @ctx.types.fetch("str")
1391
+ return IR::Call.new(callee: "mt_str_concat", arguments: [left, right], type:)
1392
+ end
1393
+
1390
1394
  expanded = lower_vector_binary_operation(expression.operator, left, left_type, right, right_type, type)
1391
1395
  return expanded if expanded
1392
1396
 
@@ -369,7 +369,7 @@ module MilkTea
369
369
  AST::AttributeDecl.new(name: name_token.lexeme, targets:, params:, visibility:, line:, column: name_token.column)
370
370
  end
371
371
 
372
- def parse_struct_decl(packed: false, alignment: nil, visibility: :private, attributes: [])
372
+ def parse_struct_decl(packed: false, alignment: nil, visibility: :private, attributes: [], inline_methods: true)
373
373
  line = previous.line
374
374
  name_token = consume_name("expected struct name")
375
375
  name = name_token.lexeme
@@ -377,13 +377,32 @@ module MilkTea
377
377
  implements = parse_implements_clause
378
378
  c_name = parse_optional_c_name
379
379
  packed, alignment = parse_struct_layout_attributes(attributes) if attributes.any?
380
- members = parse_recoverable_block do
381
- parse_struct_member
380
+ receiver_type_param_names = type_params.map(&:name)
381
+ members = with_type_param_names(receiver_type_param_names) do
382
+ parse_recoverable_block do
383
+ parse_struct_member
384
+ end
382
385
  end
383
386
  fields = members.filter_map { |kind, member| member if kind == :field }
384
387
  events = members.filter_map { |kind, member| member if kind == :event }
385
388
  nested_types = members.filter_map { |kind, member| member if kind == :nested_type }
386
- AST::StructDecl.new(name:, type_params:, implements:, c_name:, fields:, events:, nested_types:, attributes:, packed:, alignment:, visibility:, lifetime_params:, line:, column: name_token.column)
389
+ methods = members.filter_map { |kind, member| member if kind == :method }
390
+ struct_decl = AST::StructDecl.new(name:, type_params:, implements:, c_name:, fields:, events:, nested_types:, attributes:, packed:, alignment:, visibility:, lifetime_params:, line:, column: name_token.column)
391
+
392
+ if inline_methods && methods.any?
393
+ type_ref_args = type_params.map do |tp|
394
+ AST::TypeArgument.new(
395
+ value: AST::TypeRef.new(name: AST::QualifiedName.new(parts: [tp.name]), arguments: [], nullable: false, line: tp.line, column: tp.column),
396
+ line: tp.line,
397
+ column: tp.column,
398
+ )
399
+ end
400
+ type_ref = AST::TypeRef.new(name: AST::QualifiedName.new(parts: [name]), arguments: type_ref_args, nullable: false, line: name_token.line, column: name_token.column)
401
+ extending_block = AST::ExtendingBlock.new(type_name: type_ref, methods:, line: name_token.line, column: name_token.column)
402
+ [struct_decl, extending_block]
403
+ else
404
+ struct_decl
405
+ end
387
406
  end
388
407
 
389
408
  def parse_struct_decl_params
@@ -429,8 +448,24 @@ module MilkTea
429
448
  [lifetime_params, type_params]
430
449
  end
431
450
 
451
+ def check_method_start?
452
+ saved = @current
453
+ match(:public)
454
+ match(:async)
455
+ match(:editable) if check(:editable)
456
+ match(:static) if check(:static)
457
+ result = check(:function)
458
+ @current = saved
459
+ result
460
+ end
461
+
432
462
  def parse_struct_member
433
463
  field_attributes = parse_attribute_applications
464
+
465
+ if check_method_start?
466
+ return [:method, parse_method_def(attributes: field_attributes)]
467
+ end
468
+
434
469
  visibility, visibility_token = parse_visibility
435
470
 
436
471
  if match(:event)
@@ -438,7 +473,7 @@ module MilkTea
438
473
  end
439
474
 
440
475
  if match(:struct)
441
- return [:nested_type, parse_struct_decl(visibility:, attributes: field_attributes)]
476
+ return [:nested_type, parse_struct_decl(visibility:, attributes: field_attributes, inline_methods: false)]
442
477
  end
443
478
 
444
479
  raise error(visibility_token, "public is only allowed on struct events") if visibility == :public
@@ -154,13 +154,23 @@ module MilkTea
154
154
  until eof?
155
155
  if errors
156
156
  begin
157
- declarations << parse_declaration
157
+ result = parse_declaration
158
+ if result.is_a?(Array)
159
+ declarations.concat(result)
160
+ else
161
+ declarations << result
162
+ end
158
163
  rescue ParseError => e
159
164
  errors << e
160
165
  synchronize_to_top_level_boundary
161
166
  end
162
167
  else
163
- declarations << parse_declaration
168
+ result = parse_declaration
169
+ if result.is_a?(Array)
170
+ declarations.concat(result)
171
+ else
172
+ declarations << result
173
+ end
164
174
  end
165
175
  skip_newlines
166
176
  end
@@ -612,8 +612,8 @@ module MilkTea
612
612
 
613
613
  left_type
614
614
  when "+", "-", "*", "/"
615
- if expression.operator == "+" && (string_like_type?(left_type) || string_like_type?(right_type))
616
- raise_sema_error("operator + does not support str/cstr concatenation; use continued string literals for static text or string.String/str_buffer for dynamic text")
615
+ if expression.operator == "+" && left_type == @ctx.types.fetch("str") && right_type == @ctx.types.fetch("str")
616
+ return left_type
617
617
  end
618
618
 
619
619
  pointer_result = pointer_arithmetic_result(expression.operator, left_type, right_type)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mt-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.27
4
+ version: 0.3.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -149,9 +149,6 @@ files:
149
149
  - docs/index.html
150
150
  - docs/language-design.md
151
151
  - docs/language-manual.md
152
- - docs/self-host-contract.md
153
- - docs/self-host-plan.md
154
- - docs/self-host-progress.md
155
152
  - lib/milk_tea.rb
156
153
  - lib/milk_tea/base.rb
157
154
  - lib/milk_tea/bindings.rb
@@ -627,7 +624,7 @@ metadata:
627
624
  homepage_uri: https://teefan.github.io/mt-lang/
628
625
  source_code_uri: https://github.com/teefan/mt-lang
629
626
  post_install_message: |
630
- Milk Tea 0.3.27 installed!
627
+ Milk Tea 0.3.30 installed!
631
628
 
632
629
  System requirements:
633
630
  - A C compiler (gcc or clang) must be available on PATH