mt-lang 0.3.28 → 0.3.31
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 +4 -4
- data/README.md +9 -7
- data/docs/index.html +24 -5
- data/docs/language-design.md +3 -1
- data/docs/language-manual.md +60 -4
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/c_backend/expressions.rb +22 -2
- data/lib/milk_tea/core/c_backend/feature_detection.rb +122 -24
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +110 -20
- data/lib/milk_tea/core/c_backend.rb +37 -5
- data/lib/milk_tea/core/compile_time.rb +25 -0
- data/lib/milk_tea/core/lowering/declarations.rb +8 -0
- data/lib/milk_tea/core/lowering/expressions.rb +4 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +37 -8
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +45 -3
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +31 -1
- metadata +2 -3
- data/docs/self-hosted-compiler-plan.md +0 -737
|
@@ -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 << ""
|
|
@@ -232,17 +236,45 @@ module MilkTea
|
|
|
232
236
|
lines << ""
|
|
233
237
|
end
|
|
234
238
|
|
|
239
|
+
if uses_variant_equality_helper? || uses_struct_equality_helper?
|
|
240
|
+
lines.concat(emit_aggregate_equality_forward_declarations)
|
|
241
|
+
lines << ""
|
|
242
|
+
end
|
|
235
243
|
if uses_variant_equality_helper?
|
|
236
244
|
unless uses_str_equality_helper?
|
|
237
|
-
|
|
238
|
-
if variant_needs_str_eq
|
|
239
|
-
lines.concat(emit_string_type) unless uses_string_view?
|
|
240
|
-
lines.concat(emit_str_equality_helper)
|
|
241
|
-
end
|
|
245
|
+
emit_str_equality_support(lines) if aggregate_equality_needs_string_view?
|
|
242
246
|
end
|
|
243
247
|
lines.concat(emit_variant_equality_helpers)
|
|
244
248
|
lines << ""
|
|
245
249
|
end
|
|
250
|
+
if uses_struct_equality_helper?
|
|
251
|
+
unless uses_str_equality_helper?
|
|
252
|
+
emit_str_equality_support(lines) if aggregate_equality_needs_string_view?
|
|
253
|
+
end
|
|
254
|
+
lines.concat(emit_struct_equality_helpers)
|
|
255
|
+
lines << ""
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def emit_aggregate_equality_forward_declarations
|
|
260
|
+
lines = []
|
|
261
|
+
struct_equality_types.each do |type|
|
|
262
|
+
c = named_type_c_name(type)
|
|
263
|
+
lines << "static bool mt_struct_eq_#{c}(struct #{c} left, struct #{c} right);"
|
|
264
|
+
end
|
|
265
|
+
variant_equality_types.each do |type|
|
|
266
|
+
c = named_type_c_name(type)
|
|
267
|
+
lines << "static bool mt_variant_eq_#{c}(struct #{c} left, struct #{c} right);"
|
|
268
|
+
end
|
|
269
|
+
lines
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def emit_str_equality_support(lines)
|
|
273
|
+
return if @emitted_aggregate_str_equality_support
|
|
274
|
+
|
|
275
|
+
@emitted_aggregate_str_equality_support = true
|
|
276
|
+
lines.concat(emit_string_type) unless uses_string_view?
|
|
277
|
+
lines.concat(emit_str_equality_helper)
|
|
246
278
|
end
|
|
247
279
|
|
|
248
280
|
def emit_function_forward_declarations(lines)
|
|
@@ -31,10 +31,35 @@ module MilkTea
|
|
|
31
31
|
return left == right if left.is_a?(String) && right.is_a?(String)
|
|
32
32
|
return left == right if boolean_value?(left) && boolean_value?(right)
|
|
33
33
|
return left == right if left.is_a?(Types::Base) && right.is_a?(Types::Base)
|
|
34
|
+
return struct_equality_result(left, right) if left.is_a?(Hash) && right.is_a?(Hash)
|
|
35
|
+
return variant_equality_result(left, right) if left.is_a?(VariantValue) && right.is_a?(VariantValue)
|
|
34
36
|
|
|
35
37
|
nil
|
|
36
38
|
end
|
|
37
39
|
|
|
40
|
+
# Const-time struct values are represented as {field_name => value} hashes;
|
|
41
|
+
# compare them field by field, mirroring the runtime struct == semantics.
|
|
42
|
+
def self.struct_equality_result(left, right)
|
|
43
|
+
return nil unless left.is_a?(Hash) && right.is_a?(Hash)
|
|
44
|
+
return nil unless left.keys.sort == right.keys.sort
|
|
45
|
+
|
|
46
|
+
left.each do |name, value|
|
|
47
|
+
field_result = equality_result(value, right[name])
|
|
48
|
+
return nil if field_result.nil?
|
|
49
|
+
return false if field_result == false
|
|
50
|
+
end
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Const-time variant values; fields mirror the runtime arm payload layout.
|
|
55
|
+
VariantValue = Data.define(:arm, :fields)
|
|
56
|
+
|
|
57
|
+
def self.variant_equality_result(left, right)
|
|
58
|
+
return false unless left.arm == right.arm
|
|
59
|
+
|
|
60
|
+
struct_equality_result(left.fields, right.fields)
|
|
61
|
+
end
|
|
62
|
+
|
|
38
63
|
def self.boolean_value?(value)
|
|
39
64
|
value == true || value == false
|
|
40
65
|
end
|
|
@@ -78,6 +78,14 @@ module MilkTea
|
|
|
78
78
|
IR::AggregateField.new(name:, value: lower_const_value_literal(field_type, field_value))
|
|
79
79
|
end
|
|
80
80
|
IR::AggregateLiteral.new(type:, fields:)
|
|
81
|
+
when CompileTime::VariantValue
|
|
82
|
+
arm_field_types = type.respond_to?(:arm) ? (type.arm(const_value.arm) || {}) : {}
|
|
83
|
+
fields = const_value.fields.map do |field_name, field_value|
|
|
84
|
+
field_type = arm_field_types[field_name]
|
|
85
|
+
raise LoweringError.new("constant variant arm #{const_value.arm} field #{field_name} not found in #{type}", line: 0, column: 0, path: @ctx.current_analysis_path) unless field_type
|
|
86
|
+
IR::AggregateField.new(name: field_name, value: lower_const_value_literal(field_type, field_value))
|
|
87
|
+
end
|
|
88
|
+
IR::VariantLiteral.new(type:, arm_name: const_value.arm, fields:)
|
|
81
89
|
else
|
|
82
90
|
raise LoweringError.new("unsupported const value type #{const_value.class}", line: 0, column: 0, path: @ctx.current_analysis_path)
|
|
83
91
|
end
|
|
@@ -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
|
|
|
@@ -270,10 +270,19 @@ module MilkTea
|
|
|
270
270
|
|
|
271
271
|
if expected_type.is_a?(Types::Primitive) && expected_type.integer? &&
|
|
272
272
|
value_fits_integer_type?(expression.value, expected_type)
|
|
273
|
-
expected_type
|
|
274
|
-
else
|
|
275
|
-
@ctx.types.fetch("int")
|
|
273
|
+
return expected_type
|
|
276
274
|
end
|
|
275
|
+
|
|
276
|
+
# A bare literal (no suffix, no fitting expected type) picks the
|
|
277
|
+
# narrowest fixed-width type that holds its value: int -> long -> ulong.
|
|
278
|
+
# Falling through to int unconditionally emitted overflowing C
|
|
279
|
+
# (e.g. `int32_t x = 2147483648;`).
|
|
280
|
+
["int", "long", "ulong"].each do |name|
|
|
281
|
+
candidate = @ctx.types.fetch(name)
|
|
282
|
+
return candidate if value_fits_integer_type?(expression.value, candidate)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
raise_sema_error("integer literal #{expression.value} does not fit in any integer type", expression)
|
|
277
286
|
end
|
|
278
287
|
|
|
279
288
|
INTEGER_SUFFIX_TYPES = {
|
|
@@ -300,11 +309,23 @@ module MilkTea
|
|
|
300
309
|
@ctx.types.fetch("double")
|
|
301
310
|
elsif expected_type.is_a?(Types::Primitive) && expected_type.float?
|
|
302
311
|
expected_type
|
|
303
|
-
|
|
312
|
+
elsif float_literal_fits_in_float?(expression.value)
|
|
304
313
|
@ctx.types.fetch("float")
|
|
314
|
+
else
|
|
315
|
+
# A bare float literal beyond float32 range would silently overflow
|
|
316
|
+
# to infinity in C (`float x = 1e40`); promote to double instead.
|
|
317
|
+
@ctx.types.fetch("double")
|
|
305
318
|
end
|
|
306
319
|
end
|
|
307
320
|
|
|
321
|
+
FLOAT32_MAX_MAGNITUDE = 3.4028234663852886e+38
|
|
322
|
+
|
|
323
|
+
def float_literal_fits_in_float?(value)
|
|
324
|
+
return false unless value.is_a?(Numeric) && value.finite?
|
|
325
|
+
|
|
326
|
+
value.abs <= FLOAT32_MAX_MAGNITUDE
|
|
327
|
+
end
|
|
328
|
+
|
|
308
329
|
def infer_identifier(expression, scopes:, expected_type: nil)
|
|
309
330
|
binding = lookup_value(expression.name, scopes)
|
|
310
331
|
if binding
|
|
@@ -612,8 +633,8 @@ module MilkTea
|
|
|
612
633
|
|
|
613
634
|
left_type
|
|
614
635
|
when "+", "-", "*", "/"
|
|
615
|
-
if expression.operator == "+" && (
|
|
616
|
-
|
|
636
|
+
if expression.operator == "+" && left_type == @ctx.types.fetch("str") && right_type == @ctx.types.fetch("str")
|
|
637
|
+
return left_type
|
|
617
638
|
end
|
|
618
639
|
|
|
619
640
|
pointer_result = pointer_arithmetic_result(expression.operator, left_type, right_type)
|
|
@@ -662,8 +683,16 @@ module MilkTea
|
|
|
662
683
|
when "==", "!="
|
|
663
684
|
unless c_natively_equality_comparable_type?(left_type) && c_natively_equality_comparable_type?(right_type)
|
|
664
685
|
bad_type = c_natively_equality_comparable_type?(right_type) ? left_type : right_type
|
|
665
|
-
if
|
|
666
|
-
raise_sema_error("operator #{expression.operator} is not supported for
|
|
686
|
+
if bad_type.is_a?(Types::Union)
|
|
687
|
+
raise_sema_error("operator #{expression.operator} is not supported for union type #{bad_type}; use equal[#{bad_type}](...) instead")
|
|
688
|
+
elsif bad_type.is_a?(Types::Variant)
|
|
689
|
+
field = first_non_equality_comparable_variant_field(bad_type)
|
|
690
|
+
field_hint = field ? " (arm '#{field[0]}' field '#{field[1]}' of type #{field[2]} is not equality-comparable)" : ""
|
|
691
|
+
raise_sema_error("operator #{expression.operator} is not supported for variant type #{bad_type}#{field_hint}; use equal[#{bad_type}](...) instead")
|
|
692
|
+
elsif struct_instance_type?(bad_type) && !bad_type.is_a?(Types::Variant)
|
|
693
|
+
field = first_non_equality_comparable_field(bad_type)
|
|
694
|
+
field_hint = field ? " (field '#{field.first}' of type #{field.last} is not equality-comparable)" : ""
|
|
695
|
+
raise_sema_error("operator #{expression.operator} is not supported for struct type #{bad_type}#{field_hint}; use equal[#{bad_type}](...) instead")
|
|
667
696
|
else
|
|
668
697
|
raise_sema_error("operator #{expression.operator} is not supported for type #{bad_type}")
|
|
669
698
|
end
|
|
@@ -902,23 +902,65 @@ module MilkTea
|
|
|
902
902
|
type.is_a?(Types::Struct) || type.is_a?(Types::Variant)
|
|
903
903
|
end
|
|
904
904
|
|
|
905
|
-
def c_natively_equality_comparable_type?(type)
|
|
905
|
+
def c_natively_equality_comparable_type?(type, visiting = nil)
|
|
906
906
|
return true if type.is_a?(Types::Primitive)
|
|
907
907
|
return true if type.is_a?(Types::EnumBase)
|
|
908
908
|
return true if type.is_a?(Types::Opaque)
|
|
909
|
-
return true if type.is_a?(Types::Nullable)
|
|
909
|
+
return true if type.is_a?(Types::Nullable) && equality_comparable_field_type?(type.base, visiting)
|
|
910
910
|
return true if type.is_a?(Types::Null)
|
|
911
911
|
return true if type.is_a?(Types::Function)
|
|
912
912
|
return true if type.is_a?(Types::Error)
|
|
913
913
|
return true if type.is_a?(Types::StringView)
|
|
914
914
|
return true if pointer_type?(type)
|
|
915
915
|
return true if ref_type?(type)
|
|
916
|
-
return true if type.is_a?(Types::Variant)
|
|
916
|
+
return true if type.is_a?(Types::Variant) && equality_comparable_variant_type?(type, visiting)
|
|
917
917
|
return true if type.is_a?(Types::VariantArmPayload)
|
|
918
|
+
return true if equality_comparable_struct_type?(type, visiting)
|
|
919
|
+
|
|
920
|
+
false
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
# A struct value is equality-comparable with ==/!= when every field is
|
|
924
|
+
# itself equality-comparable. Untagged unions are excluded because their
|
|
925
|
+
# active-field comparison is ambiguous.
|
|
926
|
+
def equality_comparable_struct_type?(type, visiting = nil)
|
|
927
|
+
type.is_a?(Types::Struct) && !type.is_a?(Types::Union) &&
|
|
928
|
+
type.fields.all? { |_name, field_type| equality_comparable_field_type?(field_type, visiting) }
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
def equality_comparable_field_type?(type, visiting = nil)
|
|
932
|
+
return true if c_natively_equality_comparable_type?(type, visiting)
|
|
933
|
+
return true if array_type?(type) && equality_comparable_field_type?(array_element_type(type), visiting)
|
|
918
934
|
|
|
919
935
|
false
|
|
920
936
|
end
|
|
921
937
|
|
|
938
|
+
# A variant is equality-comparable when every arm payload field is itself
|
|
939
|
+
# equality-comparable. A variant that reaches itself through value fields
|
|
940
|
+
# (a recursive/cyclic variant) is comparable too: the C backend embeds
|
|
941
|
+
# cyclic fields as pointers, and those fields compare by pointer identity.
|
|
942
|
+
def equality_comparable_variant_type?(type, visiting = nil)
|
|
943
|
+
return true if visiting&.key?(type)
|
|
944
|
+
|
|
945
|
+
visiting = (visiting || {}).merge(type => true)
|
|
946
|
+
type.arm_names.all? do |arm_name|
|
|
947
|
+
(type.arm(arm_name) || {}).all? { |_field_name, field_type| equality_comparable_field_type?(field_type, visiting) }
|
|
948
|
+
end
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
def first_non_equality_comparable_field(struct_type)
|
|
952
|
+
struct_type.fields.find { |_name, field_type| !equality_comparable_field_type?(field_type) }
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
def first_non_equality_comparable_variant_field(variant_type)
|
|
956
|
+
variant_type.arm_names.each do |arm_name|
|
|
957
|
+
(variant_type.arm(arm_name) || {}).each do |field_name, field_type|
|
|
958
|
+
return [arm_name, field_name, field_type] unless equality_comparable_field_type?(field_type)
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
nil
|
|
962
|
+
end
|
|
963
|
+
|
|
922
964
|
def collection_loop_type(type)
|
|
923
965
|
super
|
|
924
966
|
end
|
|
@@ -301,7 +301,12 @@ module MilkTea
|
|
|
301
301
|
end
|
|
302
302
|
|
|
303
303
|
if (receiver_type = resolve_type_expression(member_access_expression.receiver))
|
|
304
|
-
|
|
304
|
+
if receiver_type.is_a?(Types::EnumBase)
|
|
305
|
+
next resolve_enum_member_const_value(receiver_type, member_access_expression.member)
|
|
306
|
+
end
|
|
307
|
+
if receiver_type.is_a?(Types::Variant)
|
|
308
|
+
next CompileTime::VariantValue.new(arm: member_access_expression.member, fields: {})
|
|
309
|
+
end
|
|
305
310
|
end
|
|
306
311
|
|
|
307
312
|
next unless member_access_expression.receiver.is_a?(AST::Identifier)
|
|
@@ -321,6 +326,31 @@ module MilkTea
|
|
|
321
326
|
|
|
322
327
|
def evaluate_compile_time_call(expression, scopes: nil)
|
|
323
328
|
case expression.callee
|
|
329
|
+
when AST::MemberAccess
|
|
330
|
+
if (receiver_type = resolve_type_expression(expression.callee.receiver)) && receiver_type.is_a?(Types::Variant)
|
|
331
|
+
arm_name = expression.callee.member
|
|
332
|
+
fields = {}
|
|
333
|
+
expression.arguments.each do |argument|
|
|
334
|
+
val = CompileTime.evaluate(argument.value, resolve_identifier: lambda { |id|
|
|
335
|
+
if scopes
|
|
336
|
+
binding = lookup_value(id.name, scopes)
|
|
337
|
+
return binding.const_value unless binding&.const_value.nil?
|
|
338
|
+
end
|
|
339
|
+
resolve_current_module_const_value(id.name)
|
|
340
|
+
}, resolve_member_access: lambda { |ma|
|
|
341
|
+
if (member_receiver_type = resolve_type_expression(ma.receiver))
|
|
342
|
+
next resolve_enum_member_const_value(member_receiver_type, ma.member) if member_receiver_type.is_a?(Types::EnumBase)
|
|
343
|
+
next CompileTime::VariantValue.new(arm: ma.member, fields: {}) if member_receiver_type.is_a?(Types::Variant)
|
|
344
|
+
end
|
|
345
|
+
nil
|
|
346
|
+
}, resolve_call: lambda { |inner_call|
|
|
347
|
+
evaluate_compile_time_call(inner_call, scopes:)
|
|
348
|
+
})
|
|
349
|
+
return nil unless val
|
|
350
|
+
fields[argument.name] = val
|
|
351
|
+
end
|
|
352
|
+
return CompileTime::VariantValue.new(arm: arm_name, fields: fields)
|
|
353
|
+
end
|
|
324
354
|
when AST::Identifier
|
|
325
355
|
if (struct_type = @ctx.types[expression.callee.name]) && struct_type.is_a?(Types::Struct)
|
|
326
356
|
fields = {}
|
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.
|
|
4
|
+
version: 0.3.31
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Long (Teefan) Tran
|
|
@@ -149,7 +149,6 @@ files:
|
|
|
149
149
|
- docs/index.html
|
|
150
150
|
- docs/language-design.md
|
|
151
151
|
- docs/language-manual.md
|
|
152
|
-
- docs/self-hosted-compiler-plan.md
|
|
153
152
|
- lib/milk_tea.rb
|
|
154
153
|
- lib/milk_tea/base.rb
|
|
155
154
|
- lib/milk_tea/bindings.rb
|
|
@@ -625,7 +624,7 @@ metadata:
|
|
|
625
624
|
homepage_uri: https://teefan.github.io/mt-lang/
|
|
626
625
|
source_code_uri: https://github.com/teefan/mt-lang
|
|
627
626
|
post_install_message: |
|
|
628
|
-
Milk Tea 0.3.
|
|
627
|
+
Milk Tea 0.3.31 installed!
|
|
629
628
|
|
|
630
629
|
System requirements:
|
|
631
630
|
- A C compiler (gcc or clang) must be available on PATH
|