Module: MilkTea::Parse::Expressions

Included in:
MilkTea::Parser
Defined in:
lib/milk_tea/core/parser/expressions.rb

Instance Method Summary collapse

Instance Method Details

#adjacent_tokens?(left, right) ⇒ Boolean

Returns:

  • (Boolean)


263
264
265
# File 'lib/milk_tea/core/parser/expressions.rb', line 263

def adjacent_tokens?(left, right)
  left.line == right.line && right.column == (left.column + left.lexeme.length)
end

#aggregate_specialization_target?(expression) ⇒ Boolean

Returns:

  • (Boolean)


591
592
593
594
595
596
597
598
599
600
# File 'lib/milk_tea/core/parser/expressions.rb', line 591

def aggregate_specialization_target?(expression)
  case expression
  when AST::Identifier
    true
  when AST::MemberAccess
    true
  else
    false
  end
end

#builtin_specialization_target?(expression) ⇒ Boolean

Returns:

  • (Boolean)


583
584
585
# File 'lib/milk_tea/core/parser/expressions.rb', line 583

def builtin_specialization_target?(expression)
  expression.is_a?(AST::Identifier) && %w[array reinterpret span zero ptr const_ptr own ref adapt equal hash order simd SoA].include?(expression.name)
end

#definite_type_argument?(value) ⇒ Boolean

Returns:

  • (Boolean)


644
645
646
647
648
649
650
651
652
653
# File 'lib/milk_tea/core/parser/expressions.rb', line 644

def definite_type_argument?(value)
  case value
  when AST::FunctionType
    true
  when AST::TypeRef
    known_type_like_name?(value.name.parts.first)
  else
    false
  end
end

#explicit_specialization_argument?(value) ⇒ Boolean

Returns:

  • (Boolean)


633
634
635
636
637
638
# File 'lib/milk_tea/core/parser/expressions.rb', line 633

def explicit_specialization_argument?(value)
  definite_type_argument?(value) ||
    potential_named_literal_type_argument?(value) ||
    value.is_a?(AST::IntegerLiteral) ||
    value.is_a?(AST::FloatLiteral)
end

#generic_callable_specialization_target?(expression) ⇒ Boolean

Returns:

  • (Boolean)


625
626
627
# File 'lib/milk_tea/core/parser/expressions.rb', line 625

def generic_callable_specialization_target?(expression)
  expression.is_a?(AST::Identifier) && @known_generic_callable_names.key?(expression.name)
end

#imported_member_specialization_target?(expression) ⇒ Boolean

Returns:

  • (Boolean)


629
630
631
# File 'lib/milk_tea/core/parser/expressions.rb', line 629

def imported_member_specialization_target?(expression)
  expression.is_a?(AST::MemberAccess) && expression.receiver.is_a?(AST::Identifier) && @known_import_aliases.key?(expression.receiver.name)
end

#parse_additiveObject



204
205
206
# File 'lib/milk_tea/core/parser/expressions.rb', line 204

def parse_additive
  parse_left_associative(:parse_multiplicative, :plus, :minus)
end

#parse_adjacent_string_literal(first_token, cstring:) ⇒ Object



432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/milk_tea/core/parser/expressions.rb', line 432

def parse_adjacent_string_literal(first_token, cstring:)
  lexeme = +first_token.lexeme
  value = +first_token.literal
  all_cstring = cstring

  while match(:string, :cstring)
    token = previous
    lexeme << token.lexeme
    value << token.literal
    all_cstring &&= token.type == :cstring
  end

  AST::StringLiteral.new(lexeme:, value:, cstring: all_cstring)
end

#parse_alignof_exprObject



473
474
475
476
477
478
# File 'lib/milk_tea/core/parser/expressions.rb', line 473

def parse_alignof_expr
  consume(:lparen, "expected '(' after align_of")
  type = parse_type_ref
  consume(:rparen, "expected ')' after align_of type")
  AST::AlignofExpr.new(type:)
