mt-lang 0.3.43 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6842b1cfb6eac5181fdbcd31cd90dbb9f436bb0f3c7b0cc25d49a8c1f8e13a8b
4
- data.tar.gz: 4e97091f3da86a115656c6ec7d9bb10fe5cd65360bd59a203fc6595111285aff
3
+ metadata.gz: 990a1e0ede945713a31152caccc4a7de798dc6ead6078d46d5a376f19054a52a
4
+ data.tar.gz: 97d7877587ca50ab673212d1d0d80d6d60ad8f97a74c439965feb32c2f5a4e37
5
5
  SHA512:
6
- metadata.gz: 71630831d0eceda79fefdd93b07a9f83b48c3ef3b0cd879c5c7db8b064e459ef321d36b49a2e0d46e8ba20f134fffc65039cdf455d3cc47638ea031c3edddd83
7
- data.tar.gz: b7fccbe3f6547fcb7e17c07b9002d278965934a0f0ec68d1b6c79a1ce5b48d807186633a9e57b75e34c8671413ed984a18823a575719ba1f8af4e3777c3309f3
6
+ metadata.gz: c04b4d611724c8b4f42ae14929b3d8d98b4aa4da5157f7c3ee765afb6356be643b6ada8c96ed45a8e61103e71792c7855c25d87f65865b9609cdb0cc85544507
7
+ data.tar.gz: 82416c2665d49928f0d6dd5e9b8f225177f9df0f782e973339545a6375cc673e082bccc3bbb6126cbdc50875dc6fa29880b5e23b8e3ccd458934b32f5d2f6718
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.43"
6
+ VERSION = "0.4.0"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -120,8 +120,8 @@ module MilkTea
120
120
  FunctionDef = Data.define(:name, :type_params, :params, :return_type, :body, :visibility, :async, :const, :attributes, :line, :column) do
121
121
  def initialize(name:, type_params:, params:, return_type:, body:, visibility:, async:, const: false, attributes: [], line: nil, column: nil) = super
122
122
  end
123
- MethodDef = Data.define(:name, :type_params, :params, :return_type, :body, :kind, :visibility, :async, :attributes, :line, :column) do
124
- def initialize(name:, type_params:, params:, return_type:, body:, kind:, visibility:, async:, attributes: [], line: nil, column: nil) = super
123
+ MethodDef = Data.define(:name, :type_params, :params, :return_type, :body, :kind, :visibility, :async, :const, :attributes, :line, :column) do
124
+ def initialize(name:, type_params:, params:, return_type:, body:, kind:, visibility:, async:, const: false, attributes: [], line: nil, column: nil) = super
125
125
  end
126
126
  ExternFunctionDecl = Data.define(:name, :type_params, :params, :return_type, :variadic, :attributes, :line, :column, :mapping) do
