Module: MilkTea::ImportedBindings::Generator::GeneratorMethodSource

Included in:
MilkTea::ImportedBindings::Generator
Defined in:
lib/milk_tea/bindings/imported_bindings/method_source.rb

Instance Method Summary collapse

Instance Method Details

#index_method_source_declarations(source_ast) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 48

def index_method_source_declarations(source_ast)
  types = {}
  type_order = []
  functions = {}
  function_order = []

  source_ast.declarations.each do |declaration|
    case declaration
    when AST::TypeAliasDecl, AST::StructDecl, AST::UnionDecl, AST::EnumDecl, AST::FlagsDecl, AST::OpaqueDecl
      next unless visible_from_method_source?(declaration, module_kind: source_ast.module_kind)

      types[declaration.name] = declaration
      type_order << declaration.name
    when AST::ExternFunctionDecl, AST::ForeignFunctionDecl
      next unless visible_from_method_source?(declaration, module_kind: source_ast.module_kind)

      functions[declaration.name] = declaration
      function_order << declaration.name
    end
  end

  {
    types:,
    type_order:,
    functions:,
    function_order:,
  }
end

#load_method_source(spec) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 18

def load_method_source(spec)
  module_name = spec.fetch(:module_name)
  module_path = resolve_module_path(module_name)
  source_ast = ModuleLoader.new(module_roots: @module_roots).load_file(module_path)
  unless source_ast.module_name&.to_s == module_name
    raise Error, "expected #{module_path} to define module #{module_name}"
  end

  declarations = index_method_source_declarations(source_ast)
  MethodSource.new(
    module_name:,
    module_path:,
    module_kind: source_ast.module_kind,
    import_alias: spec.fetch(:module_import_alias),
    imports_by_alias: source_ast.imports.to_h { |import| [import.alias_name, import.path.parts.join(".")] },
    public_type_names: declarations[:types].keys,
    functions: declarations[:functions],
    function_order: declarations[:function_order],
    import_specs: [{ module_name:, alias: spec.fetch(:module_import_alias) }],
  )
end

#load_method_sources(method_specs) ⇒ Object



7
8
9
10
11
12
13
14
15
16
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 7

def load_method_sources(method_specs)
  method_specs.each_with_object({}) do |spec, sources|
    next unless spec[:module_name]

    key = method_source_key(spec)
    next if sources.key?(key)

    sources[key] = load_method_source(spec)
  end
end

#method_source_import_specs(method_sources) ⇒ Object



40
41
42
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 40

def method_source_import_specs(method_sources)
  method_sources.values.flat_map(&:import_specs)
end

#method_source_key(spec) ⇒ Object



44
45
46
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 44

def method_source_key(spec)
  [spec.fetch(:module_name), spec.fetch(:module_import_alias)]
end

#normalize_method_source_param_mode(mode) ⇒ Object



116
117
118
119
120
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 116

def normalize_method_source_param_mode(mode)
  return nil if mode == :plain || mode.nil?

  mode.to_s
end

#plan_method_source_function(declaration, source:) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 83

def plan_method_source_function(declaration, source:)
  if declaration.respond_to?(:variadic) && declaration.variadic
    raise Error, "method generation for #{declaration.name} in #{source.module_path} cannot use variadic functions"
  end

  {
    raw_name: declaration.name,
    public_name: declaration.name,
    call_name: "#{source.import_alias}.#{declaration.name}",
    type_params: raw_type_param_names(declaration),
    params: declaration.params.map { |param| project_method_source_param(param, source:) },
    return_type: project_method_source_type(declaration.return_type, source:),
  }
end

#project_method_source_function_type_param(param, source:) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 140

def project_method_source_function_type_param(param, source:)
  case param
  when AST::Param
    "#{param.name}: #{project_method_source_type(param.type, source:)}"
  when AST::ForeignParam
    text = +""
    text << "#{param.mode} " if param.mode
    text << "#{param.name}: #{project_method_source_type(param.type, source:)}"
    if param.boundary_type
      text << " as #{project_method_source_type(param.boundary_type, source:)}"
    end
    text
  else
    project_method_source_type(param, source:)
  end
end

#project_method_source_param(param, source:) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 98

def project_method_source_param(param, source:)
  case param
  when AST::ForeignParam
    {
      "name" => param.name,
      "type" => project_method_source_type(param.type, source:),
      "mode" => normalize_method_source_param_mode(param.mode),
    }
  when AST::Param
    {
      "name" => param.name,
      "type" => project_method_source_type(param.type, source:),
    }
  else
    raise Error, "unsupported method source parameter #{param.class.name} in #{source.module_path}"
  end
end

#project_method_source_type(type, source:) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 122

def project_method_source_type(type, source:)
  case type
  when AST::TypeRef
    text = project_method_source_type_name(type.name.to_s, source:)
    unless type.arguments.empty?
      rendered_arguments = type.arguments.map { |argument| project_method_source_type_argument(argument.value, source:) }
      text << "[#{rendered_arguments.join(', ')}]"
    end
    text << "?" if type.nullable
    text
  when AST::FunctionType
    params = type.params.map { |param| project_method_source_function_type_param(param, source:) }.join(', ')
    "fn(#{params}) -> #{project_method_source_type(type.return_type, source:)}"
  else
    raise Error, "unsupported method source type node #{type.class.name} in #{source.module_path}"
  end
end

#project_method_source_type_argument(argument, source:) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 157

def project_method_source_type_argument(argument, source:)
  case argument
  when AST::TypeRef, AST::FunctionType
    project_method_source_type(argument, source:)
  when AST::IntegerLiteral
    argument.lexeme
  when AST::Identifier
    argument.name
  else
    raise Error, "unsupported method source type argument #{argument.class.name} in #{source.module_path}"
  end
end

#project_method_source_type_name(raw_name, source:) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 170

def project_method_source_type_name(raw_name, source:)
  parts = raw_name.split(".")
  if parts.length > 1
    import_alias = parts.first
    imported_name = parts[1..].join(".")
    imported_module_name = source.imports_by_alias[import_alias]
    return rendered_type_name(imported_name) if imported_module_name == @raw_module_name
    return imported_name if imported_module_name == @module_name
    return raw_name if imported_module_name
  end

  return "#{source.import_alias}.#{raw_name}" if source.public_type_names.include?(raw_name)

  rendered_type_name(raw_name)
end

#visible_from_method_source?(declaration, module_kind:) ⇒ Boolean

Returns:

  • (Boolean)


77
78
79
80
81
# File 'lib/milk_tea/bindings/imported_bindings/method_source.rb', line 77

def visible_from_method_source?(declaration, module_kind:)
  return true if module_kind == :raw_module

  declaration.respond_to?(:visibility) && declaration.visibility == :public
end