end

#parse_andObject



125
126
127
# File 'lib/milk_tea/core/parser/expressions.rb', line 125

def parse_and
  parse_left_associative(:parse_not, :and)
end

#parse_bitwise_andObject



188
189
190
# File 'lib/milk_tea/core/parser/expressions.rb', line 188

def parse_bitwise_and
  parse_left_associative(:parse_equality, :amp)
end

#parse_bitwise_orObject



180
181
182
# File 'lib/milk_tea/core/parser/expressions.rb', line 180

def parse_bitwise_or
  parse_left_associative(:parse_bitwise_xor, :pipe)
end

#parse_bitwise_xorObject



184
185
186
# File 'lib/milk_tea/core/parser/expressions.rb', line 184

def parse_bitwise_xor
  parse_left_associative(:parse_bitwise_and, :caret)
end

#parse_call_argumentObject



346
347
348
349
350
351
352
353
354
# File 'lib/milk_tea/core/parser/expressions.rb', line 346

def parse_call_argument
  if (check_name || keyword_token?(peek)) && check_next(:equal)
    name = advance.lexeme
    consume(:equal, "expected '=' after named argument name")
    AST::Argument.new(name:, value: parse_expression)
  else
    AST::Argument.new(name: nil, value: parse_expression)
  end
end

#parse_call_argumentsObject



333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/milk_tea/core/parser/expressions.rb', line 333

def parse_call_arguments
  arguments = []
  unless check(:rparen)
    loop do
      arguments << parse_call_argument
      break unless match(:comma)
      break if check(:rparen)
    end
  end
  consume(:rparen, "expected ')' after call arguments")
  arguments
end

#parse_comparisonObject



196
197
198
# File 'lib/milk_tea/core/parser/expressions.rb', line 196

def parse_comparison
  parse_left_associative(:parse_shift, :less, :less_equal, :greater, :greater_equal)
end

#parse_diagnostic_hint?(error) ⇒ Boolean

Returns:

  • (Boolean)


587
588
589
# File 'lib/milk_tea/core/parser/expressions.rb', line 587

def parse_diagnostic_hint?(error)
  error.message.include?("did you mean T<-expr?")
end

#parse_embedded_expression(source, line:, column:) ⇒ Object



531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
# File 'lib/milk_tea/core/parser/expressions.rb', line 531

def parse_embedded_expression(source, line:, column:)
  # Interpolation source may carry surrounding whitespace (e.g. `#{ x }`);
  # strip it so leading whitespace is not re-lexed as indentation. Shift
  # the column by the stripped leading width to keep error positions exact.
  leading = source.length - source.lstrip.length
  stripped = source.strip
  tokens = Lexer.lex(stripped, path: @path).map do |token|
    Token.new(
      type: token.type,
      lexeme: token.lexeme,
      literal: token.literal,
      line: token.line + line - 1,
      column: token.column + column - 1 + leading,
      start_offset: token.start_offset,
      end_offset: token.end_offset,
      leading_trivia: token.leading_trivia,
      trailing_trivia: token.trailing_trivia,
    )
  end

  parser = self.class.new(tokens, path: @path)
  parser.instance_variable_set(:@known_type_names, @known_type_names.dup)
  parser.instance_variable_set(:@known_import_aliases, @known_import_aliases.dup)
  parser.instance_variable_set(:@known_generic_callable_names, @known_generic_callable_names.dup)
  parser.instance_variable_set(:@current_type_param_names, @current_type_param_names.dup)

  expression = parser.parse_expression
  parser.skip_newlines
  raise parser.error(parser.peek, "expected end of interpolation") unless parser.eof?

  expression
end

#parse_equalityObject



192
193
194
# File 'lib/milk_tea/core/parser/expressions.rb', line 192

def parse_equality
  parse_left_associative(:parse_comparison, :equal_equal, :bang_equal)
end

#parse_expressionObject