127
127
  def initialize(name:, type_params:, params:, return_type:, variadic:, attributes: [], line: nil, column: nil, mapping: nil) = super
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module CompileTime
5
+ # Compile-time folding for const-method calls and the resolution helpers
6
+ # behind them. Included by both the semantic checker and the lowering
7
+ # engine so the folding behavior lives in one place instead of two
8
+ # near-identical copies. Each includer supplies a small set of `comptime_*`
9
+ # adapters for the phase-specific lookups (ctx maps, scope/env evaluation,
10
+ # and the BlockContext factory).
11
+ module MethodFolding
12
+ def comptime_methods_map_for_binding(type, keys)
13
+ dispatch_type = type.respond_to?(:definition) ? type.definition : type
14
+ method_map = comptime_methods_map.fetch(dispatch_type, nil)
15
+ method_map ||= comptime_methods_map_by_to_s(dispatch_type)
16
+ return nil unless method_map
17
+
18
+ keys.each do |key|
19
+ binding = method_map[key]
20
+ next unless binding
21
+
22
+ ast = binding.ast
23
+ next unless ast.respond_to?(:const) && ast.const && ast.body
24
+ next unless binding.type_params.empty?
25
+
26
+ return binding
27
+ end
28
+ nil
29
+ end
30
+
31
+ def comptime_method_binding(member_access, ctx)
32
+ member = member_access.member
33
+ keys = ["static:#{member}", member]
34
+
35
+ if (type = resolve_type_expression(member_access.receiver)) &&
36
+ (binding = comptime_methods_map_for_binding(type, keys))
37
+ return binding
38
+ end
39
+
40
+ receiver_type = comptime_receiver_type(member_access.receiver, ctx)
41
+ return nil unless receiver_type
42
+
43
+ comptime_methods_map_for_binding(receiver_type, keys)
44
+ end
45
+
46
+ def comptime_method_binding_for_receiver(receiver_type, member)
47
+ comptime_methods_map_for_binding(receiver_type, ["static:#{member}", member])
48
+ end
49
+
50
+ def comptime_receiver_type(receiver, ctx)
51
+ case receiver
52
+ when AST::Identifier
53
+ type = comptime_scoped_value_type(receiver.name, ctx)
54
+ return type if type
55
+
56
+ comptime_value_type(receiver.name)
57
+ when AST::MemberAccess
58
+ return nil unless receiver.receiver.is_a?(AST::Identifier)
59
+
60
+ comptime_imported_value_type(receiver.receiver.name, receiver.member)
61
+ when AST::Call, AST::Specialization
62
+ comptime_call_return_type(receiver, ctx)
63
+ end
64
+ end
65
+
66
+ def comptime_member_receiver_value(receiver, ctx)
67
+ return nil if resolve_type_expression(receiver)
68
+
69
+ comptime_eval(receiver, ctx)
70
+ end
71
+
72
+ def comptime_call_return_type(call_expr, ctx)
73
+ case call_expr.callee
74
+ when AST::MemberAccess
75
+ binding = comptime_method_binding(call_expr.callee, ctx)
76
+ return binding.body_return_type if binding
77
+ when AST::Identifier
78
+ func = comptime_const_function(call_expr.callee.name)
79
+ return func.body_return_type if comptime_const_function_return?(func)
80
+ when AST::Specialization
81
+ callee_name = call_expr.callee.callee.is_a?(AST::Identifier) ? call_expr.callee.callee.name : nil
82
+ if callee_name
83
+ func = comptime_const_function(callee_name)
84
+ return func.body_return_type if comptime_const_function_return?(func)
85
+ end
86
+ end
87
+ nil
88
+ end
89
+
90
+ def comptime_expression_type(expression, scopes:)
91
+ case expression
92
+ when AST::Call, AST::Specialization
93
+ comptime_call_return_type(expression, scopes)
94
+ end
95
+ end
96
+
97
+ def comptime_struct_field_names(type_name)
98
+ parts = type_name.is_a?(Array) ? type_name.map(&:to_s) : [type_name.to_s]
99
+ return nil if parts.empty?
100
+
101
+ type = resolve_type_expression(::MilkTea::AST.build_chain_from_parts(parts))
102
+ return nil unless type
103
+ return nil unless type.respond_to?(:fields) && type.fields
104
+
105
+ type.fields.keys
106
+ end
107
+
108
+ def comptime_folded_value?(value)
109
+ return false if value.nil?
110
+ return false if value.is_a?(Types::Base)
111
+
112
+ true
113
+ end
114
+
115
+ def comptime_const_function_return?(func)
116
+ func && func.respond_to?(:ast) && func.ast.respond_to?(:const) && func.ast.const
117
+ end
118
+
119
+ def comptime_const_method_body(binding, arguments, scopes:, receiver_value:)
120
+ method = binding.ast
121
+ return nil unless method.respond_to?(:body) && method.body
122
+ return nil if binding.type_params.any?
123
+ return nil unless method.params.length == arguments.length
124
+
125
+ initial_vars = {}
126
+ if binding.type.receiver_type
127
+ return nil unless comptime_folded_value?(receiver_value)
128
+
129
+ initial_vars["this"] = receiver_value
130
+ end
131
+
132
+ method.params.each_with_index do |param, idx|
133
+ arg_value = comptime_eval(arguments[idx].value, scopes)
134
+ return nil unless arg_value
135
+
136
+ initial_vars[param.name] = arg_value
137
+ end
138
+
139
+ variable_types = binding.body_params.each_with_object({}) do |param, acc|
140
+ acc[param.name] = param.type
141
+ end
142
+ block_context = comptime_block_context(initial_vars, variable_types)
143
+ result = block_context.evaluate_block(method.body, scopes: nil)
144
+ result.is_a?(CompileTime::ReturnOutcome) ? result.value : result
145
+ rescue CompileTime::Error => e
146
+ comptime_raise_error(e)
147
+ end
148
+
149
+ def comptime_imported_value_type(import_name, member)
150
+ imported_module = @ctx.imports.fetch(import_name, nil)
151
+ return nil unless imported_module
152
+
153
+ imported_module.values[member]&.type
154
+ end
155
+ end
156
+ end
157
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "types/layout"
4
+ require_relative "compile_time/method_folding"
4
5
 
5
6
  module MilkTea
6
7
  module CompileTime
@@ -9,6 +10,10 @@ module MilkTea
9
10
  # Carries the value of a `return` statement out of the block evaluator as
10
11
  # an ordinary value instead of an exception; callers unwrap it when present.
11
12
  ReturnOutcome = Data.define(:value)
13
+ # Signals that a `break` was executed inside a compile-time loop body.
14
+ BreakOutcome = Data.define(:value)
15
+ # Signals that a `continue` was executed inside a compile-time loop body.
16
+ ContinueOutcome = Data.define(:value)
12
17
 
13
18
  class Error < StandardError
14
19
  def code
@@ -93,7 +98,7 @@ module MilkTea
93
98
  start_val = evaluate(expression.start_expr)
94
99
  end_val = evaluate(expression.end_expr)
95
100
  start_val.is_a?(Integer) && end_val.is_a?(Integer) ? (start_val...end_val).to_a : nil
96
- when AST::IntegerLiteral, AST::FloatLiteral, AST::BooleanLiteral
101
+ when AST::IntegerLiteral, AST::FloatLiteral, AST::BooleanLiteral, AST::CharLiteral
97
102
  expression.value
98
103
  when AST::StringLiteral
99
104
  expression.value
@@ -133,11 +138,98 @@ module MilkTea
133
138
  return unless CompileTime.boolean_value?(condition)
134
139
 
135
140
  evaluate(condition ? expression.then_expression : expression.else_expression)
141
+ when AST::MatchExpr
142
+ evaluate_match_expression(expression)
143
+ when AST::IndexAccess
144
+ evaluate_index_access(expression)
145
+ when AST::PrefixCast
146
+ evaluate_prefix_cast(expression)
136
147
  else
137
148
  nil
138
149
  end
139
150
  end
140
151
 
