mt-lang 0.3.17 → 0.3.22
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/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/c_backend/expressions.rb +34 -23
- data/lib/milk_tea/core/c_backend/feature_detection.rb +25 -45
- data/lib/milk_tea/core/c_backend/type_collectors.rb +18 -30
- data/lib/milk_tea/core/c_backend/type_declaration.rb +0 -6
- data/lib/milk_tea/core/c_backend.rb +49 -39
- data/lib/milk_tea/core/compile_time.rb +98 -72
- data/lib/milk_tea/core/control_flow/builder.rb +2 -1
- data/lib/milk_tea/core/intrinsics.rb +7 -0
- data/lib/milk_tea/core/lexer.rb +46 -35
- data/lib/milk_tea/core/lowering/block.rb +9 -0
- data/lib/milk_tea/core/lowering/calls.rb +2 -0
- data/lib/milk_tea/core/lowering/declarations.rb +1 -1
- data/lib/milk_tea/core/lowering/functions.rb +11 -8
- data/lib/milk_tea/core/lowering/resolve.rb +11 -4
- data/lib/milk_tea/core/lowering/scans.rb +13 -18
- data/lib/milk_tea/core/module_binder.rb +19 -15
- data/lib/milk_tea/core/module_loader.rb +212 -59
- data/lib/milk_tea/core/module_path_resolver.rb +8 -7
- data/lib/milk_tea/core/parser/declarations.rb +43 -19
- data/lib/milk_tea/core/parser/expressions.rb +5 -8
- data/lib/milk_tea/core/parser/statements.rb +5 -5
- data/lib/milk_tea/core/parser.rb +26 -0
- data/lib/milk_tea/core/semantic_analyzer/calls.rb +24 -31
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +20 -23
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +146 -92
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +19 -41
- data/lib/milk_tea/core/semantic_analyzer.rb +56 -37
- data/lib/milk_tea/core/types.rb +12 -17
- data/lib/milk_tea/lsp/diagnostics.rb +13 -0
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +6 -0
- data/lib/milk_tea/tooling/cli/commands/bindgen.rb +11 -0
- data/lib/milk_tea/tooling/cli/commands/build.rb +37 -0
- data/lib/milk_tea/tooling/cli/commands/cache.rb +46 -0
- data/lib/milk_tea/tooling/cli/commands/check.rb +106 -0
- data/lib/milk_tea/tooling/cli/commands/command_base.rb +8 -0
- data/lib/milk_tea/tooling/cli/commands/completions.rb +48 -0
- data/lib/milk_tea/tooling/cli/commands/dap.rb +58 -0
- data/lib/milk_tea/tooling/cli/commands/debug.rb +77 -0
- data/lib/milk_tea/tooling/cli/commands/deps.rb +17 -0
- data/lib/milk_tea/tooling/cli/commands/docs.rb +58 -0
- data/lib/milk_tea/tooling/cli/commands/emit_c.rb +64 -0
- data/lib/milk_tea/tooling/cli/commands/format.rb +199 -0
- data/lib/milk_tea/tooling/cli/commands/lex.rb +46 -0
- data/lib/milk_tea/tooling/cli/commands/lint.rb +248 -0
- data/lib/milk_tea/tooling/cli/commands/lower.rb +50 -0
- data/lib/milk_tea/tooling/cli/commands/lsp.rb +43 -0
- data/lib/milk_tea/tooling/cli/commands/new.rb +26 -0
- data/lib/milk_tea/tooling/cli/commands/parse.rb +51 -0
- data/lib/milk_tea/tooling/cli/commands/run.rb +99 -0
- data/lib/milk_tea/tooling/cli/commands/snapshot.rb +117 -0
- data/lib/milk_tea/tooling/cli/commands/test.rb +557 -0
- data/lib/milk_tea/tooling/cli/commands/toolchain.rb +16 -0
- data/lib/milk_tea/tooling/cli.rb +101 -1893
- data/lib/milk_tea/tooling/sexpr_dumper.rb +7 -7
- metadata +24 -2
|
@@ -94,6 +94,7 @@ module MilkTea
|
|
|
94
94
|
end
|
|
95
95
|
ResolvedAttributeApplication = Data.define(:binding, :argument_values)
|
|
96
96
|
AttributePresenceKey = Data.define(:target, :attribute_module_name, :attribute_name)
|
|
97
|
+
CallableResolution = Data.define(:kind, :value, :receiver)
|
|
97
98
|
TypeParamConstraintBinding = Data.define(:interfaces) do
|
|
98
99
|
def initialize(interfaces: []) = super
|
|
99
100
|
end
|
|
@@ -185,30 +186,47 @@ module MilkTea
|
|
|
185
186
|
end
|
|
186
187
|
|
|
187
188
|
def check
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
189
|
+
@completed_phases = Set.new
|
|
190
|
+
|
|
191
|
+
run_phase(:install_builtin_types)
|
|
192
|
+
run_phase(:install_builtin_attributes)
|
|
193
|
+
run_phase(:install_imports)
|
|
194
|
+
run_phase(:install_prelude_types, requires: [:install_imports])
|
|
195
|
+
run_phase(:declare_named_types, requires: [:install_builtin_types, :install_imports, :install_prelude_types])
|
|
196
|
+
run_phase(:resolve_generic_type_param_constraints, requires: [:declare_named_types])
|
|
197
|
+
run_phase(:resolve_type_aliases, requires: [:declare_named_types])
|
|
198
|
+
run_phase(:declare_attributes)
|
|
199
|
+
run_phase(:resolve_aggregate_fields, requires: [:resolve_type_aliases, :declare_named_types])
|
|
200
|
+
run_phase(:resolve_enum_members, requires: [:declare_named_types])
|
|
201
|
+
run_phase(:resolve_variant_arms, requires: [:declare_named_types])
|
|
202
|
+
run_phase(:collect_emit_declarations)
|
|
203
|
+
run_phase(:declare_top_level_values, requires: [:resolve_aggregate_fields, :resolve_type_aliases])
|
|
204
|
+
run_phase(:check_attribute_applications, requires: [:declare_attributes])
|
|
205
|
+
run_phase(:declare_functions, requires: [:resolve_aggregate_fields, :resolve_enum_members, :resolve_variant_arms])
|
|
206
|
+
run_phase(:check_interface_conformances, requires: [:declare_functions, :resolve_aggregate_fields])
|
|
207
|
+
run_phase(:check_top_level_values, requires: [:declare_top_level_values])
|
|
208
|
+
run_phase(:finalize_top_level_const_values, requires: [:check_top_level_values])
|
|
209
|
+
run_phase(:check_top_level_static_asserts, requires: [:finalize_top_level_const_values])
|
|
210
|
+
run_phase(:check_functions, requires: [:declare_functions, :resolve_aggregate_fields, :check_interface_conformances])
|
|
208
211
|
|
|
209
212
|
build_analysis
|
|
210
213
|
end
|
|
211
214
|
|
|
215
|
+
def run_phase(name, requires: [])
|
|
216
|
+
requires.each do |required|
|
|
217
|
+
unless @completed_phases&.include?(required)
|
|
218
|
+
raise "BUG: phase #{required} must run before #{name} — check phase ordering"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
send(name)
|
|
222
|
+
ensure
|
|
223
|
+
@completed_phases << name if @completed_phases
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def run_collecting_phase(name, requires: [])
|
|
227
|
+
catch_structural { run_phase(name, requires:) }
|
|
228
|
+
end
|
|
229
|
+
|
|
212
230
|
def collect_emit_declarations
|
|
213
231
|
collect_emit_from_declarations(expanded_declarations)
|
|
214
232
|
@ctx.ast.declarations.grep(AST::ConstDecl).each { |decl| @ctx.const_declarations[decl.name] ||= decl }
|
|
@@ -274,23 +292,24 @@ module MilkTea
|
|
|
274
292
|
def check_collecting_errors
|
|
275
293
|
@collecting_errors = true
|
|
276
294
|
@structural_errors = []
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
295
|
+
@completed_phases = Set.new
|
|
296
|
+
|
|
297
|
+
run_collecting_phase(:install_builtin_types)
|
|
298
|
+
run_collecting_phase(:install_builtin_attributes)
|
|
299
|
+
run_collecting_phase(:install_imports)
|
|
300
|
+
run_collecting_phase(:install_prelude_types, requires: [:install_imports])
|
|
301
|
+
run_collecting_phase(:declare_named_types, requires: [:install_builtin_types, :install_imports, :install_prelude_types])
|
|
302
|
+
run_collecting_phase(:resolve_generic_type_param_constraints, requires: [:declare_named_types])
|
|
303
|
+
run_collecting_phase(:resolve_type_aliases, requires: [:declare_named_types])
|
|
304
|
+
run_collecting_phase(:declare_attributes)
|
|
305
|
+
run_collecting_phase(:resolve_aggregate_fields, requires: [:resolve_type_aliases, :declare_named_types])
|
|
306
|
+
run_collecting_phase(:resolve_enum_members, requires: [:declare_named_types])
|
|
307
|
+
run_collecting_phase(:resolve_variant_arms, requires: [:declare_named_types])
|
|
308
|
+
run_collecting_phase(:collect_emit_declarations)
|
|
309
|
+
run_collecting_phase(:declare_top_level_values, requires: [:resolve_aggregate_fields, :resolve_type_aliases])
|
|
310
|
+
run_collecting_phase(:check_attribute_applications, requires: [:declare_attributes])
|
|
311
|
+
run_collecting_phase(:declare_functions, requires: [:resolve_aggregate_fields, :resolve_enum_members, :resolve_variant_arms])
|
|
312
|
+
run_collecting_phase(:check_interface_conformances, requires: [:declare_functions, :resolve_aggregate_fields])
|
|
294
313
|
|
|
295
314
|
errors = @structural_errors.dup
|
|
296
315
|
|
data/lib/milk_tea/core/types.rb
CHANGED
|
@@ -822,6 +822,18 @@ module MilkTea
|
|
|
822
822
|
self
|
|
823
823
|
end
|
|
824
824
|
|
|
825
|
+
def eql?(other)
|
|
826
|
+
other.is_a?(GenericStructDefinition) && other.name == name &&
|
|
827
|
+
other.type_params == type_params && other.module_name == module_name &&
|
|
828
|
+
other.external == external && other.packed == packed && other.alignment == alignment
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
alias == eql?
|
|
832
|
+
|
|
833
|
+
def hash
|
|
834
|
+
[self.class, name, type_params, module_name, external, packed, alignment].hash
|
|
835
|
+
end
|
|
836
|
+
|
|
825
837
|
def set_layout(packed:, alignment:)
|
|
826
838
|
@packed = packed
|
|
827
839
|
@alignment = alignment
|
|
@@ -853,23 +865,6 @@ module MilkTea
|
|
|
853
865
|
name
|
|
854
866
|
end
|
|
855
867
|
|
|
856
|
-
def eql?(other)
|
|
857
|
-
other.class == self.class &&
|
|
858
|
-
other.name == name &&
|
|
859
|
-
other.type_params == type_params &&
|
|
860
|
-
other.module_name == module_name &&
|
|
861
|
-
other.external == external &&
|
|
862
|
-
other.packed == packed &&
|
|
863
|
-
other.alignment == alignment &&
|
|
864
|
-
other.linkage_name == linkage_name
|
|
865
|
-
end
|
|
866
|
-
|
|
867
|
-
alias == eql?
|
|
868
|
-
|
|
869
|
-
def hash
|
|
870
|
-
[self.class, name, type_params, module_name, external, packed, alignment, linkage_name].hash
|
|
871
|
-
end
|
|
872
|
-
|
|
873
868
|
def instantiate(arguments)
|
|
874
869
|
raise ArgumentError, "#{name} expects #{type_params.length} type arguments, got #{arguments.length}" unless arguments.length == type_params.length
|
|
875
870
|
|
|
@@ -179,6 +179,19 @@ module MilkTea
|
|
|
179
179
|
platform: effective_platform,
|
|
180
180
|
)
|
|
181
181
|
module_name = loader.send(:inferred_module_name_for_path, path) rescue nil
|
|
182
|
+
|
|
183
|
+
# Run the full two-pass program check first so that @analysis_cache
|
|
184
|
+
# has fully-checked analyses for all transitive modules. When the
|
|
185
|
+
# import resolution below encounters a cycle member, it finds the
|
|
186
|
+
# cached analysis and resolves correctly. If the program check fails
|
|
187
|
+
# (e.g. missing module), fall through to the standard resolution
|
|
188
|
+
# path so that prelude modules and regular errors are still reported.
|
|
189
|
+
begin
|
|
190
|
+
loader.check_program_collecting(path)
|
|
191
|
+
rescue ModuleLoadError, PackageLockError
|
|
192
|
+
# best-effort — standard path below handles diagnostics
|
|
193
|
+
end
|
|
194
|
+
|
|
182
195
|
resolution_result = loader.imported_modules_for_ast_collecting_errors(ast, importer_path: path)
|
|
183
196
|
unresolved_import_paths = []
|
|
184
197
|
|
|
@@ -556,9 +556,15 @@ module MilkTea
|
|
|
556
556
|
next_tok&.type == :colon
|
|
557
557
|
end
|
|
558
558
|
|
|
559
|
+
# Keywords whose standalone syntax is "keyword:" — they form blocks or
|
|
560
|
+
# expressions directly with a colon and no intervening token, so they are
|
|
561
|
+
# not field declarations when followed by ":".
|
|
562
|
+
STANDS_ALONE_WITH_COLON = %i[unsafe else defer parallel].to_set.freeze
|
|
563
|
+
|
|
559
564
|
def keyword_field_declaration_token?(tokens, index)
|
|
560
565
|
return false if match_arm_binding_token?(tokens, index)
|
|
561
566
|
return false if destructure_let_binding?(tokens, index)
|
|
567
|
+
return false if STANDS_ALONE_WITH_COLON.include?(tokens[index].type)
|
|
562
568
|
|
|
563
569
|
next_tok = next_non_trivia_token(tokens, index + 1)
|
|
564
570
|
next_tok&.type == :colon
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandBuild
|
|
6
|
+
def build_command
|
|
7
|
+
path, options = extract_path_and_options(allow_clean: true)
|
|
8
|
+
return 1 unless path
|
|
9
|
+
|
|
10
|
+
if options.delete(:clean)
|
|
11
|
+
cleaned_path = Build.clean(path, output_path: options[:output_path], profile: options[:profile], platform: options[:platform], bundle: options[:bundle], archive: options[:archive])
|
|
12
|
+
info("cleaned #{cleaned_path}")
|
|
13
|
+
return 0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
frozen = options.delete(:frozen)
|
|
17
|
+
ensure_current_lockfile!(path) if frozen
|
|
18
|
+
locked = options.delete(:locked)
|
|
19
|
+
bundle = options[:bundle]
|
|
20
|
+
package_graph = package_graph_for(path, locked:)
|
|
21
|
+
result = Build.build(path, module_roots: module_roots_for(path, locked:), package_graph:, frontend: @build_frontend, **options.except(:timings))
|
|
22
|
+
if bundle
|
|
23
|
+
info("built #{path} -> #{File.dirname(result.output_path)}")
|
|
24
|
+
info("entry executable #{result.output_path}")
|
|
25
|
+
info(" [cached]") if result.cached
|
|
26
|
+
info("archive #{result.archive_path}") if result.archive_path
|
|
27
|
+
elsif result.cached
|
|
28
|
+
info("built #{path} -> #{result.output_path} [cached]")
|
|
29
|
+
else
|
|
30
|
+
info("built #{path} -> #{result.output_path}")
|
|
31
|
+
end
|
|
32
|
+
info("saved C to #{result.c_path}") if result.c_path
|
|
33
|
+
0
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandCache
|
|
6
|
+
def cache_command
|
|
7
|
+
subcommand = @argv.shift
|
|
8
|
+
unless subcommand
|
|
9
|
+
@err.puts("missing cache subcommand")
|
|
10
|
+
print_command_help("cache", @err)
|
|
11
|
+
return 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
cache_root = MilkTea.data_root.join("tmp", "mtc-cache")
|
|
15
|
+
|
|
16
|
+
case subcommand
|
|
17
|
+
when "purge"
|
|
18
|
+
if File.directory?(cache_root)
|
|
19
|
+
FileUtils.rm_rf(cache_root)
|
|
20
|
+
@out.puts("purged #{cache_root}")
|
|
21
|
+
else
|
|
22
|
+
@out.puts("cache is already empty")
|
|
23
|
+
end
|
|
24
|
+
0
|
|
25
|
+
when "status"
|
|
26
|
+
unless File.directory?(cache_root)
|
|
27
|
+
@out.puts("cache directory does not exist: #{cache_root}")
|
|
28
|
+
return 0
|
|
29
|
+
end
|
|
30
|
+
program_dirs = Dir.glob(File.join(cache_root, "programs", "*", "*")).select { |d| File.directory?(d) }
|
|
31
|
+
binary_files = Dir.glob(File.join(cache_root, "binaries", "*", "*", "binary")).select { |f| File.file?(f) }
|
|
32
|
+
total_size = (program_dirs + binary_files).sum { |p|
|
|
33
|
+
File.file?(p) ? File.size(p) : Dir.glob(File.join(p, "**", "*")).sum { |f| File.file?(f) ? File.size(f) : 0 }
|
|
34
|
+
}
|
|
35
|
+
@out.puts("cache #{program_dirs.length} programs, #{binary_files.length} binaries (#{format_size(total_size)})")
|
|
36
|
+
@out.puts(" root #{cache_root}")
|
|
37
|
+
0
|
|
38
|
+
else
|
|
39
|
+
@err.puts("unknown cache subcommand #{subcommand}")
|
|
40
|
+
print_command_help("cache", @err)
|
|
41
|
+
1
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandCheck
|
|
6
|
+
def check_command
|
|
7
|
+
args = @argv.dup
|
|
8
|
+
@argv = []
|
|
9
|
+
until args.empty?
|
|
10
|
+
arg = args.shift
|
|
11
|
+
@argv << arg
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
unless @argv.any?
|
|
15
|
+
@err.puts("missing source file path")
|
|
16
|
+
print_usage(@err)
|
|
17
|
+
return 1
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
resolution = extract_resolution_flags!
|
|
21
|
+
input_paths = @argv.dup
|
|
22
|
+
return 1 unless ensure_known_source_operands!("check", input_paths)
|
|
23
|
+
|
|
24
|
+
paths = expand_source_paths(input_paths)
|
|
25
|
+
return 0 if print_no_source_files_if_empty(paths, input_paths)
|
|
26
|
+
|
|
27
|
+
ensure_current_lockfiles!(paths) if resolution[:frozen]
|
|
28
|
+
|
|
29
|
+
all_diagnostics = []
|
|
30
|
+
paths.each do |path|
|
|
31
|
+
diagnostics, module_name, closure_errors = check_single_reporting_all(path, locked: resolution[:locked])
|
|
32
|
+
closure_errors = [] if paths.length > 1
|
|
33
|
+
diagnostics = sort_by_location(diagnostics)
|
|
34
|
+
|
|
35
|
+
if diagnostics.any? || closure_errors.any?
|
|
36
|
+
main_source = read_source_file(path)
|
|
37
|
+
main_abs = File.expand_path(path)
|
|
38
|
+
diagnostics.each do |d|
|
|
39
|
+
same_file = !d.respond_to?(:path) || d.path.nil? || File.expand_path(d.path) == main_abs
|
|
40
|
+
source = same_file ? main_source : nil
|
|
41
|
+
@err.puts(ErrorFormatter.format(d, source:, color: error_color?(@err)))
|
|
42
|
+
end
|
|
43
|
+
closure_errors.each do |d|
|
|
44
|
+
same_file = !d.respond_to?(:path) || d.path.nil? || File.expand_path(d.path) == main_abs
|
|
45
|
+
source = same_file ? main_source : nil
|
|
46
|
+
@err.puts(ErrorFormatter.format(d, source:, color: error_color?(@err)))
|
|
47
|
+
end
|
|
48
|
+
all_diagnostics.concat(diagnostics)
|
|
49
|
+
all_diagnostics.concat(closure_errors)
|
|
50
|
+
elsif module_name
|
|
51
|
+
info("checked #{path} as #{module_name}")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
return 0 if all_diagnostics.empty?
|
|
56
|
+
|
|
57
|
+
error_count = all_diagnostics.count { |d| !d.respond_to?(:severity) || d.severity == :error }
|
|
58
|
+
warning_count = all_diagnostics.count { |d| d.respond_to?(:severity) && d.severity == :warning }
|
|
59
|
+
info_count = all_diagnostics.count { |d| d.respond_to?(:severity) && (d.severity == :info || d.severity == :hint) }
|
|
60
|
+
|
|
61
|
+
@err.puts
|
|
62
|
+
parts = []
|
|
63
|
+
parts << "#{error_count} #{error_count == 1 ? 'error' : 'errors'}" if error_count > 0
|
|
64
|
+
parts << "#{warning_count} #{warning_count == 1 ? 'warning' : 'warnings'}" if warning_count > 0
|
|
65
|
+
parts << "#{info_count} #{info_count == 1 ? 'note' : 'notes'}" if info_count > 0
|
|
66
|
+
body = parts.join("; ")
|
|
67
|
+
if error_count > 0
|
|
68
|
+
@err.puts("#{body} found")
|
|
69
|
+
elsif warning_count > 0
|
|
70
|
+
@err.puts("#{body}")
|
|
71
|
+
end
|
|
72
|
+
final_error_count = error_count + (resolution[:warnings_as_errors] ? warning_count : 0)
|
|
73
|
+
final_error_count > 0 ? 1 : 0
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def check_single_reporting_all(path, locked: false)
|
|
77
|
+
loader = make_module_loader(path, locked:, platform: ModuleLoader.default_host_platform)
|
|
78
|
+
resolved_path = File.expand_path(path)
|
|
79
|
+
|
|
80
|
+
result = loader.check_program_collecting(path)
|
|
81
|
+
errors = result[:errors]
|
|
82
|
+
analysis = result[:root_analysis]
|
|
83
|
+
module_name = result[:module_name]
|
|
84
|
+
|
|
85
|
+
if analysis && errors.empty?
|
|
86
|
+
source = read_source_file(path)
|
|
87
|
+
warnings = Linter.lint_source(source, path: resolved_path, sema_facts: analysis, lint_tier: :full)
|
|
88
|
+
errors.concat(warnings)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
[errors, module_name, []]
|
|
92
|
+
rescue ModuleLoadError, PackageLockError => e
|
|
93
|
+
[[e], nil, []]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def sort_by_location(errors)
|
|
97
|
+
errors.sort_by do |e|
|
|
98
|
+
actual = e.respond_to?(:error) ? e.error : e
|
|
99
|
+
line = actual.respond_to?(:line) ? actual.line.to_i : 0
|
|
100
|
+
column = actual.respond_to?(:column) ? actual.column.to_i : 0
|
|
101
|
+
[line, column]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandCompletions
|
|
6
|
+
def completions_command
|
|
7
|
+
shell = @argv.shift
|
|
8
|
+
unless %w[bash zsh fish].include?(shell)
|
|
9
|
+
@err.puts("completions: shell must be bash, zsh, or fish")
|
|
10
|
+
print_command_help("completions", @err)
|
|
11
|
+
return 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
@out.puts(completion_script(shell))
|
|
15
|
+
0
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def completion_script(shell)
|
|
19
|
+
names = COMMANDS.map(&:first)
|
|
20
|
+
case shell
|
|
21
|
+
when "bash"
|
|
22
|
+
[
|
|
23
|
+
"# mtc bash completion. Source this file or install it into your bash",
|
|
24
|
+
"# completion directory (e.g. /etc/bash_completion.d/mtc).",
|
|
25
|
+
"_mtc() {",
|
|
26
|
+
%( local cur="${COMP_WORDS[COMP_CWORD]}"),
|
|
27
|
+
%( if [ "${COMP_CWORD}" -eq 1 ]; then),
|
|
28
|
+
%( COMPREPLY=( $(compgen -W "#{(names + %w[help version]).join(' ')}" -- "${cur}") )),
|
|
29
|
+
" fi",
|
|
30
|
+
"}",
|
|
31
|
+
"complete -F _mtc mtc",
|
|
32
|
+
].join("\n")
|
|
33
|
+
when "zsh"
|
|
34
|
+
lines = ["#compdef mtc", "# mtc zsh completion. Install onto your $fpath as _mtc.", "_mtc() {", " local -a commands", " commands=("]
|
|
35
|
+
COMMANDS.each { |name, summary| lines << " '#{name}:#{summary}'" }
|
|
36
|
+
lines.concat([" )", " if (( CURRENT == 2 )); then", " _describe 'mtc command' commands", " fi", "}", %(_mtc "$@")])
|
|
37
|
+
lines.join("\n")
|
|
38
|
+
when "fish"
|
|
39
|
+
lines = ["# mtc fish completion. Install into ~/.config/fish/completions/mtc.fish."]
|
|
40
|
+
COMMANDS.each do |name, summary|
|
|
41
|
+
lines << "complete -c mtc -f -n '__fish_use_subcommand' -a #{name} -d '#{summary}'"
|
|
42
|
+
end
|
|
43
|
+
lines.join("\n")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandDap
|
|
6
|
+
def dap_command
|
|
7
|
+
preferred_backend_kind = "process"
|
|
8
|
+
adapter_command = nil
|
|
9
|
+
|
|
10
|
+
until @argv.empty?
|
|
11
|
+
arg = @argv.shift
|
|
12
|
+
case arg
|
|
13
|
+
when "--log-level"
|
|
14
|
+
@argv.shift
|
|
15
|
+
when /\A--log-level=(.+)\z/
|
|
16
|
+
# accept and ignore
|
|
17
|
+
when "--backend"
|
|
18
|
+
preferred_backend_kind = @argv.shift&.downcase
|
|
19
|
+
when /\A--backend=(.+)\z/
|
|
20
|
+
preferred_backend_kind = ::Regexp.last_match(1).downcase
|
|
21
|
+
when "--adapter-path"
|
|
22
|
+
adapter_path = @argv.shift
|
|
23
|
+
adapter_command = resolve_adapter_path(adapter_path)
|
|
24
|
+
return 1 unless adapter_command
|
|
25
|
+
when /\A--adapter-path=(.+)\z/
|
|
26
|
+
adapter_path = ::Regexp.last_match(1)
|
|
27
|
+
adapter_command = resolve_adapter_path(adapter_path)
|
|
28
|
+
return 1 unless adapter_command
|
|
29
|
+
else
|
|
30
|
+
if arg.start_with?("-")
|
|
31
|
+
@err.puts("dap: unknown option #{arg}")
|
|
32
|
+
return 1
|
|
33
|
+
end
|
|
34
|
+
@err.puts("dap: unexpected argument #{arg}")
|
|
35
|
+
return 1
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
require "milk_tea/dap/server" unless defined?(MilkTea::DAP::Server)
|
|
40
|
+
server = MilkTea::DAP::Server.new(
|
|
41
|
+
preferred_backend_kind:,
|
|
42
|
+
adapter_command:,
|
|
43
|
+
)
|
|
44
|
+
server.run
|
|
45
|
+
0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def resolve_adapter_path(adapter_path)
|
|
49
|
+
unless adapter_path && File.file?(adapter_path)
|
|
50
|
+
@err.puts("dap: adapter path not found: #{adapter_path}")
|
|
51
|
+
return nil
|
|
52
|
+
end
|
|
53
|
+
expanded = File.expand_path(adapter_path)
|
|
54
|
+
adapter_path.end_with?(".rb") ? [RbConfig.ruby, expanded] : [expanded]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandDebug
|
|
6
|
+
def debug_command
|
|
7
|
+
unless @argv.any?
|
|
8
|
+
@err.puts("missing source file path")
|
|
9
|
+
print_usage(@err)
|
|
10
|
+
return 1
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
resolution = extract_resolution_flags!
|
|
14
|
+
input_paths = @argv.dup
|
|
15
|
+
return 1 unless ensure_known_source_operands!("debug", input_paths)
|
|
16
|
+
|
|
17
|
+
path = expand_source_paths(input_paths).first
|
|
18
|
+
unless path
|
|
19
|
+
@err.puts("no .mt files found in #{input_paths.join(', ')}")
|
|
20
|
+
return 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
ensure_current_lockfile!(path) if resolution[:frozen]
|
|
24
|
+
|
|
25
|
+
source = read_source_file(path)
|
|
26
|
+
resolved_path = File.expand_path(path)
|
|
27
|
+
|
|
28
|
+
tokens = MilkTea::Lexer.lex(source, path: resolved_path)
|
|
29
|
+
|
|
30
|
+
parse_result = MilkTea::Parser.parse_collecting_errors(source, path: resolved_path)
|
|
31
|
+
ast = parse_result.ast
|
|
32
|
+
parse_errors = parse_result.errors.dup
|
|
33
|
+
|
|
34
|
+
facts = nil
|
|
35
|
+
snapshot = nil
|
|
36
|
+
loader_ast = ast
|
|
37
|
+
|
|
38
|
+
if ast && parse_errors.empty?
|
|
39
|
+
begin
|
|
40
|
+
loader = make_module_loader(path, locked: resolution[:locked], platform: ModuleLoader.default_host_platform)
|
|
41
|
+
loader_ast = loader.load_file(resolved_path)
|
|
42
|
+
|
|
43
|
+
import_result = loader.send(:imported_modules_for_ast_collecting_errors, loader_ast, importer_path: resolved_path)
|
|
44
|
+
import_errors = import_result.respond_to?(:errors) ? import_result.errors : []
|
|
45
|
+
parse_errors.concat(import_errors) unless import_errors.empty?
|
|
46
|
+
|
|
47
|
+
snapshot = MilkTea::SemanticAnalyzer.tooling_snapshot(
|
|
48
|
+
loader_ast,
|
|
49
|
+
imported_modules: import_result.modules,
|
|
50
|
+
allow_missing_imports: true,
|
|
51
|
+
path: resolved_path,
|
|
52
|
+
)
|
|
53
|
+
facts = snapshot&.facts
|
|
54
|
+
rescue MilkTea::LexError, MilkTea::ParseError, ModuleLoadError, SemanticError => e
|
|
55
|
+
parse_errors << e
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
text = DebugInfoFormatter.format_all(
|
|
60
|
+
content: source,
|
|
61
|
+
tokens: tokens,
|
|
62
|
+
ast: loader_ast,
|
|
63
|
+
parse_errors: parse_errors,
|
|
64
|
+
facts: facts,
|
|
65
|
+
snapshot: snapshot,
|
|
66
|
+
path: resolved_path,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@out.puts(text)
|
|
70
|
+
0
|
|
71
|
+
rescue MilkTea::LexError => e
|
|
72
|
+
@err.puts(ErrorFormatter.format(e, color: error_color?(@err)))
|
|
73
|
+
1
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandDeps
|
|
6
|
+
def deps_command
|
|
7
|
+
PackageManagerCLI.start(
|
|
8
|
+
@argv,
|
|
9
|
+
out: @out,
|
|
10
|
+
err: @err,
|
|
11
|
+
help_printer: method(:print_deps_help),
|
|
12
|
+
services: package_services,
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandDocs
|
|
6
|
+
def docs_command
|
|
7
|
+
port = nil
|
|
8
|
+
open_flag = false
|
|
9
|
+
|
|
10
|
+
while (arg = @argv.first)
|
|
11
|
+
case arg
|
|
12
|
+
when "--port", "-p"
|
|
13
|
+
@argv.shift
|
|
14
|
+
port = @argv.shift.to_i
|
|
15
|
+
port = nil if port <= 0 || port > 65535
|
|
16
|
+
when "--open", "-o"
|
|
17
|
+
open_flag = true
|
|
18
|
+
@argv.shift
|
|
19
|
+
else
|
|
20
|
+
break
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
port = resolve_docs_port(port)
|
|
25
|
+
|
|
26
|
+
DocsApp.set :port, port
|
|
27
|
+
DocsApp.set :bind, "127.0.0.1"
|
|
28
|
+
DocsApp.set :environment, :production
|
|
29
|
+
DocsApp.set :server, :puma
|
|
30
|
+
|
|
31
|
+
url = "http://127.0.0.1:#{port}/"
|
|
32
|
+
|
|
33
|
+
@out.puts("Serving Milk Tea docs at #{url}")
|
|
34
|
+
@out.puts("Press Ctrl+C to stop.")
|
|
35
|
+
|
|
36
|
+
if open_flag
|
|
37
|
+
open_browser(url)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
DocsApp.run!
|
|
41
|
+
0
|
|
42
|
+
rescue Interrupt
|
|
43
|
+
0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def resolve_docs_port(preferred)
|
|
47
|
+
return preferred if preferred
|
|
48
|
+
|
|
49
|
+
server = TCPServer.new("127.0.0.1", 0)
|
|
50
|
+
port = server.addr[1]
|
|
51
|
+
server.close
|
|
52
|
+
port
|
|
53
|
+
rescue StandardError
|
|
54
|
+
4567
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|