6
7
8
9
10
11
12
# File 'lib/milk_tea/core/parser/expressions.rb', line 6

def parse_expression
  return parse_if_expression if match(:if)
  return parse_match_expression if match(:match)
  return parse_unsafe_expression if match(:unsafe)

  parse_range
end

#parse_format_spec(spec_str) ⇒ Object



508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/milk_tea/core/parser/expressions.rb', line 508

def parse_format_spec(spec_str)
  return nil if spec_str.nil? || spec_str.empty?

  trimmed = spec_str.strip
  if (m = trimmed.match(/\A\.(\d+)\z/))
    { kind: :precision, value: m[1].to_i }
  elsif trimmed == "x"
    { kind: :hex, uppercase: false }
  elsif trimmed == "X"
    { kind: :hex, uppercase: true }
  elsif trimmed == "o"
    { kind: :oct, uppercase: false }
  elsif trimmed == "O"
    { kind: :oct, uppercase: true }
  elsif trimmed == "b"
    { kind: :bin, uppercase: false }
  elsif trimmed == "B"
    { kind: :bin, uppercase: true }
  else
    raise error(peek, "unsupported format spec '#{trimmed}': expected .N for float precision (e.g. :.2), :x/:X, :o/:O, or :b/:B")
  end
end

#parse_format_string_literal(parts) ⇒ Object



489
490
491
# File 'lib/milk_tea/core/parser/expressions.rb', line 489

def parse_format_string_literal(parts)
  AST::FormatString.new(parts: parts.map { |part| parse_format_string_part(part) })
end

#parse_format_string_part(part) ⇒ Object



493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/milk_tea/core/parser/expressions.rb', line 493

def parse_format_string_part(part)
  case part.fetch(:kind)
  when :text
    AST::FormatTextPart.new(value: part.fetch(:value))
  when :expr
    format_spec = parse_format_spec(part.fetch(:format_spec))
    AST::FormatExprPart.new(
      expression: parse_embedded_expression(part.fetch(:source), line: part.fetch(:line), column: part.fetch(:column)),
      format_spec:,
    )
  else
    raise error(peek, "unsupported format string part #{part.inspect}")
  end
end

#parse_if_expressionObject



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/milk_tea/core/parser/expressions.rb', line 24

def parse_if_expression
  condition = parse_or

  if inline_block_body?
    consume(:colon, "expected ':' after condition in if expression")
    then_expression = parse_expression
    consume(:else, "expected 'else' in if expression")
    consume(:colon, "expected ':' after 'else' in if expression")
    else_expression = parse_expression
  else
    consume(:colon, "expected ':' after condition in if expression")
    consume(:newline, "expected newline after 'if condition:' in if expression")
    consume(:indent, "expected indented body in if expression")
    then_expression = parse_expression
    consume_end_of_statement unless block_expression?(then_expression)
    consume(:dedent, "expected end of if expression body")
    consume(:else, "expected 'else' in if expression")
    consume(:colon, "expected ':' after 'else' in if expression")
    consume(:newline, "expected newline after 'else:' in if expression")
    consume(:indent, "expected indented else body in if expression")
    else_expression = parse_expression
    consume_end_of_statement unless block_expression?(else_expression)
    consume(:dedent, "expected end of else expression body")
  end

  AST::IfExpr.new(condition:, then_expression:, else_expression:)
end

#parse_isObject



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/milk_tea/core/parser/expressions.rb', line 139