152
+ def evaluate_prefix_cast(expression)
153
+ operand = evaluate(expression.expression)
154
+ operand = 1 if operand == true
155
+ operand = 0 if operand == false
156
+ return nil unless operand.is_a?(Numeric)
157
+
158
+ target_type = begin
159
+ @resolve_type_ref&.call(expression.target_type)
160
+ rescue SemanticError
161
+ nil
162
+ end
163
+ return nil unless target_type.is_a?(Types::Primitive)
164
+
165
+ if target_type.boolean?
166
+ return operand != 0
167
+ end
168
+
169
+ if target_type.integer?
170
+ return nil unless target_type.integer_width
171
+
172
+ value = operand.is_a?(Float) ? operand.truncate : operand
173
+ return nil unless value.is_a?(Integer)
174
+
175
+ return wrap_integer(value, target_type.integer_width, target_type.signed_integer?)
176
+ end
177
+
178
+ return nil unless target_type.float?
179
+
180
+ value = operand.to_f
181
+ target_type.name == "float" ? float32_round(value) : value
182
+ end
183
+
184
+ def wrap_integer(value, width, signed)
185
+ mask = (1 << width) - 1
186
+ wrapped = value & mask
187
+ return wrapped unless signed
188
+
189
+ half = 1 << (width - 1)
190
+ wrapped >= half ? wrapped - (1 << width) : wrapped
191
+ end
192
+
193
+ def float32_round(value)
194
+ [value].pack("f").unpack1("f")
195
+ end
196
+
197
+ def evaluate_index_access(expression)
198
+ receiver = evaluate(expression.receiver)
199
+ return nil if receiver.nil?
200
+
201
+ if expression.index.is_a?(AST::RangeExpr)
202
+ start_val = evaluate(expression.index.start_expr)
203
+ end_val = evaluate(expression.index.end_expr)
204
+ return nil unless start_val.is_a?(Integer) && end_val.is_a?(Integer)
205
+
206
+ return receiver[start_val...end_val] if receiver.is_a?(Array) || receiver.is_a?(String)
207
+
208
+ return nil
209
+ end
210
+
211
+ index = evaluate(expression.index)
212
+ return nil unless index.is_a?(Integer)
213
+ return receiver[index] if receiver.is_a?(Array)
214
+ return receiver.getbyte(index) if receiver.is_a?(String)
215
+
216
+ nil
217
+ end
218
+
219
+ def evaluate_match_expression(expression)
220
+ scrutinee = evaluate(expression.expression)
221
+ return nil unless scrutinee
222
+
223
+ expression.arms.each do |arm|
224
+ wildcard = arm.pattern.is_a?(AST::Identifier) && arm.pattern.name == "_"
225
+ if wildcard || CompileTime.equality_result(scrutinee, evaluate(arm.pattern)) == true
226
+ return evaluate(arm.value)
227
+ end
228
+ end
229
+
230
+ nil
231
+ end
232
+
141
233
  def resolve_layout_type(type_ref)
142
234
  result = begin
143
235
  @resolve_type_ref&.call(type_ref)
@@ -207,7 +299,13 @@ module MilkTea
207
299
  result = CompileTime.equality_result(left, right)
208
300
  result.nil? ? nil : !result
209
301
  when "+"
210
- left.is_a?(Numeric) && right.is_a?(Numeric) ? left + right : nil
302
+ if left.is_a?(String) && right.is_a?(String)
303
+ left + right
304
+ elsif left.is_a?(Numeric) && right.is_a?(Numeric)
305
+ left + right
306
+ else
307
+ nil
308
+ end
211
309
  when "-"
212
310
  left.is_a?(Numeric) && right.is_a?(Numeric) ? left - right : nil
213
311
  when "*"
@@ -251,9 +349,10 @@ module MilkTea
251
349
  class BlockContext
252
350
  attr_reader :checker
253
351
 
254
- def initialize(checker, initial_variables: nil)
352
+ def initialize(checker, initial_variables: nil, variable_types: nil)
255
353
  @checker = checker
256
354
  @variables = initial_variables || {}
355
+ @variable_types = variable_types || {}
257
356
  end
258
357
 
259
358
  def evaluate_block(statements, scopes: nil)
@@ -261,7 +360,7 @@ module MilkTea
261
360
 
262
361
  statements.each do |statement|
263
362
  outcome = evaluate_statement(statement, scopes:)
264
- return outcome if outcome.is_a?(ReturnOutcome)
363
+ return outcome if control_outcome?(outcome)
265
364
 
266
365
  result = outcome
267
366
  end
@@ -269,6 +368,10 @@ module MilkTea
269
368
  result
270
369
  end
271
370
 
371
+ def control_outcome?(outcome)
372
+ outcome.is_a?(ReturnOutcome) || outcome.is_a?(BreakOutcome) || outcome.is_a?(ContinueOutcome)
373
+ end
374
+
272
375
  def evaluate_statement(statement, scopes:)
273
376
  case statement
274
377
  when AST::LocalDecl
@@ -288,9 +391,13 @@ module MilkTea
288
391
  evaluate_if(statement, scopes:)
289
392
  when AST::ExpressionStmt
290
393
  evaluate_expression(statement.expression, scopes:)
291
- when AST::PassStmt, AST::BreakStmt, AST::ContinueStmt
394
+ when AST::PassStmt
292
395
  # no-op at compile time
293
396
  nil
397
+ when AST::BreakStmt
398
+ BreakOutcome.new(nil)
399
+ when AST::ContinueStmt
400
+ ContinueOutcome.new(nil)
294
401
  when AST::EmitStmt
295
402
  # emitted declarations are collected during lowering
296
403
  nil
@@ -312,9 +419,25 @@ module MilkTea
312
419
  @checker.evaluate_compile_time_const_value(id_expr, scopes:)
313
420
  },
