Module: MilkTea::Parse::Types

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

Instance Method Summary collapse

Instance Method Details

#parse_callable_type_ref(keyword:, param_context:) {|params, return_type| ... } ⇒ Object

Yields:

  • (params, return_type)


159
160
161
162
163
164
165
166
167
# File 'lib/milk_tea/core/parser/types.rb', line 159

def parse_callable_type_ref(keyword:, param_context:)
  consume(:lparen, "expected '(' after #{keyword}")
  params = parse_comma_separated_until(:rparen) { parse_function_type_param }

  consume(:rparen, "expected ')' after #{param_context}")
  consume(:arrow, "expected '->' after #{param_context}")
  return_type = parse_type_ref
  yield params, return_type
end

#parse_declaration_type_paramsObject



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/milk_tea/core/parser/types.rb', line 189

def parse_declaration_type_params
  return [] unless match(:lbracket)

  params = parse_comma_separated_until(:rbracket) do
    name_token = consume_name("expected type parameter name")
    name = name_token.lexeme
    if match(:colon)
      value_type = parse_type_ref
      AST::ValueTypeParam.new(
        name:,
        type: value_type,
        line: name_token.line,
        column: name_token.column,
        length: name.length,
      )
    else
      constraints = parse_type_param_constraints
      AST::TypeParam.new(
        name:,
        constraints:,
        line: name_token.line,
        column: name_token.column,
        length: name.length,
      )
    end
  end

  consume(:rbracket, "expected ']' after type parameters")
  params
end

#parse_dyn_type_refObject



132
133
134
135
136
137
138
139
# File 'lib/milk_tea/core/parser/types.rb', line 132

def parse_dyn_type_ref
  first_token = previous
  consume(:lbracket, "expected '[' after dyn")
  interface = parse_qualified_name_with_type_arguments
  consume(:rbracket, "expected ']' after interface name")
  nullable = match(:question)
  AST::DynType.new(interface:, nullable:, line: first_token.line, column: first_token.column, length: 3)
end

#parse_foreign_paramObject



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/milk_tea/core/parser/types.rb', line 65

def parse_foreign_param
  mode = if foreign_param_qualifier_mode?
           advance.type
         else
           :plain
         end
  name_token = consume_name("expected parameter name")
  raise error(name_token, "expected ':' and parameter type") unless match(:colon)

  param_type = parse_type_ref
  boundary_type = match(:as) ? parse_type_ref : nil

  AST::ForeignParam.new(name: name_token.lexeme, type: param_type, mode:, boundary_type:)
rescue ParseError => e
  raise unless @recovery_errors

  @recovery_errors << e
  param_name = name_token&.lexeme || "error"
  error_type = recovery_error_expr(e)
  AST::ForeignParam.new(name: param_name, type: error_type, mode: mode || :plain, boundary_type: nil)
end

#parse_foreign_params(allow_variadic: false) ⇒ Object



14
15
16
# File 'lib/milk_tea/core/parser/types.rb', line 14

def parse_foreign_params(allow_variadic: false)
  parse_parameter_list(allow_variadic:) { parse_foreign_param }
end

#parse_function_type_paramObject



169
170
171
172
173
174
175
# File 'lib/milk_tea/core/parser/types.rb', line 169

def parse_function_type_param
  name_token = consume_name("expected function type parameter name")
  name = name_token.lexeme
  consume(:colon, "expected ':' after function type parameter name")
  type = parse_type_ref
  AST::Param.new(name:, type:, line: name_token.line, column: name_token.column)
end

#parse_function_type_refObject



120
121
122
123
124
# File 'lib/milk_tea/core/parser/types.rb', line 120

def parse_function_type_ref
  parse_callable_type_ref(keyword: "fn", param_context: "function type parameters") do |params, return_type|
    AST::FunctionType.new(params:, return_type:)
  end
end

#parse_implements_clauseObject



249
250
251
252
253
254
255
# File 'lib/milk_tea/core/parser/types.rb', line 249

def parse_implements_clause
  return [] unless match(:implements)

  implements = [parse_qualified_name_with_type_arguments]
  implements << parse_qualified_name_with_type_arguments while match(:comma)
  implements
end

#parse_paramObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/milk_tea/core/parser/types.rb', line 42

def parse_param
  name_token = consume_name("expected parameter name")
  raise error(name_token, "expected ':' and parameter type") unless match(:colon)

  param_type = parse_type_ref
  default_value = parse_param_default_value

  AST::Param.new(name: name_token.lexeme, type: param_type, line: name_token.line, column: name_token.column, default_value:)
rescue ParseError => e
  raise unless @recovery_errors

  @recovery_errors << e
  param_name = name_token&.lexeme || "error"
  error_type = recovery_error_expr(e)
  AST::Param.new(name: param_name, type: error_type, line: name_token&.line || 1, column: name_token&.column || 1)
end

#parse_param_default_valueObject



59
60
61
62
63
# File 'lib/milk_tea/core/parser/types.rb', line 59

def parse_param_default_value
  return nil unless match(:equal)

  parse_expression
end

#parse_parameter_list(allow_variadic: false) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/milk_tea/core/parser/types.rb', line 18