def parse_is
  expr = parse_bitwise_or
  while match(:is)
    line = previous.line
    column = previous.column
    arm_pattern = parse_bitwise_or
    if arm_pattern.is_a?(AST::Call) && arm_pattern.arguments.any?
      raise error(previous, "`is` does not support struct pattern bindings; use `match` to destructure payload fields")
    end

    expr = AST::MatchExpr.new(
      expression: expr,
      arms: [
        AST::MatchExprArm.new(
          pattern: arm_pattern,
          binding_name: nil,
          binding_line: nil,
          binding_column: nil,
          value: AST::BooleanLiteral.new(value: true),
          line: arm_pattern.line,
          column: arm_pattern.column,
        ),
        AST::MatchExprArm.new(
          pattern: AST::Identifier.new(name: "_", line:, column:),
          binding_name: nil,
          binding_line: nil,
          binding_column: nil,
          value: AST::BooleanLiteral.new(value: false),
          line:,
          column:,
        ),
      ],
      line:,
      column:,
      length: previous.lexeme.length,
      desugared_from_is: true,
    )
  end
  expr
end

#parse_left_associative(operand_method, *operator_types) ⇒ Object



564
565
566
567
568
569
570
571
572
573
# File 'lib/milk_tea/core/parser/expressions.rb', line 564

def parse_left_associative(operand_method, *operator_types)
  operand = method(operand_method)
  expression = operand.call
  while match(*operator_types)
    operator = previous.lexeme
    right = operand.call
    expression = AST::BinaryOp.new(operator:, left: expression, right:)
  end
  expression
end

#parse_match_arm_patternObject



75
76
77
78
79
80
81
# File 'lib/milk_tea/core/parser/expressions.rb', line 75

def parse_match_arm_pattern
  pattern = parse_bitwise_xor
  return pattern unless (pattern.is_a?(AST::IntegerLiteral) || pattern.is_a?(AST::CharLiteral)) && match(:dot_dot)

  end_expr = parse_bitwise_xor
  AST::RangeExpr.new(start_expr: pattern, end_expr:, line: previous.line, column: previous.column)
end

#parse_match_expressionObject



52
53
54
55
56
57
# File 'lib/milk_tea/core/parser/expressions.rb', line 52

def parse_match_expression
  token = previous
  expression = parse_expression
  arms = parse_match_expression_arms
  AST::MatchExpr.new(expression:, arms:, line: token.line, column: token.column, length: token.lexeme.length)
end

#parse_match_expression_armObject



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/milk_tea/core/parser/expressions.rb', line 83

def parse_match_expression_arm
  patterns = []
  if match(:else)
    patterns << AST::Identifier.new(name: "_", line: previous.line, column: previous.column)
  else
    patterns << parse_match_arm_pattern
    while match(:pipe)
      patterns << parse_match_arm_pattern
    end
  end
  binding_token = nil
  binding_name = if match(:as)
                   binding_token = consume_name("expected binding name after 'as'")
                   binding_token.lexeme
                 end
  consume(:colon, "expected ':' after match expression arm pattern")
  value = parse_expression
  consume_end_of_statement unless block_expression?(value)
  patterns.map do |pattern|
    AST::MatchExprArm.new(
      pattern:,
      binding_name:,
      binding_line: binding_token&.line,
      binding_column: binding_token&.column,
      value:,
      line: pattern.line,
      column: pattern.column,
    )
  end
end

#parse_match_expression_armsObject



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/milk_tea/core/parser/expressions.rb', line 59

def parse_match_expression_arms
  consume(:colon, "expected ':' before match expression arms")
  consume(:newline, "expected newline before match expression arms")
  consume(:indent, "expected indented match expression arms")

  arms = []
  skip_newlines
  until check(:dedent) || eof?
    arms.concat(parse_match_expression_arm)
    skip_newlines
  end

  consume(:dedent, "expected end of match expression arms")
  arms
end

#parse_multiplicativeObject



208
209
210
# File 'lib/milk_tea/core/parser/expressions.rb', line 208

def parse_multiplicative
  parse_left_associative(:parse_unary, :star, :slash, :percent)
end

#parse_notObject



129
130
131
132
133
134
135
136
137
# File 'lib/milk_tea/core/parser/expressions.rb', line 129

def parse_not
  if match(:not)
    operator = previous.lexeme
    operand = parse_not
    return AST::UnaryOp.new(operator:, operand:)
  end

  parse_is