314
421
  resolve_member_access: ->(ma_expr) {
422
+ if ma_expr.receiver.is_a?(AST::Identifier) && @variables.key?(ma_expr.receiver.name)
423
+ value = @variables[ma_expr.receiver.name]
424
+ case value
425
+ when Hash
426
+ return value[ma_expr.member] if value.key?(ma_expr.member)
427
+ when Array
428
+ if ma_expr.member =~ /\A_(\d+)\z/
429
+ return value[Regexp.last_match(1).to_i]
430
+ end
431
+ return value.length if ma_expr.member == "len"
432
+ when String
433
+ return value.length if ma_expr.member == "len"
434
+ end
435
+ end
436
+
315
437
  @checker.evaluate_compile_time_const_value(ma_expr, scopes:)
316
438
  },
317
439
  resolve_call: ->(call_expr) { resolve_compile_time_call(call_expr, scopes:) },
440
+ resolve_type_ref: ->(type_ref) { @checker.resolve_type_ref(type_ref) },
318
441
  )
319
442
  end
320
443
  end
@@ -323,26 +446,153 @@ module MilkTea
323
446
  return nil unless decl.value
324
447
 
325
448
  value = evaluate_expression(decl.value, scopes:)
449
+ return value unless value
450
+
451
+ if decl.else_body
452
+ return evaluate_guard_local_decl(decl, value, scopes:)
453
+ end
454
+
455
+ if decl.destructure_bindings&.any?
456
+ return evaluate_destructure_local_decl(decl, value)
457
+ end
458
+
459
+ @variables[decl.name] = value
460
+ @variable_types[decl.name] = compile_time_decl_type(decl.value, scopes:) unless @variable_types.key?(decl.name)
461
+ value
462
+ end
463
+
464
+ def evaluate_guard_local_decl(decl, value, scopes:)
465
+ if value.is_a?(CompileTime::VariantValue)
466
+ case value.arm
467
+ when "some", "success"
468
+ @variables[decl.name] = value.fields["value"]
469
+ @variable_types[decl.name] = compile_time_decl_type(decl.value, scopes:) unless @variable_types.key?(decl.name)
470
+ return value
471
+ when "none", "failure"
472
+ @variables[decl.else_binding.name] = value.fields["error"] if decl.else_binding
473
+ return evaluate_block(decl.else_body, scopes:)
474
+ end
475
+ return nil
476
+ end
477
+
326
478
  @variables[decl.name] = value
479
+ @variable_types[decl.name] = compile_time_decl_type(decl.value, scopes:) unless @variable_types.key?(decl.name)
327
480
  value
328
481
  end
329
482
 
483
+ def evaluate_destructure_local_decl(decl, value)
484
+ if value.is_a?(Array)
485
+ decl.destructure_bindings.each_with_index do |name, idx|
486
+ next if name == "_"
487
+
488
+ @variables[name] = value[idx]
489
+ end
490
+ return value
491
+ end
492
+
493
+ if value.is_a?(Hash)
494
+ field_names = destructure_field_names(decl.destructure_type_name)
495
+ decl.destructure_bindings.each_with_index do |name, idx|
496
+ next if name == "_"
497
+
498
+ field_name = field_names&.[](idx) || name
499
+ @variables[name] = value[field_name]
500
+ end
501
+ return value
502
+ end
503
+
504
+ nil
505
+ end
506
+
507
+ def destructure_field_names(type_name)
508
+ return nil unless type_name
509
+ return nil unless @checker.respond_to?(:comptime_struct_field_names)
510
+
511
+ @checker.comptime_struct_field_names(type_name)
512
+ rescue StandardError
513
+ nil
514
+ end
515
+
516
+ def compile_time_decl_type(expression, scopes:)
517
+ return nil unless @checker.respond_to?(:comptime_expression_type)
518
+
519
+ @checker.comptime_expression_type(expression, scopes:)
520
+ rescue StandardError
521
+ nil
522
+ end
523
+
330
524
  def evaluate_assignment(assignment, scopes:)
331
525
  value = evaluate_expression(assignment.value, scopes:)
526
+ return nil unless value
527
+
332
528
  case assignment.target
333
529
  when AST::Identifier
530
+ return nil unless @variables.key?(assignment.target.name)
531
+
334
532
  if assignment.operator != "="
335
533
  current = @variables[assignment.target.name]
336
534
  value = apply_compile_time_binary(assignment.operator.chomp("="), current, value)
337
535
  end
338
536
  @variables[assignment.target.name] = value
537
+ when AST::MemberAccess
538
+ return nil unless evaluate_member_assignment(assignment, value, scopes:)
539
+ when AST::IndexAccess
540
+ return nil unless evaluate_index_assignment(assignment, value, scopes:)
339
541
  end
340
542
  value
341
543
  end
342
544
 
545
+ def evaluate_member_assignment(assignment, value, scopes:)
546
+ target = assignment.target
547
+ return nil unless target.receiver.is_a?(AST::Identifier)
548
+ return nil unless @variables.key?(target.receiver.name)
549
+
550
+ receiver = @variables[target.receiver.name]
551
+ return nil unless receiver.is_a?(Hash)
552
+
553
+ if assignment.operator != "="
554
+ current = receiver[target.member]
555
+ value = apply_compile_time_binary(assignment.operator.chomp("="), current, value)
556
+ return nil unless value
557
+ end
558
+
559
+ updated = receiver.dup
560
+ updated[target.member] = value
561
+ @variables[target.receiver.name] = updated
562
+ end
563
+
564
+ def evaluate_index_assignment(assignment, value, scopes:)
565
+ target = assignment.target
566
+ return nil unless target.receiver.is_a?(AST::Identifier)
567
+ return nil unless @variables.key?(target.receiver.name)
568
+
569
+ receiver = @variables[target.receiver.name]
570
+ return nil unless receiver.is_a?(Array)
571
+
572
+ index = evaluate_expression(target.index, scopes:)
573
+ return nil unless index.is_a?(Integer)
574
+
575
+ if assignment.operator != "="
576
+ current = receiver[index]
577
+ value = apply_compile_time_binary(assignment.operator.chomp("="), current, value)
578
+ return nil unless value
579
+ end
580
+
581
+ updated = receiver.dup
582
+ updated[index] = value
583
+ @variables[target.receiver.name] = updated
584
+ end
585
+
343
586
  def apply_compile_time_binary(operator, left, right)