def parse_parameter_list(allow_variadic: false)
  consume(:lparen, "expected '('")
  params = []
  variadic = false

  unless check(:rparen)
    loop do
      if allow_variadic && match(:ellipsis)
        variadic = true
        break
      end

      params << yield
      break unless match(:comma)
      break if check(:rparen)
    end
  end

  consume(:rparen, "expected ')' after parameters")
  return [params, variadic] if allow_variadic

  params
end

#parse_params(allow_variadic: false) ⇒ Object



6
7
8
# File 'lib/milk_tea/core/parser/types.rb', line 6

def parse_params(allow_variadic: false)
  parse_parameter_list(allow_variadic:) { parse_param }
end

#parse_proc_type_refObject



126
127
128
129
130
# File 'lib/milk_tea/core/parser/types.rb', line 126

def parse_proc_type_ref
  parse_callable_type_ref(keyword: "proc", param_context: "proc type parameters") do |params, return_type|
    AST::ProcType.new(params:, return_type:)
  end
end

#parse_qualified_name_with_type_argumentsObject



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/milk_tea/core/parser/types.rb', line 257

def parse_qualified_name_with_type_arguments
  first_token = consume_path_component("expected identifier")
  parts = [first_token.lexeme]
  while match(:dot)
    parts << consume_path_component("expected identifier after '.'").lexeme
  end
  type_arguments = if match(:lbracket)
    args = parse_comma_separated_until(:rbracket) do
      parse_type_ref
    end
    consume(:rbracket, "expected ']' after type arguments")
    args
  else
    []
  end
  AST::QualifiedName.new(parts:, type_arguments:, line: first_token.line, column: first_token.column)
end

#parse_signature_paramsObject



10
11
12
# File 'lib/milk_tea/core/parser/types.rb', line 10

def parse_signature_params
  parse_parameter_list { parse_param }
end

#parse_tuple_type_refObject



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/milk_tea/core/parser/types.rb', line 141

def parse_tuple_type_ref
  match(:lparen)
  first_type = parse_type_ref
  if match(:comma)
    element_types = [first_type]
    loop do
      element_types << parse_type_ref
      break unless match(:comma)
    end
    consume(:rparen, "expected ')' after tuple type elements")
    nullable = match(:question)
    AST::TupleType.new(element_types:, nullable:)
  else
    consume(:rparen, "expected ')' after type")
    first_type
  end
end

#parse_type_argumentObject



177
178
179
180
181
182
183
184
185
186
187
# File 'lib/milk_tea/core/parser/types.rb', line 177

def parse_type_argument
  if match(:integer)
    token = previous
    AST::IntegerLiteral.new(lexeme: token.lexeme, value: token.literal)
  elsif match(:float)
    token = previous
    AST::FloatLiteral.new(lexeme: token.lexeme, value: token.literal)
  else
    parse_type_ref
  end
end

#parse_type_param_constraint(interface_constraint_mode) ⇒ Object



239
240
241
242
243
244
245
246
247
# File 'lib/milk_tea/core/parser/types.rb', line 239

def parse_type_param_constraint(interface_constraint_mode)
  if match(:implements)
    return [AST::TypeParamConstraint.new(kind: :interface, interface_ref: parse_qualified_name_with_type_arguments), true]
  end

  return [AST::TypeParamConstraint.new(kind: :interface, interface_ref: parse_qualified_name_with_type_arguments), true] if interface_constraint_mode

  [nil, interface_constraint_mode]
end

#parse_type_param_constraintsObject



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/milk_tea/core/parser/types.rb', line 220

def parse_type_param_constraints
  constraints = []
  interface_constraint_mode = false

  constraint, interface_constraint_mode = parse_type_param_constraint(interface_constraint_mode)
  return constraints unless constraint

  constraints << constraint

  while match(:and)
    constraint, interface_constraint_mode = parse_type_param_constraint(interface_constraint_mode)
    raise error(peek, "expected constraint after 'and'") unless constraint

    constraints << constraint
  end

  constraints
end

#parse_type_refObject



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
113
114
115
116
117
118
# File 'lib/milk_tea/core/parser/types.rb', line 87

def parse_type_ref
  return parse_function_type_ref if match(:fn)
  return parse_proc_type_ref if match(:proc)
  return parse_dyn_type_ref if match(:dyn)
  return parse_tuple_type_ref if check(:lparen)

  first_token = peek
  if match(:at)
    lt_token = consume_name("expected lifetime name after @")
    return AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["@#{lt_token.lexeme}"]), arguments: [], nullable: false, lifetime: nil, line: first_token.line, column: first_token.column, length: lt_token.lexeme.length + 1)
  end
  name = parse_qualified_name
  arguments = []
  lifetime = nil
  if match(:lbracket)
    if check(:at) && name.to_s == "ref"
      match(:at)
      lt_token = consume_name("expected lifetime name after @")
      lifetime = "@#{lt_token.lexeme}"
      consume(:comma, "expected ',' after lifetime in type arguments")
    end
    arguments = parse_comma_separated_until(:rbracket) do
      AST::TypeArgument.new(value: parse_type_argument)
    end
    consume(:rbracket, "expected ']' after type arguments")
  end

  nullable = match(:question)
  type_name = name.to_s
  length = type_name.length + (nullable ? 1 : 0)
  AST::TypeRef.new(name:, arguments:, nullable:, lifetime:, line: first_token.line, column: first_token.column, length:)
end