end

#parse_offsetof_exprObject



480
481
482
483
484
485
486
487
# File 'lib/milk_tea/core/parser/expressions.rb', line 480

def parse_offsetof_expr
  consume(:lparen, "expected '(' after offset_of")
  type = parse_type_ref
  consume(:comma, "expected ',' after offset_of type")
  field = consume_name("expected field name in offset_of").lexeme
  consume(:rparen, "expected ')' after offset_of field")
  AST::OffsetofExpr.new(type:, field:)
end

#parse_orObject



121
122
123
# File 'lib/milk_tea/core/parser/expressions.rb', line 121

def parse_or
  parse_left_associative(:parse_and, :or)
end

#parse_postfixObject



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/milk_tea/core/parser/expressions.rb', line 267

def parse_postfix
  expression = parse_primary

  loop do
    if match(:dot)
      member_token = consume_name_allowing_keywords("expected member name after '.'")
      expression = AST::MemberAccess.new(
        receiver: expression,
        member: member_token.lexeme,
        line: member_token.line,
        column: member_token.column,
      )
    elsif check(:lbracket)
      if (specialization = try_parse_specialization(expression))
        expression = specialization
      else
        advance
        index = parse_expression
        consume(:rbracket, "expected ']' after index expression")
        expression = AST::IndexAccess.new(receiver: expression, index:)
      end
    elsif match(:lparen)
      expression = AST::Call.new(callee: expression, arguments: parse_call_arguments)
    elsif match(:question)
      expression = AST::UnaryOp.new(operator: "?", operand: expression)
    else
      break
    end
  end

  expression
end

#parse_primaryObject



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/milk_tea/core/parser/expressions.rb', line 356

def parse_primary
  if match(:size_of)
    parse_sizeof_expr
  elsif match(:align_of)
    parse_alignof_expr
  elsif match(:offset_of)
    parse_offsetof_expr
  elsif match(:members_of) || match(:attributes_of) || match(:field_of) || match(:callable_of) || match(:attribute_of) || match(:has_attribute) || match(:attribute_arg) || match(:fields_of)
    AST::Identifier.new(name: previous.lexeme, line: previous.line, column: previous.column)
  elsif match(:proc)
    parse_proc_expr
  elsif match_name
    AST::Identifier.new(name: previous.lexeme, line: previous.line, column: previous.column)
  elsif match(:integer)
    AST::IntegerLiteral.new(lexeme: previous.lexeme, value: previous.literal)
  elsif match(:float)
    AST::FloatLiteral.new(lexeme: previous.lexeme, value: previous.literal)
  elsif match(:char_literal)
    AST::CharLiteral.new(lexeme: previous.lexeme, value: previous.literal, line: previous.line, column: previous.column)
  elsif match(:string)
    parse_adjacent_string_literal(previous, cstring: false)
  elsif match(:cstring)
    parse_adjacent_string_literal(previous, cstring: true)
  elsif match(:fstring)
    parse_format_string_literal(previous.literal)
  elsif match(:true)
    AST::BooleanLiteral.new(value: true)
  elsif match(:false)
    AST::BooleanLiteral.new(value: false)
  elsif match(:null)
    line = previous.line
    column = previous.column
    type = nil
    if match(:lbracket)
      type = parse_type_ref
      consume(:rbracket, "expected ']' after typed null literal")
    end
    AST::NullLiteral.new(type:, line:, column:)
  elsif match(:lparen)
    line = previous.line
    column = previous.column
    first = if check_name && check_next(:equal)
              name_token = advance
              consume(:equal, "expected '=' after named tuple field")
              AST::Argument.new(name: name_token.lexeme, value: parse_expression)
            else
              parse_expression
            end
    if match(:comma)
      elements = [first]
      loop do
        if check_name && check_next(:equal)
          name_token = advance
          consume(:equal, "expected '=' after named tuple field")
          value = parse_expression
          elements << AST::Argument.new(name: name_token.lexeme, value:)
        else
          elements << parse_expression
        end
        break unless match(:comma)
        break if check(:rparen)
      end
      consume(:rparen, "expected ')' after tuple elements")
      AST::ExpressionList.new(elements:, line:, column:)
    else
      consume(:rparen, "expected ')' after expression")
      first
    end
  elsif keyword_token?(peek)
    advance
    AST::Identifier.new(name: previous.lexeme, line: previous.line, column: previous.column)
  else
    raise error(peek, "expected expression")
  end