344
587
  case operator
345
- when "+" then left.is_a?(Numeric) && right.is_a?(Numeric) ? left + right : nil
588
+ when "+"
589
+ if left.is_a?(String) && right.is_a?(String)
590
+ left + right
591
+ elsif left.is_a?(Numeric) && right.is_a?(Numeric)
592
+ left + right
593
+ else
594
+ nil
595
+ end
346
596
  when "-" then left.is_a?(Numeric) && right.is_a?(Numeric) ? left - right : nil
347
597
  when "*" then left.is_a?(Numeric) && right.is_a?(Numeric) ? left * right : nil
348
598
  when "/" then left.is_a?(Numeric) && right.is_a?(Numeric) && !zero_numeric?(right) ? left / right : nil
@@ -371,7 +621,11 @@ module MilkTea
371
621
 
372
622
  statement.body.each do |body_stmt|
373
623
  outcome = evaluate_statement(body_stmt, scopes:)
374
- return outcome if outcome.is_a?(ReturnOutcome)
624
+ case outcome
625
+ when ReturnOutcome then return outcome
626
+ when BreakOutcome then return result
627
+ when ContinueOutcome then break
628
+ end
375
629
  end
376
630
  iterations += 1
377
631
  end
@@ -392,32 +646,35 @@ module MilkTea
392
646
  @variables[loop_var_name] = element
393
647
  statement.body.each do |body_stmt|
394
648
  outcome = evaluate_statement(body_stmt, scopes:)
395
- return outcome if outcome.is_a?(ReturnOutcome)
649
+ case outcome
650
+ when ReturnOutcome then return outcome
651
+ when BreakOutcome then return result
652
+ when ContinueOutcome then break
653
+ end
396
654
  end
397
655
  end
398
656
 
399
657
  result
400
658
  end
401
659
 
660
+ def run_body(body, scopes:, fallback: nil)
661
+ body.each do |body_stmt|
662
+ outcome = evaluate_statement(body_stmt, scopes:)
663
+ return outcome if control_outcome?(outcome)
664
+ end
665
+ fallback
666
+ end
667
+
402
668
  def evaluate_if(statement, scopes:)
403
669
  statement.branches.each do |branch|
404
670
  condition = evaluate_expression(branch.condition, scopes:)
405
- if CompileTime.boolean_value?(condition) && condition
406
- branch.body.each do |body_stmt|
407
- outcome = evaluate_statement(body_stmt, scopes:)
408
- return outcome if outcome.is_a?(ReturnOutcome)
409
- end
410
- return condition
411
- end
412
- end
671
+ next unless CompileTime.boolean_value?(condition) && condition
413
672
 
414
- if statement.else_body
415
- statement.else_body.each do |body_stmt|
416
- outcome = evaluate_statement(body_stmt, scopes:)
417
- return outcome if outcome.is_a?(ReturnOutcome)
418
- end
673
+ return run_body(branch.body, scopes:, fallback: condition)
419
674
  end
420
675
 
676
+ return run_body(statement.else_body, scopes:) if statement.else_body
677
+
421
678
  nil
422
679
  end
423
680
 
@@ -428,11 +685,7 @@ module MilkTea
428
685
  statement.arms.each do |arm|
429
686
  wildcard = arm.pattern.is_a?(AST::Identifier) && arm.pattern.name == "_"
430
687
  if wildcard || CompileTime.equality_result(scrutinee, evaluate_expression(arm.pattern, scopes:)) == true
431
- arm.body.each do |body_stmt|
432
- outcome = evaluate_statement(body_stmt, scopes:)
433
- return outcome if outcome.is_a?(ReturnOutcome)
434
- end
435
- return scrutinee
688
+ return run_body(arm.body, scopes:, fallback: scrutinee)
436
689
  end
437
690
  end
438
691
 
@@ -443,6 +696,9 @@ module MilkTea
443
696
  result = try_const_function_call(call_expr, scopes:)
444
697
  return result if result
445
698
 
699
+ result = try_const_method_call(call_expr, scopes:)
700
+ return result if result
701
+
446
702
  result = try_struct_constructor_call(call_expr, scopes:)
447
703
  return result if result
448
704
 
@@ -452,6 +708,27 @@ module MilkTea
452
708
  @checker.evaluate_compile_time_const_value(call_expr, scopes:)
453
709
  end
454
710
 
