mt-lang 0.3.17 → 0.3.20
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/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 +10 -3
- data/lib/milk_tea/core/lowering/scans.rb +13 -18
- data/lib/milk_tea/core/module_binder.rb +9 -10
- data/lib/milk_tea/core/module_loader.rb +38 -42
- data/lib/milk_tea/core/module_path_resolver.rb +1 -4
- data/lib/milk_tea/core/parser/declarations.rb +43 -19
- data/lib/milk_tea/core/parser/expressions.rb +4 -7
- 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 +117 -85
- 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/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 +116 -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
- metadata +24 -2
|
@@ -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,116 @@
|
|
|
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
|
+
ast = loader.load_file(resolved_path)
|
|
80
|
+
module_name = ast.module_name.to_s
|
|
81
|
+
|
|
82
|
+
import_result = loader.imported_modules_for_ast_collecting_errors(ast, importer_path: resolved_path)
|
|
83
|
+
errors = import_result.errors.dup
|
|
84
|
+
|
|
85
|
+
analysis = nil
|
|
86
|
+
begin
|
|
87
|
+
result = SemanticAnalyzer.check_collecting_errors(ast, imported_modules: import_result.modules, path: resolved_path)
|
|
88
|
+
errors.concat(result[:errors])
|
|
89
|
+
analysis = result[:analysis]
|
|
90
|
+
rescue SemanticError => e
|
|
91
|
+
errors << e
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if analysis && errors.empty?
|
|
95
|
+
source = read_source_file(path)
|
|
96
|
+
warnings = Linter.lint_source(source, path: resolved_path, sema_facts: analysis, lint_tier: :full)
|
|
97
|
+
errors.concat(warnings)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
closure_errors = loader.collecting_path_errors.values.flatten.compact
|
|
101
|
+
[errors, module_name, closure_errors]
|
|
102
|
+
rescue ModuleLoadError, PackageLockError, SemanticError => e
|
|
103
|
+
[[e], nil, []]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def sort_by_location(errors)
|
|
107
|
+
errors.sort_by do |e|
|
|
108
|
+
actual = e.respond_to?(:error) ? e.error : e
|
|
109
|
+
line = actual.respond_to?(:line) ? actual.line.to_i : 0
|
|
110
|
+
column = actual.respond_to?(:column) ? actual.column.to_i : 0
|
|
111
|
+
[line, column]
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
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
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandEmitC
|
|
6
|
+
def emit_c_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!("emit-c", input_paths)
|
|
23
|
+
|
|
24
|
+
program_paths = input_paths.map { |p| resolve_program_path(p) }
|
|
25
|
+
return 1 if program_paths.include?(nil)
|
|
26
|
+
|
|
27
|
+
ensure_current_lockfiles!(input_paths) if resolution[:frozen]
|
|
28
|
+
|
|
29
|
+
multiple = program_paths.length > 1
|
|
30
|
+
program_paths.each_with_index do |path, index|
|
|
31
|
+
program = make_module_loader(path, locked: resolution[:locked], platform: ModuleLoader.default_host_platform).check_program(path)
|
|
32
|
+
if multiple
|
|
33
|
+
@out.puts("/* --- #{path} --- */")
|
|
34
|
+
end
|
|
35
|
+
@out.write(CBackend.generate_c(Lowering.lower(program), emit_line_directives: false))
|
|
36
|
+
@out.puts if multiple && index < program_paths.length - 1
|
|
37
|
+
end
|
|
38
|
+
0
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def resolve_program_path(path)
|
|
42
|
+
return path unless File.directory?(path)
|
|
43
|
+
|
|
44
|
+
manifest_path = File.join(path, "package.toml")
|
|
45
|
+
unless File.file?(manifest_path)
|
|
46
|
+
@err.puts("no package.toml found in #{path}")
|
|
47
|
+
return nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
manifest = PackageManifest.load(path)
|
|
51
|
+
entry_path = manifest.source_path
|
|
52
|
+
unless entry_path && File.file?(entry_path)
|
|
53
|
+
@err.puts("no build entry found for #{path}")
|
|
54
|
+
return nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
entry_path
|
|
58
|
+
rescue PackageManifestError => e
|
|
59
|
+
@err.puts("failed to load package manifest for #{path}: #{e.message}")
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|