end

#parse_proc_exprObject



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/milk_tea/core/parser/expressions.rb', line 447

def parse_proc_expr
  consume(:lparen, "expected '(' after proc")
  params = parse_comma_separated_until(:rparen) { parse_function_type_param }

  consume(:rparen, "expected ')' after proc parameters")
  consume(:arrow, "expected '->' after proc parameters")
  return_type = parse_type_ref
  consume(:colon, "expected ':' before proc body")
  body = if match(:newline)
           consume(:indent, "expected indented block")
           statements = parse_statement_block_body
           consume(:dedent, "expected end of block")
           statements
         else
           [AST::ReturnStmt.new(value: parse_expression)]
         end
  AST::ProcExpr.new(params:, return_type:, body:)
end

#parse_rangeObject



14
15
16
17
18
19
20
21
22
# File 'lib/milk_tea/core/parser/expressions.rb', line 14

def parse_range
  expr = parse_or
  return expr unless match(:dot_dot)

  line = previous.line
  column = expr.column || 0
  end_expr = parse_or
  AST::RangeExpr.new(start_expr: expr, end_expr:, line:, column:)
end

#parse_shiftObject



200
201
202
# File 'lib/milk_tea/core/parser/expressions.rb', line 200

def parse_shift
  parse_left_associative(:parse_additive, :shift_left, :shift_right)
end

#parse_sizeof_exprObject



466
467
468
469
470
471
# File 'lib/milk_tea/core/parser/expressions.rb', line 466

def parse_sizeof_expr
  consume(:lparen, "expected '(' after size_of")
  type = parse_type_ref
  consume(:rparen, "expected ')' after size_of type")
  AST::SizeofExpr.new(type:)
end

#parse_unaryObject



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/milk_tea/core/parser/expressions.rb', line 212

def parse_unary
  if (cast_prefix = try_parse_prefix_cast_expression)
    cast_prefix
  elsif match(:unsafe)
    parse_unsafe_expression
  elsif match(:await)
    AST::AwaitExpr.new(expression: parse_unary)
  elsif match(:detach)
    line = previous.line
    column = previous.column
    AST::DetachExpr.new(body: [AST::ExpressionStmt.new(expression: parse_unary)], line:, column:)
  elsif match(:minus, :plus, :tilde, :out, :in, :inout)
    operator = previous.lexeme
    operand = parse_unary
    AST::UnaryOp.new(operator:, operand:)
  else
    parse_postfix
  end
end

#parse_unsafe_expressionObject



114
115
116
117
118
119
# File 'lib/milk_tea/core/parser/expressions.rb', line 114

def parse_unsafe_expression
  token = previous
  consume(:colon, "expected ':' after unsafe in expression")
  expression = parse_expression
  AST::UnsafeExpr.new(expression:, line: token.line, column: token.column, length: token.lexeme.length)
end

#postfix_bracket_starts_specialization?(expression) ⇒ Boolean

Returns:

  • (Boolean)


575
576
577
# File 'lib/milk_tea/core/parser/expressions.rb', line 575

def postfix_bracket_starts_specialization?(expression)
  specialization_target?(expression) && matching_rbracket_index(@current)
end

#potential_named_literal_type_argument?(value) ⇒ Boolean

Returns:

  • (Boolean)


640
641
642
# File 'lib/milk_tea/core/parser/expressions.rb', line 640