711
+ def try_const_method_call(call_expr, scopes:)
712
+ return unless call_expr.callee.is_a?(AST::MemberAccess)
713
+ return unless @checker.respond_to?(:comptime_method_binding_for_receiver)
714
+ return unless @checker.respond_to?(:comptime_const_method_body)
715
+
716
+ receiver = call_expr.callee.receiver
717
+ return unless receiver.is_a?(AST::Identifier)
718
+ return unless @variables.key?(receiver.name)
719
+
720
+ receiver_value = @variables[receiver.name]
721
+ return nil if receiver_value.nil? || receiver_value.is_a?(Types::Base)
722
+
723
+ receiver_type = @variable_types[receiver.name]
724
+ return nil unless receiver_type
725
+
726
+ binding = @checker.comptime_method_binding_for_receiver(receiver_type, call_expr.callee.member)
727
+ return nil unless binding
728
+
729
+ @checker.comptime_const_method_body(binding, call_expr.arguments, scopes:, receiver_value:)
730
+ end
731
+
455
732
  def try_const_function_call(call_expr, scopes:)
456
733
  return unless call_expr.callee.is_a?(AST::Identifier)
457
734
 
@@ -3,6 +3,7 @@
3
3
  module MilkTea
4
4
  module Lowering
5
5
  module Resolve
6
+ include CompileTime::MethodFolding
6
7
  PASS_THROUGH_BUILTINS = {
7
8
  "fatal" => :fatal,
8
9
  "ref_of" => :ref_of,
@@ -1558,7 +1559,7 @@ module MilkTea
1558
1559
  end
1559
1560
  resolve_current_module_const_value(identifier_expression.name)
1560
1561
  end,
1561
- resolve_member_access: lambda { |ma| nil },
1562
+ resolve_member_access: lambda { |ma| compile_time_const_value(ma, env:) },
1562
1563
  resolve_type_ref: lambda { |tr| resolve_type_ref(tr) },
1563
1564
  resolve_call: lambda { |ce| evaluate_compile_time_call(ce, env:) },
1564
1565
  ))
@@ -1573,6 +1574,15 @@ module MilkTea
1573
1574
  when "name" then next receiver_value.member_name
1574
1575
  when "value" then next receiver_value.member_value
1575
1576
  end
1577
+ when Hash
1578
+ next receiver_value[member_access_expression.member] if receiver_value.key?(member_access_expression.member)
1579
+ when Array
1580
+ if member_access_expression.member =~ /\A_(\d+)\z/
1581
+ next receiver_value[Regexp.last_match(1).to_i]
1582
+ end
1583
+ next receiver_value.length if member_access_expression.member == "len"
1584
+ when String
1585
+ next receiver_value.length if member_access_expression.member == "len"
1576
1586
  end
1577
1587
  end
1578
1588
 
@@ -1594,6 +1604,11 @@ module MilkTea
1594
1604
 
1595
1605
  def evaluate_compile_time_call(expression, env:)
1596
1606
  case expression.callee
1607
+ when AST::MemberAccess
1608
+ if (method_binding = comptime_method_binding(expression.callee, env))
1609
+ receiver_value = comptime_member_receiver_value(expression.callee.receiver, env)
1610
+ return comptime_const_method_body(method_binding, expression.arguments, scopes: nil, receiver_value:)
1611
+ end
1597
1612
  when AST::Identifier
1598
1613
  case expression.callee.name
1599
1614
  when "field_of"
@@ -1794,12 +1809,51 @@ module MilkTea
1794
1809
  initial_vars[param.name] = arg_value
1795
1810
  end
1796
1811
 
1812
+ variable_types = func.body_params.each_with_object({}) do |param, acc|
1813
+ acc[param.name] = param.type
1814
+ end
1797
1815
  evaluator = ConstFnLowerEvaluator.new(self)
1798
- ctx = CompileTime::BlockContext.new(evaluator, initial_variables: initial_vars)
1816
+ ctx = CompileTime::BlockContext.new(evaluator, initial_variables: initial_vars, variable_types: variable_types)
1799
1817
  result = ctx.evaluate_block(func.ast.body, scopes: nil)
1800
1818
  result.is_a?(CompileTime::ReturnOutcome) ? result.value : result
1801
1819
  end
1802
1820
 
1821
+ def comptime_methods_map
1822
+ @ctx.methods
1823
+ end
1824
+
1825
+ def comptime_methods_map_by_to_s(type)
1826
+ entry = @ctx.methods.find { |k, _| k.to_s == type.to_s }
1827
+ entry&.last
1828
+ end
1829
+
1830
+ def comptime_scoped_value_type(name, ctx)
1831
+ return nil unless ctx
1832
+
1833
+ binding = lookup_value(name, ctx)
1834
+ binding[:type] if binding && binding[:type]
1835
+ end
1836
+
1837
+ def comptime_value_type(name)
1838
+ @ctx.values[name]&.type
1839
+ end
1840
+
1841
+ def comptime_const_function(name)
1842
+ @ctx.functions[name]
1843
+ end
1844
+
1845
+ def comptime_eval(expression, _scopes)
1846
+ compile_time_const_value(expression, env: empty_env)
1847
+ end
1848
+
1849
+ def comptime_block_context(initial_vars, variable_types)
1850
+ ::MilkTea::CompileTime::BlockContext.new(ConstFnLowerEvaluator.new(self), initial_variables: initial_vars, variable_types: variable_types)
1851
+ end
1852
+
1853
+ def comptime_raise_error(error)
1854
+ raise error
1855
+ end
1856
+
1803
1857
  class ConstFnLowerEvaluator
1804
1858
  def initialize(lowerer)
1805
1859
  @lowerer = lowerer
@@ -1816,6 +1870,26 @@ module MilkTea
1816
1870
  def top_level_function(name)
1817
1871
  @lowerer.instance_variable_get(:@ctx).functions&.[](name)
1818
1872
  end
1873
+
1874
+ def resolve_type_ref(type_ref)
1875
+ @lowerer.resolve_type_ref(type_ref)
1876
+ end
1877
+
1878
+ def comptime_struct_field_names(type_name)
1879
+ @lowerer.comptime_struct_field_names(type_name)
1880
+ end
1881
+
1882
+ def comptime_method_binding_for_receiver(receiver_type, member)
1883
+ @lowerer.comptime_method_binding_for_receiver(receiver_type, member)
1884
+ end
1885
+
1886
+ def comptime_const_method_body(binding, arguments, scopes: nil, receiver_value: nil)
1887
+ @lowerer.comptime_const_method_body(binding, arguments, scopes:, receiver_value:)
1888
+ end
1889
+
1890
+ def comptime_expression_type(expression, scopes: nil)
1891
+ @lowerer.comptime_expression_type(expression, scopes:)
1892
+ end
1819
1893
  end
1820
1894
 
1821
1895
  def evaluate_reflection_target_argument(expression, env:)
@@ -459,6 +459,7 @@ module MilkTea
459
459
  match(:async)
460
460
  match(:editable) if check(:editable)
461
461
  match(:static) if check(:static)
462
+ match(:const) if check(:const)
462
463
  result = check(:function) && !check_next(:colon)
463
464
  @current = saved
464
465
  result
@@ -657,10 +658,10 @@ module MilkTea
657
658
  end
658
659
 
659
660
  def parse_method_def(attributes: [])
660
- visibility, _visibility_token, async, kind, line, name_token = parse_method_head
661
+ visibility, _visibility_token, async, kind, const, line, name_token = parse_method_head
661
662
  name = name_token.lexeme
662
663
  type_params, params, return_type, body = parse_callable_signature
663
- AST::MethodDef.new(name:, type_params:, params:, return_type:, body:, kind:, visibility:, async:, attributes:, line:, column: name_token.column)
664
+ AST::MethodDef.new(name:, type_params:, params:, return_type:, body:, kind:, visibility:, async:, const:, attributes:, line:, column: name_token.column)
664
665
  rescue ParseError => e
665
666
  raise unless @recovery_errors
666
667
 
@@ -668,11 +669,12 @@ module MilkTea
668
669
  name = name_token&.lexeme || "unknown"
669
670
  col = name_token&.column || 1
670
671
  advance until eof? || check(:function) || check(:dedent)
671
- AST::MethodDef.new(name:, type_params: [], params: [], return_type: nil, body: nil, kind: kind || :plain, visibility: visibility || :private, async: async || false, attributes:, line:, column: col)
672
+ AST::MethodDef.new(name:, type_params: [], params: [], return_type: nil, body: nil, kind: kind || :plain, visibility: visibility || :private, async: async || false, const: const || false, attributes:, line:, column: col)
672
673
  end
673
674
 
674
675
  def parse_interface_method_decl(attributes: [])
675
- visibility, visibility_token, async, kind, line, name_token = parse_method_head
676
+ visibility, visibility_token, async, kind, const, line, name_token = parse_method_head
677
+ raise error(name_token, "const is not allowed on interface methods") if const
676
678
  raise error(visibility_token, "public is not allowed on interface methods") if visibility == :public
677
679
 
678
680
  name = name_token.lexeme
@@ -689,6 +691,9 @@ module MilkTea
689
691
  visibility, visibility_token = parse_visibility
690
692
  async = match(:async)
691
693
  kind = parse_method_kind
694
+ const = match(:const)
695
+ raise error(previous, "editable methods cannot be const") if kind == :editable && const
696
+ raise error(previous, "async methods cannot be const") if async && const
692
697
  if check(:function)
693
698
  consume(:function, "expected function declaration")
694
699
  elsif check(:identifier)
@@ -699,7 +704,7 @@ module MilkTea
699
704
  end
700
705
  line = previous.line
701
706
  name_token = consume_name("expected function name")
702
- [visibility, visibility_token, async, kind, line, name_token]
707
+ [visibility, visibility_token, async, kind, const, line, name_token]
703
708
  end
704
709
 
705
710
  def parse_method_kind
@@ -298,14 +298,16 @@ module MilkTea
298
298
 
299
299
  def render_function_signature(function, prefix: "")
300
300
  signature_prefix = if function.is_a?(AST::MethodDef)
301
- case function.kind
302
- when :editable
303
- "editable function "
304
- when :static
305
- "static function "
306
- else
307
- "function "
308
- end
301
+ kind_prefix = case function.kind
302
+ when :editable
303
+ "editable "
304
+ when :static
305
+ "static "
306
+ else
307
+ ""
308
+ end
309
+ const_prefix = function.respond_to?(:const) && function.const ? "const " : ""
310
+ "#{kind_prefix}#{const_prefix}function "
309
311
  elsif function.is_a?(AST::ForeignFunctionDecl)
310
312
  "foreign function "
311
313
  else
@@ -298,6 +298,33 @@ module MilkTea
298
298
  current_type_params[identifier_expression.name] || @ctx.types[identifier_expression.name]
299
299
  end,
300
300
  resolve_member_access: lambda do |member_access_expression|