def potential_named_literal_type_argument?(value)
  value.is_a?(AST::TypeRef) && value.arguments.empty? && !value.nullable
end

#specialization_call_target?(expression, arguments, call_arguments) ⇒ Boolean

Returns:

  • (Boolean)


602
603
604
605
606
607
608
609
610
611
612
613
614
# File 'lib/milk_tea/core/parser/expressions.rb', line 602

def specialization_call_target?(expression, arguments, call_arguments)
  if expression.is_a?(AST::Identifier) && %w[default zero].include?(expression.name) &&
      !known_type_like_name?(expression.name) && !generic_callable_specialization_target?(expression)
    return false
  end

  return true if builtin_specialization_target?(expression)
  return true if aggregate_specialization_target?(expression) && call_arguments.all?(&:name)
  return true if generic_callable_specialization_target?(expression) && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }
  return true if imported_member_specialization_target?(expression) && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }

  aggregate_specialization_target?(expression) && arguments.all? { |argument| definite_type_argument?(argument.value) }
end

#specialization_target?(expression) ⇒ Boolean

Returns:

  • (Boolean)


579
580
581
# File 'lib/milk_tea/core/parser/expressions.rb', line 579

def specialization_target?(expression)
  builtin_specialization_target?(expression) || aggregate_specialization_target?(expression)
end

#specialization_value_target?(expression, arguments) ⇒ Boolean

Returns:

  • (Boolean)


616
617
618
619
620
621
622
623
# File 'lib/milk_tea/core/parser/expressions.rb', line 616

def specialization_value_target?(expression, arguments)
  return true if expression.is_a?(AST::Identifier) && expression.name == "zero" && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }
  return true if aggregate_specialization_target?(expression) && arguments.all? { |argument| definite_type_argument?(argument.value) }
  return true if generic_callable_specialization_target?(expression) && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }
  return true if imported_member_specialization_target?(expression) && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }

  false
end

#try_parse_prefix_cast_expressionObject



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/milk_tea/core/parser/expressions.rb', line 232

def try_parse_prefix_cast_expression
  saved_current = @current
  return nil unless check_name && known_type_like_name?(peek.lexeme)

  start_token = peek
  expression = nil
  target_type = parse_type_ref
  type_tail = @tokens[@current - 1]
  less_token = peek
  return nil unless less_token.type == :less

  minus_token = @tokens[@current + 1]
  return nil unless minus_token&.type == :minus

  unless adjacent_tokens?(type_tail, less_token) && adjacent_tokens?(less_token, minus_token)
    raise error(less_token, "did you mean T<-expr?")
  end

  advance
  advance

  expression = parse_unary
  AST::PrefixCast.new(target_type:, expression:, line: start_token.line, column: start_token.column)
rescue ParseError => e
  raise e if parse_diagnostic_hint?(e)

  nil
ensure
  @current = saved_current if expression.nil?
end

#try_parse_specialization(expression) ⇒ Object



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/milk_tea/core/parser/expressions.rb', line 300

def try_parse_specialization(expression)
  return nil unless postfix_bracket_starts_specialization?(expression)

  saved_current = @current
  advance
  arguments = parse_comma_separated_until(:rbracket) do
    AST::TypeArgument.new(value: parse_type_argument)
  end
  consume(:rbracket, "expected ']' after specialization arguments")

  if match(:lparen)
    call_arguments = parse_call_arguments
    unless specialization_call_target?(expression, arguments, call_arguments)
      @current = saved_current
      return nil
    end

    return AST::Call.new(callee: AST::Specialization.new(callee: expression, arguments:), arguments: call_arguments)
  end

  unless specialization_value_target?(expression, arguments)
    @current = saved_current
    return nil
  end

  AST::Specialization.new(callee: expression, arguments:)
rescue ParseError => e
  raise e if parse_diagnostic_hint?(e)

  @current = saved_current
  nil
end