301
+ if (receiver_value = CompileTime.evaluate(
302
+ member_access_expression.receiver,
303
+ resolve_identifier: lambda do |identifier_expression|
304
+ if scopes
305
+ binding = lookup_value(identifier_expression.name, scopes)
306
+ return binding.const_value unless binding&.const_value.nil?
307
+ end
308
+
309
+ resolve_current_module_const_value(identifier_expression.name)
310
+ end,
311
+ resolve_member_access: lambda { |ma| evaluate_compile_time_const_value(ma, scopes:) },
312
+ resolve_type_ref: lambda { |tr| resolve_type_ref(tr) },
313
+ resolve_call: lambda { |ce| evaluate_compile_time_call(ce, scopes:) },
314
+ ))
315
+ case receiver_value
316
+ when Hash
317
+ next receiver_value[member_access_expression.member] if receiver_value.key?(member_access_expression.member)
318
+ when Array
319
+ if member_access_expression.member =~ /\A_(\d+)\z/
320
+ next receiver_value[Regexp.last_match(1).to_i]
321
+ end
322
+ next receiver_value.length if member_access_expression.member == "len"
323
+ when String
324
+ next receiver_value.length if member_access_expression.member == "len"
325
+ end
326
+ end
327
+
301
328
  if member_access_expression.receiver.is_a?(AST::Identifier) && scopes
302
329
  binding = lookup_value(member_access_expression.receiver.name, scopes)
303
330
  if binding && binding.const_value
@@ -392,6 +419,11 @@ module MilkTea
392
419
  end
393
420
  return CompileTime::VariantValue.new(arm: arm_name, fields: fields)
394
421
  end
422
+
423
+ if (method_binding = comptime_method_binding(expression.callee, scopes))
424
+ receiver_value = comptime_member_receiver_value(expression.callee.receiver, scopes)
425
+ return comptime_const_method_body(method_binding, expression.arguments, scopes:, receiver_value:)
426
+ end
395
427
  when AST::Identifier
396
428
  if (struct_type = @ctx.types[expression.callee.name]) && struct_type.is_a?(Types::Struct)
397
429
  fields = {}
@@ -587,7 +619,10 @@ module MilkTea
587
619
  initial_vars[param.name] = arg_value
588
620
  end
589
621
 
590
- ctx = CompileTime::BlockContext.new(self, initial_variables: initial_vars)
622
+ variable_types = func.body_params.each_with_object({}) do |param, acc|
623
+ acc[param.name] = param.type
624
+ end
625
+ ctx = CompileTime::BlockContext.new(self, initial_variables: initial_vars, variable_types: variable_types)
591
626
  result = ctx.evaluate_block(func.ast.body, scopes: nil)
592
627
  result.is_a?(CompileTime::ReturnOutcome) ? result.value : result
593
628
  rescue CompileTime::Error => e
@@ -613,6 +648,40 @@ module MilkTea
613
648
  end
614
649
  end
615
650
 
651
+ def comptime_methods_map
652
+ @ctx.methods
653
+ end
654
+
655
+ def comptime_methods_map_by_to_s(_type)
656
+ nil
657
+ end
658
+
659
+ def comptime_scoped_value_type(name, ctx)
660
+ return nil unless ctx
661
+
662
+ lookup_value(name, ctx)&.type
663
+ end
664
+
665
+ def comptime_value_type(name)
666
+ @ctx.top_level_values[name]&.type
667
+ end
668
+
669
+ def comptime_const_function(name)
670
+ @ctx.top_level_functions[name]
671
+ end
672
+
673
+ def comptime_eval(expression, scopes)
674
+ evaluate_compile_time_const_value(expression, scopes: scopes)
675
+ end
676
+
677
+ def comptime_block_context(initial_vars, variable_types)
678
+ ::MilkTea::CompileTime::BlockContext.new(self, initial_variables: initial_vars, variable_types: variable_types)
679
+ end
680
+
681
+ def comptime_raise_error(error)
682
+ raise_sema_error(error.message)
683
+ end
684
+
616
685
  def evaluate_has_attribute_call(arguments, scopes:)
617
686
  target = evaluate_reflection_target_argument(arguments.first.value, scopes:)
618
687
  binding = resolve_attribute_name_argument(arguments[1].value)
@@ -131,6 +131,7 @@ module MilkTea
131
131
 
132
132
  class Checker
133
133
  include Intrinsics
134
+ include CompileTime::MethodFolding
134
135
 
135
136
  attr_reader :ctx
136
137
 
@@ -222,8 +223,14 @@ module MilkTea
222
223
  when AST::WhenStmt
223
224
  body = when_chosen_body(decl)
224
225
  collect_emit_from_declarations(body) if body
225
- when AST::StructDecl, AST::ExtendingBlock
226
- # no emit in structs/extending blocks
226
+ when AST::StructDecl
227
+ # no emit in structs
228
+ when AST::ExtendingBlock
229
+ decl.methods.each do |method|
230
+ next unless method.respond_to?(:const) && method.const && method.body
231
+
232
+ collect_emit_from_statements(method.body)
233
+ end
227
234
  end
228
235
  end
229
236
  end
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.43
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -205,6 +205,7 @@ files:
205
205
  - lib/milk_tea/core/c_backend/type_declaration.rb
206
206
  - lib/milk_tea/core/c_backend/type_system.rb
207
207
  - lib/milk_tea/core/compile_time.rb
208
+ - lib/milk_tea/core/compile_time/method_folding.rb
208
209
  - lib/milk_tea/core/control_flow.rb
209
210
  - lib/milk_tea/core/control_flow/builder.rb
210
211
  - lib/milk_tea/core/control_flow/constant_propagation.rb
@@ -628,7 +629,7 @@ metadata:
628
629
  homepage_uri: https://teefan.github.io/mt-lang/
629
630
  source_code_uri: https://github.com/teefan/mt-lang
630
631
  post_install_message: |
631
- Milk Tea 0.3.43 installed!
632
+ Milk Tea 0.4.0 installed!
632
633
 
633
634
  System requirements:
634
635
  - A C compiler (gcc or clang) must be available on PATH