mt-lang 0.2.0
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 +7 -0
- data/.ruby-version +1 -0
- data/AUTHORS +3 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +82 -0
- data/LICENSE +21 -0
- data/README.md +1208 -0
- data/Rakefile +332 -0
- data/bin/mtc +8 -0
- data/bin/profile-mtc-checks +133 -0
- data/bin/tracy-profiler +0 -0
- data/docs/build-guide.md +536 -0
- data/docs/index.html +2778 -0
- data/docs/language-design.md +1519 -0
- data/docs/language-manual.md +1534 -0
- data/lib/milk_tea/base.rb +49 -0
- data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
- data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
- data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
- data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
- data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
- data/lib/milk_tea/bindings/bindgen.rb +207 -0
- data/lib/milk_tea/bindings/cli.rb +121 -0
- data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
- data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
- data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
- data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
- data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
- data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
- data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
- data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
- data/lib/milk_tea/bindings/steamworks.rb +629 -0
- data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
- data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
- data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
- data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
- data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
- data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
- data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
- data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
- data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
- data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
- data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
- data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
- data/lib/milk_tea/bindings.rb +23 -0
- data/lib/milk_tea/core/ast.rb +311 -0
- data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
- data/lib/milk_tea/core/binding_types.rb +18 -0
- data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
- data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
- data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
- data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
- data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
- data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
- data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
- data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
- data/lib/milk_tea/core/c_backend/statements.rb +563 -0
- data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
- data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
- data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
- data/lib/milk_tea/core/c_backend.rb +287 -0
- data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
- data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
- data/lib/milk_tea/core/compile_time.rb +329 -0
- data/lib/milk_tea/core/control_flow/builder.rb +582 -0
- data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
- data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
- data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
- data/lib/milk_tea/core/control_flow/graph.rb +90 -0
- data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
- data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
- data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
- data/lib/milk_tea/core/control_flow/termination.rb +31 -0
- data/lib/milk_tea/core/control_flow.rb +34 -0
- data/lib/milk_tea/core/cst.rb +48 -0
- data/lib/milk_tea/core/cst_builder.rb +19 -0
- data/lib/milk_tea/core/ir.rb +85 -0
- data/lib/milk_tea/core/keywords.rb +97 -0
- data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
- data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
- data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
- data/lib/milk_tea/core/lexer/indentation.rb +62 -0
- data/lib/milk_tea/core/lexer/numbers.rb +96 -0
- data/lib/milk_tea/core/lexer/recovery.rb +32 -0
- data/lib/milk_tea/core/lexer/strings.rb +167 -0
- data/lib/milk_tea/core/lexer/symbols.rb +71 -0
- data/lib/milk_tea/core/lexer/trivia.rb +53 -0
- data/lib/milk_tea/core/lexer.rb +430 -0
- data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
- data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
- data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
- data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
- data/lib/milk_tea/core/lowering/async.rb +714 -0
- data/lib/milk_tea/core/lowering/block.rb +1052 -0
- data/lib/milk_tea/core/lowering/calls.rb +1565 -0
- data/lib/milk_tea/core/lowering/declarations.rb +214 -0
- data/lib/milk_tea/core/lowering/dyn.rb +206 -0
- data/lib/milk_tea/core/lowering/events.rb +1054 -0
- data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
- data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
- data/lib/milk_tea/core/lowering/functions.rb +242 -0
- data/lib/milk_tea/core/lowering/loops.rb +1087 -0
- data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
- data/lib/milk_tea/core/lowering/proc.rb +419 -0
- data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
- data/lib/milk_tea/core/lowering/scans.rb +220 -0
- data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
- data/lib/milk_tea/core/lowering/utils.rb +1453 -0
- data/lib/milk_tea/core/lowering.rb +378 -0
- data/lib/milk_tea/core/module_binder.rb +181 -0
- data/lib/milk_tea/core/module_loader/errors.rb +21 -0
- data/lib/milk_tea/core/module_loader.rb +479 -0
- data/lib/milk_tea/core/module_path_resolver.rb +153 -0
- data/lib/milk_tea/core/module_roots.rb +88 -0
- data/lib/milk_tea/core/parser/attributes.rb +71 -0
- data/lib/milk_tea/core/parser/blocks.rb +119 -0
- data/lib/milk_tea/core/parser/declarations.rb +749 -0
- data/lib/milk_tea/core/parser/expressions.rb +624 -0
- data/lib/milk_tea/core/parser/recovery.rb +131 -0
- data/lib/milk_tea/core/parser/statements.rb +756 -0
- data/lib/milk_tea/core/parser/types.rb +271 -0
- data/lib/milk_tea/core/parser.rb +400 -0
- data/lib/milk_tea/core/prelude_installer.rb +29 -0
- data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
- data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
- data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
- data/lib/milk_tea/core/pretty_printer.rb +17 -0
- data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
- data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
- data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
- data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
- data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
- data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
- data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
- data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
- data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
- data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
- data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
- data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
- data/lib/milk_tea/core/token.rb +26 -0
- data/lib/milk_tea/core/token_stream.rb +30 -0
- data/lib/milk_tea/core/types/layout.rb +243 -0
- data/lib/milk_tea/core/types/predicates.rb +609 -0
- data/lib/milk_tea/core/types/registry.rb +83 -0
- data/lib/milk_tea/core/types/types.rb +1696 -0
- data/lib/milk_tea/core/types/visitor.rb +442 -0
- data/lib/milk_tea/core.rb +25 -0
- data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
- data/lib/milk_tea/dap/protocol.rb +58 -0
- data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
- data/lib/milk_tea/dap/server/debug_map.rb +291 -0
- data/lib/milk_tea/dap/server/handlers.rb +374 -0
- data/lib/milk_tea/dap/server/launch.rb +261 -0
- data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
- data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
- data/lib/milk_tea/dap/server/utilities.rb +160 -0
- data/lib/milk_tea/dap/server/wire.rb +55 -0
- data/lib/milk_tea/dap/server.rb +131 -0
- data/lib/milk_tea/dap/session.rb +152 -0
- data/lib/milk_tea/dap.rb +6 -0
- data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
- data/lib/milk_tea/lsp/diagnostics.rb +611 -0
- data/lib/milk_tea/lsp/protocol.rb +104 -0
- data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
- data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
- data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
- data/lib/milk_tea/lsp/server/completion.rb +1099 -0
- data/lib/milk_tea/lsp/server/configuration.rb +167 -0
- data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
- data/lib/milk_tea/lsp/server/definition.rb +779 -0
- data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
- data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
- data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
- data/lib/milk_tea/lsp/server/formatting.rb +575 -0
- data/lib/milk_tea/lsp/server/hover.rb +1465 -0
- data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
- data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
- data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
- data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
- data/lib/milk_tea/lsp/server/progress.rb +47 -0
- data/lib/milk_tea/lsp/server/references.rb +433 -0
- data/lib/milk_tea/lsp/server/rename.rb +598 -0
- data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
- data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
- data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
- data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
- data/lib/milk_tea/lsp/server/utilities.rb +520 -0
- data/lib/milk_tea/lsp/server.rb +415 -0
- data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
- data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
- data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
- data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
- data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
- data/lib/milk_tea/lsp/workspace/store.rb +154 -0
- data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
- data/lib/milk_tea/lsp/workspace.rb +127 -0
- data/lib/milk_tea/lsp.rb +13 -0
- data/lib/milk_tea/packages/atomic_write.rb +63 -0
- data/lib/milk_tea/packages/dependency_solver.rb +194 -0
- data/lib/milk_tea/packages/graph.rb +139 -0
- data/lib/milk_tea/packages/lock.rb +421 -0
- data/lib/milk_tea/packages/manager_cli.rb +485 -0
- data/lib/milk_tea/packages/manifest.rb +356 -0
- data/lib/milk_tea/packages/manifest_editor.rb +134 -0
- data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
- data/lib/milk_tea/packages/registry_store.rb +420 -0
- data/lib/milk_tea/packages/services.rb +41 -0
- data/lib/milk_tea/packages/source_cache.rb +71 -0
- data/lib/milk_tea/packages/source_fetcher.rb +124 -0
- data/lib/milk_tea/packages/source_resolver.rb +389 -0
- data/lib/milk_tea/packages/version.rb +164 -0
- data/lib/milk_tea/packages.rb +16 -0
- data/lib/milk_tea/tooling/asset_pack.rb +141 -0
- data/lib/milk_tea/tooling/build.rb +1124 -0
- data/lib/milk_tea/tooling/build_cache.rb +240 -0
- data/lib/milk_tea/tooling/cli.rb +2688 -0
- data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
- data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
- data/lib/milk_tea/tooling/debug_map.rb +248 -0
- data/lib/milk_tea/tooling/docs_app.rb +369 -0
- data/lib/milk_tea/tooling/error_formatter.rb +86 -0
- data/lib/milk_tea/tooling/formatter.rb +787 -0
- data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
- data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
- data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
- data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
- data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
- data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
- data/lib/milk_tea/tooling/linter/rules.rb +593 -0
- data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
- data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
- data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
- data/lib/milk_tea/tooling/linter.rb +798 -0
- data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
- data/lib/milk_tea/tooling/public/css/docs.css +166 -0
- data/lib/milk_tea/tooling/public/js/docs.js +94 -0
- data/lib/milk_tea/tooling/run.rb +408 -0
- data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
- data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
- data/lib/milk_tea/tooling/views/404.erb +7 -0
- data/lib/milk_tea/tooling/views/index.erb +87 -0
- data/lib/milk_tea/tooling/views/layout.erb +69 -0
- data/lib/milk_tea/tooling/views/module.erb +97 -0
- data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
- data/lib/milk_tea/tooling.rb +20 -0
- data/lib/milk_tea.rb +8 -0
- metadata +426 -0
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../core"
|
|
4
|
+
require_relative "cst_formatter"
|
|
5
|
+
|
|
6
|
+
module MilkTea
|
|
7
|
+
class FormatterError < StandardError; end
|
|
8
|
+
|
|
9
|
+
class Formatter
|
|
10
|
+
CheckResult = Data.define(:changed, :formatted_source)
|
|
11
|
+
MODES = %i[safe canonical preserve tidy].freeze
|
|
12
|
+
DEFAULT_MAX_LINE_LENGTH = 120
|
|
13
|
+
|
|
14
|
+
def self.format_source(source, path: nil, mode: :safe, max_line_length: nil, profile: nil)
|
|
15
|
+
validate_mode!(mode)
|
|
16
|
+
|
|
17
|
+
case mode
|
|
18
|
+
when :safe, :canonical
|
|
19
|
+
canonical_format(source, path:, profile:)
|
|
20
|
+
when :preserve
|
|
21
|
+
preserve_format(source, path:, profile:)
|
|
22
|
+
when :tidy
|
|
23
|
+
tidy_format(source, path:, max_line_length: resolve_max_line_length(path, explicit: max_line_length), profile:)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.check_source(source, path: nil, mode: :canonical, max_line_length: nil, profile: nil)
|
|
28
|
+
profile_phase(profile, "format") do
|
|
29
|
+
formatted = format_source(source, path:, mode:, max_line_length:, profile:)
|
|
30
|
+
CheckResult.new(changed: source != formatted, formatted_source: formatted)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.build_cst(source, path: nil)
|
|
35
|
+
CSTBuilder.build(source, path:)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.profile_phase(profile, name)
|
|
39
|
+
return yield unless profile
|
|
40
|
+
|
|
41
|
+
profile.measure(name) { yield }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.canonical_format(source, path:, profile: nil)
|
|
45
|
+
lexed = profile_phase(profile, "format.lex") { Lexer.lex_with_trivia(source, path:) }
|
|
46
|
+
ast = profile_phase(profile, "format.parse") { Parser.parse(source, path:) }
|
|
47
|
+
profile_phase(profile, "format.ast") { PrettyPrinter.format_ast(ast, trivia: lexed.trivia) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.preserve_format(source, path:, profile: nil)
|
|
51
|
+
cst = profile_phase(profile, "format.cst") { build_cst(source, path:) }
|
|
52
|
+
profile_phase(profile, "format.cst_fmt") { CSTFormatter.format(cst) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.tidy_format(source, path:, max_line_length: DEFAULT_MAX_LINE_LENGTH, profile: nil)
|
|
56
|
+
cst = profile_phase(profile, "format.cst") { build_cst(source, path:) }
|
|
57
|
+
normalized = profile_phase(profile, "format.normalize") { CSTFormatter.format_normalized(cst) }
|
|
58
|
+
wrapped = profile_phase(profile, "format.wrap") { wrap_long_argument_lists(normalized, max_line_length:, path:) }
|
|
59
|
+
profile_phase(profile, "format.blank_lines") { normalize_blank_lines(wrapped, path:) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.resolve_max_line_length(path = nil, explicit: nil)
|
|
63
|
+
value = explicit
|
|
64
|
+
if (!value || value.to_i <= 0) && defined?(Linter) && Linter.respond_to?(:load_config)
|
|
65
|
+
value = Linter.load_config(path)&.fetch(:max_line_length, nil)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
value = value.to_i if value
|
|
69
|
+
value&.positive? ? value : DEFAULT_MAX_LINE_LENGTH
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.wrap_long_argument_lists(source, max_line_length: DEFAULT_MAX_LINE_LENGTH, path: nil)
|
|
73
|
+
return source unless max_line_length.to_i.positive?
|
|
74
|
+
|
|
75
|
+
current = source
|
|
76
|
+
100.times do
|
|
77
|
+
lines = current.lines
|
|
78
|
+
tokens = Lexer.lex(current, path:)
|
|
79
|
+
tokens_by_line = non_trivia_tokens_by_line(tokens)
|
|
80
|
+
fix = nil
|
|
81
|
+
|
|
82
|
+
lines.each_index do |line_index|
|
|
83
|
+
next unless lines[line_index].delete_suffix("\n").length > max_line_length
|
|
84
|
+
|
|
85
|
+
fix = build_long_line_wrap_fix(current, line_index, max_line_length:, path:, tokens:, tokens_by_line:)
|
|
86
|
+
break if fix
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
break unless fix
|
|
90
|
+
|
|
91
|
+
updated_lines = current.lines
|
|
92
|
+
updated_lines[fix[:start_line_idx]..fix[:end_line_idx]] = [fix[:new_text]]
|
|
93
|
+
updated = updated_lines.join
|
|
94
|
+
break if updated == current
|
|
95
|
+
|
|
96
|
+
current = updated
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
return current if current == source
|
|
100
|
+
|
|
101
|
+
Parser.parse(current, path:)
|
|
102
|
+
current
|
|
103
|
+
rescue StandardError
|
|
104
|
+
source
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.build_long_line_wrap_fix(source, line_index, max_line_length: DEFAULT_MAX_LINE_LENGTH, path: nil, tokens: nil, tokens_by_line: nil, validate: true)
|
|
108
|
+
return nil unless max_line_length.to_i.positive?
|
|
109
|
+
|
|
110
|
+
lines = source.lines
|
|
111
|
+
return nil unless line_index >= 0 && line_index < lines.length
|
|
112
|
+
return nil if line_inside_external_or_foreign_function_header?(lines, line_index)
|
|
113
|
+
return nil if line_inside_fn_type_signature?(lines, line_index)
|
|
114
|
+
|
|
115
|
+
original_line = lines[line_index]
|
|
116
|
+
line = original_line.delete_suffix("\n")
|
|
117
|
+
return nil if line.length <= max_line_length
|
|
118
|
+
return nil unless wrappable_long_line_candidate_text?(line)
|
|
119
|
+
|
|
120
|
+
tokens ||= Lexer.lex(source, path:)
|
|
121
|
+
line_tokens = tokens_by_line ? tokens_by_line.fetch(line_index + 1, []) : non_trivia_tokens_on_line(tokens, line_index + 1)
|
|
122
|
+
candidates = long_line_wrap_candidates(line_tokens, line)
|
|
123
|
+
|
|
124
|
+
indent = line[/\A\s*/] || ""
|
|
125
|
+
arg_indent = indent + " "
|
|
126
|
+
line_terminator = original_line.end_with?("\n") ? "\n" : ""
|
|
127
|
+
|
|
128
|
+
unless candidates.empty?
|
|
129
|
+
candidates
|
|
130
|
+
.sort_by { |entry| [-(entry[:end_char] - entry[:start_char]), entry[:depth]] }
|
|
131
|
+
.each do |candidate|
|
|
132
|
+
new_text = build_wrapped_delimited_group_text(
|
|
133
|
+
line,
|
|
134
|
+
candidate,
|
|
135
|
+
indent:,
|
|
136
|
+
item_indent: arg_indent,
|
|
137
|
+
line_terminator:,
|
|
138
|
+
)
|
|
139
|
+
next if new_text == original_line
|
|
140
|
+
|
|
141
|
+
updated_lines = lines.dup
|
|
142
|
+
updated_lines[line_index..line_index] = [new_text]
|
|
143
|
+
Parser.parse(updated_lines.join, path:) if validate
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
start_line_idx: line_index,
|
|
147
|
+
end_line_idx: line_index,
|
|
148
|
+
new_text:,
|
|
149
|
+
}
|
|
150
|
+
rescue StandardError
|
|
151
|
+
next if validate
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
logical_chain_fix = build_wrapped_logical_chain_fix(
|
|
156
|
+
lines,
|
|
157
|
+
line_index,
|
|
158
|
+
line,
|
|
159
|
+
line_tokens,
|
|
160
|
+
line_terminator:,
|
|
161
|
+
path:,
|
|
162
|
+
validate:,
|
|
163
|
+
)
|
|
164
|
+
return logical_chain_fix if logical_chain_fix
|
|
165
|
+
|
|
166
|
+
nil
|
|
167
|
+
rescue StandardError
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def self.build_wrapped_delimited_group_text(line, candidate, indent:, item_indent:, line_terminator:)
|
|
172
|
+
new_text = +"#{line[0...candidate[:start_char]]}#{candidate[:opening_delimiter]}\n"
|
|
173
|
+
candidate[:arguments].each_with_index do |argument, index|
|
|
174
|
+
suffix = index < candidate[:arguments].length - 1 ? "," : ""
|
|
175
|
+
new_text << "#{item_indent}#{argument}#{suffix}\n"
|
|
176
|
+
end
|
|
177
|
+
new_text << "#{indent}#{candidate[:closing_delimiter]}#{line[candidate[:end_char]..].to_s}#{line_terminator}"
|
|
178
|
+
new_text
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def self.build_wrapped_logical_chain_fix(lines, line_index, line, line_tokens, line_terminator:, path:, validate: true)
|
|
182
|
+
candidate = logical_chain_wrap_candidate(line_tokens, line)
|
|
183
|
+
return nil unless candidate
|
|
184
|
+
|
|
185
|
+
indent = line[/\A\s*/] || ""
|
|
186
|
+
item_indent = indent + " "
|
|
187
|
+
new_text = build_wrapped_logical_chain_text(
|
|
188
|
+
line,
|
|
189
|
+
candidate,
|
|
190
|
+
indent:,
|
|
191
|
+
item_indent:,
|
|
192
|
+
line_terminator:,
|
|
193
|
+
)
|
|
194
|
+
return nil if new_text == lines[line_index]
|
|
195
|
+
|
|
196
|
+
updated_lines = lines.dup
|
|
197
|
+
updated_lines[line_index..line_index] = [new_text]
|
|
198
|
+
Parser.parse(updated_lines.join, path:) if validate
|
|
199
|
+
|
|
200
|
+
{
|
|
201
|
+
start_line_idx: line_index,
|
|
202
|
+
end_line_idx: line_index,
|
|
203
|
+
new_text:,
|
|
204
|
+
}
|
|
205
|
+
rescue StandardError
|
|
206
|
+
nil
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def self.build_wrapped_logical_chain_text(line, candidate, indent:, item_indent:, line_terminator:)
|
|
210
|
+
new_text = +"#{line[0...candidate[:start_char]]}(\n"
|
|
211
|
+
new_text << "#{item_indent}#{candidate[:segments].first}\n"
|
|
212
|
+
candidate[:operators].each_with_index do |operator, index|
|
|
213
|
+
new_text << "#{item_indent}#{operator} #{candidate[:segments][index + 1]}\n"
|
|
214
|
+
end
|
|
215
|
+
new_text << "#{indent})#{line[candidate[:end_char]..].to_s}#{line_terminator}"
|
|
216
|
+
new_text
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Enforce blank-line rules:
|
|
220
|
+
# - exactly 2 blank lines before top-level function definitions with a body
|
|
221
|
+
# - exactly 2 blank lines before top-level extending blocks
|
|
222
|
+
# - bodyless function declarations: 1 blank line before the first declaration,
|
|
223
|
+
# then 0 blank lines between consecutive declarations
|
|
224
|
+
# - at most 1 blank line everywhere else (constants, variable declarations, expressions)
|
|
225
|
+
# - exactly 1 trailing newline at EOF
|
|
226
|
+
def self.normalize_blank_lines(source, path: nil)
|
|
227
|
+
lines = source.lines(chomp: true)
|
|
228
|
+
result = []
|
|
229
|
+
blank_run = 0
|
|
230
|
+
emitted_content = false
|
|
231
|
+
previous_content_line = nil
|
|
232
|
+
previous_content_index = nil
|
|
233
|
+
previous_top_level_declaration_group = nil
|
|
234
|
+
previous_content_was_bodyful_function = false
|
|
235
|
+
previous_content_was_comment = false
|
|
236
|
+
|
|
237
|
+
lines.each_with_index do |line, line_index|
|
|
238
|
+
if blank_line?(line)
|
|
239
|
+
blank_run += 1
|
|
240
|
+
else
|
|
241
|
+
if emitted_content
|
|
242
|
+
current_top_level_group = top_level_declaration_group(line)
|
|
243
|
+
decorated_function_index = top_level_function_attached_to_attribute_start(lines, line_index)
|
|
244
|
+
needed = if decorated_function_index
|
|
245
|
+
if bodyless_function_header_at?(lines, decorated_function_index)
|
|
246
|
+
1
|
|
247
|
+
else
|
|
248
|
+
2
|
|
249
|
+
end
|
|
250
|
+
elsif previous_content_index && line_inside_attribute_application?(lines, previous_content_index)
|
|
251
|
+
0
|
|
252
|
+
elsif extending_block_header_line?(line)
|
|
253
|
+
if previous_content_was_comment
|
|
254
|
+
1 # 1 blank line between a comment block and an extending block
|
|
255
|
+
else
|
|
256
|
+
2 # exactly 2 blank lines before extending blocks
|
|
257
|
+
end
|
|
258
|
+
elsif function_line?(line)
|
|
259
|
+
current_bodyless_function_header = bodyless_function_header_at?(lines, line_index)
|
|
260
|
+
if current_bodyless_function_header
|
|
261
|
+
if previous_content_index && line_inside_bodyless_function_header?(lines, previous_content_index)
|
|
262
|
+
0 # Keep consecutive declaration-style functions tightly packed.
|
|
263
|
+
elsif previous_content_line && interface_block_header_line?(previous_content_line)
|
|
264
|
+
0 # First interface method should not have leading blank lines.
|
|
265
|
+
else
|
|
266
|
+
1 # Separate declaration-style functions from preceding non-function content.
|
|
267
|
+
end
|
|
268
|
+
elsif previous_content_line && extending_block_header_line?(previous_content_line)
|
|
269
|
+
0 # First method in an extending block should not have leading blank lines.
|
|
270
|
+
elsif previous_content_was_comment
|
|
271
|
+
1 # 1 blank line between a comment block and a function
|
|
272
|
+
elsif previous_content_was_bodyful_function
|
|
273
|
+
2 # 2 blank lines between consecutive function definitions
|
|
274
|
+
else
|
|
275
|
+
2 # 2 blank lines before first function in a scope (after const/struct/enum/import etc.)
|
|
276
|
+
end
|
|
277
|
+
elsif current_top_level_group
|
|
278
|
+
if current_top_level_group == :struct && previous_top_level_declaration_group == :struct
|
|
279
|
+
1
|
|
280
|
+
elsif current_top_level_group == :enum && previous_top_level_declaration_group == :enum
|
|
281
|
+
1
|
|
282
|
+
elsif current_top_level_group == :union && previous_top_level_declaration_group == :union
|
|
283
|
+
1
|
|
284
|
+
elsif previous_top_level_declaration_group && current_top_level_group != previous_top_level_declaration_group
|
|
285
|
+
1
|
|
286
|
+
else
|
|
287
|
+
[blank_run, 1].min
|
|
288
|
+
end
|
|
289
|
+
else
|
|
290
|
+
[blank_run, 1].min # max 1 blank line elsewhere
|
|
291
|
+
end
|
|
292
|
+
needed.times { result << "" }
|
|
293
|
+
end
|
|
294
|
+
result << line
|
|
295
|
+
blank_run = 0
|
|
296
|
+
emitted_content = true
|
|
297
|
+
previous_content_line = line
|
|
298
|
+
previous_content_index = line_index
|
|
299
|
+
current_top_level_group = top_level_declaration_group(line)
|
|
300
|
+
previous_top_level_declaration_group = current_top_level_group if current_top_level_group
|
|
301
|
+
if decorated_function_index
|
|
302
|
+
unless bodyless_function_header_at?(lines, decorated_function_index)
|
|
303
|
+
previous_content_was_bodyful_function = true
|
|
304
|
+
end
|
|
305
|
+
elsif function_line?(line) && !bodyless_function_header_at?(lines, line_index)
|
|
306
|
+
previous_content_was_bodyful_function = true
|
|
307
|
+
elsif current_top_level_group || extending_block_header_line?(line) || interface_block_header_line?(line) || line_is_import?(line) || line_is_comment?(line)
|
|
308
|
+
previous_content_was_bodyful_function = false
|
|
309
|
+
end
|
|
310
|
+
previous_content_was_comment = line_is_comment?(line)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
return "" if result.empty?
|
|
315
|
+
|
|
316
|
+
"#{result.join("\n")}\n"
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def self.function_line?(line)
|
|
320
|
+
bytes = line.bytes
|
|
321
|
+
i = 0
|
|
322
|
+
|
|
323
|
+
i += 1 while i < bytes.length && (bytes[i] == 32 || bytes[i] == 9)
|
|
324
|
+
return false if i >= bytes.length
|
|
325
|
+
|
|
326
|
+
loop do
|
|
327
|
+
word_start = i
|
|
328
|
+
return false unless identifier_head_byte?(bytes[i])
|
|
329
|
+
|
|
330
|
+
i += 1
|
|
331
|
+
i += 1 while i < bytes.length && identifier_tail_byte?(bytes[i])
|
|
332
|
+
word = bytes[word_start...i].pack("C*")
|
|
333
|
+
|
|
334
|
+
if word == "function"
|
|
335
|
+
return i < bytes.length && (bytes[i] == 32 || bytes[i] == 9)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
return false unless i < bytes.length && (bytes[i] == 32 || bytes[i] == 9)
|
|
339
|
+
|
|
340
|
+
i += 1 while i < bytes.length && (bytes[i] == 32 || bytes[i] == 9)
|
|
341
|
+
return false if i >= bytes.length
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def self.blank_line?(line)
|
|
346
|
+
line.empty? || line.bytes.all? { |b| b == 32 || b == 9 }
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def self.identifier_head_byte?(byte)
|
|
350
|
+
(byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122) || byte == 95
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def self.identifier_tail_byte?(byte)
|
|
354
|
+
identifier_head_byte?(byte) || (byte >= 48 && byte <= 57)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def self.bodyless_function_line?(line)
|
|
358
|
+
bytes = line.bytes
|
|
359
|
+
i = bytes.length - 1
|
|
360
|
+
i -= 1 while i >= 0 && (bytes[i] == 32 || bytes[i] == 9)
|
|
361
|
+
return false if i < 0
|
|
362
|
+
|
|
363
|
+
bytes[i] != 58 # ':'
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def self.bodyless_function_header_at?(lines, line_index)
|
|
367
|
+
line = lines[line_index]
|
|
368
|
+
return false unless function_line?(line)
|
|
369
|
+
|
|
370
|
+
return false unless bodyless_function_line?(line)
|
|
371
|
+
|
|
372
|
+
function_indent = leading_indent_width(line)
|
|
373
|
+
cursor = line_index + 1
|
|
374
|
+
|
|
375
|
+
while cursor < lines.length
|
|
376
|
+
candidate = lines[cursor]
|
|
377
|
+
if blank_line?(candidate)
|
|
378
|
+
cursor += 1
|
|
379
|
+
next
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
candidate_indent = leading_indent_width(candidate)
|
|
383
|
+
if candidate_indent < function_indent
|
|
384
|
+
break
|
|
385
|
+
elsif candidate_indent == function_indent && !function_header_continuation_line?(candidate)
|
|
386
|
+
break
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
return false unless bodyless_function_line?(candidate)
|
|
390
|
+
|
|
391
|
+
cursor += 1
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
true
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def self.leading_indent_width(line)
|
|
398
|
+
bytes = line.bytes
|
|
399
|
+
i = 0
|
|
400
|
+
i += 1 while i < bytes.length && (bytes[i] == 32 || bytes[i] == 9)
|
|
401
|
+
i
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def self.function_header_continuation_line?(line)
|
|
405
|
+
stripped = line.strip
|
|
406
|
+
return false if stripped.empty?
|
|
407
|
+
|
|
408
|
+
stripped.start_with?(")", "]", ",", "->")
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def self.line_inside_bodyless_function_header?(lines, line_index)
|
|
412
|
+
start_index = line_index
|
|
413
|
+
while start_index >= 0
|
|
414
|
+
if function_line?(lines[start_index])
|
|
415
|
+
return bodyless_function_header_at?(lines, start_index) && line_in_function_header_span?(lines, start_index, line_index)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
start_index -= 1
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
false
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def self.line_in_function_header_span?(lines, start_index, target_index)
|
|
425
|
+
return false if target_index < start_index
|
|
426
|
+
|
|
427
|
+
function_indent = leading_indent_width(lines[start_index])
|
|
428
|
+
header_end = start_index
|
|
429
|
+
cursor = start_index + 1
|
|
430
|
+
|
|
431
|
+
while cursor < lines.length
|
|
432
|
+
candidate = lines[cursor]
|
|
433
|
+
if blank_line?(candidate)
|
|
434
|
+
cursor += 1
|
|
435
|
+
next
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
candidate_indent = leading_indent_width(candidate)
|
|
439
|
+
if candidate_indent < function_indent
|
|
440
|
+
break
|
|
441
|
+
elsif candidate_indent == function_indent && !function_header_continuation_line?(candidate)
|
|
442
|
+
break
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
header_end = cursor
|
|
446
|
+
cursor += 1
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
target_index <= header_end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def self.line_inside_external_or_foreign_function_header?(lines, line_index)
|
|
453
|
+
start_index = line_index
|
|
454
|
+
while start_index >= 0
|
|
455
|
+
if external_or_foreign_function_header_line?(lines[start_index])
|
|
456
|
+
return line_in_function_header_span?(lines, start_index, line_index)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
break if function_line?(lines[start_index])
|
|
460
|
+
start_index -= 1
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
false
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def self.external_or_foreign_function_header_line?(line)
|
|
467
|
+
line.strip.match?(/\A(?:[A-Za-z_]\w*\s+)*(?:external|foreign)\s+function\b/)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def self.line_inside_fn_type_signature?(lines, line_index)
|
|
471
|
+
start_index = line_index
|
|
472
|
+
while start_index >= 0
|
|
473
|
+
if fn_type_signature_start_line?(lines[start_index])
|
|
474
|
+
return line_in_function_header_span?(lines, start_index, line_index)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
break if function_line?(lines[start_index]) || external_or_foreign_function_header_line?(lines[start_index])
|
|
478
|
+
start_index -= 1
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
false
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def self.fn_type_signature_start_line?(line)
|
|
485
|
+
stripped = line.strip
|
|
486
|
+
return true if stripped.match?(/\Atype\s+[A-Za-z_]\w*\s*=\s*fn\s*\(/)
|
|
487
|
+
|
|
488
|
+
stripped.match?(/\A[A-Za-z_]\w*\s*:\s*fn\s*\(/)
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def self.extending_block_header_line?(line)
|
|
492
|
+
stripped = line.strip
|
|
493
|
+
return false unless stripped.end_with?(":")
|
|
494
|
+
|
|
495
|
+
stripped.start_with?("extending ")
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def self.interface_block_header_line?(line)
|
|
499
|
+
stripped = line.strip
|
|
500
|
+
return false unless stripped.end_with?(":")
|
|
501
|
+
|
|
502
|
+
stripped.start_with?("interface ")
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def self.line_is_import?(line)
|
|
506
|
+
line.strip.start_with?("import ")
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def self.line_is_comment?(line)
|
|
510
|
+
line.strip.start_with?("#")
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def self.attribute_application_line?(line)
|
|
514
|
+
line.strip.start_with?("@[")
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def self.attribute_application_end_line(lines, start_index)
|
|
518
|
+
return nil unless start_index >= 0 && start_index < lines.length
|
|
519
|
+
return nil unless attribute_application_line?(lines[start_index])
|
|
520
|
+
|
|
521
|
+
depth = 0
|
|
522
|
+
open_seen = false
|
|
523
|
+
index = start_index
|
|
524
|
+
|
|
525
|
+
while index < lines.length
|
|
526
|
+
bytes = lines[index].bytes
|
|
527
|
+
char_index = 0
|
|
528
|
+
while char_index < bytes.length
|
|
529
|
+
byte = bytes[char_index]
|
|
530
|
+
if byte == 91
|
|
531
|
+
depth += 1
|
|
532
|
+
open_seen = true
|
|
533
|
+
elsif byte == 93 && depth.positive?
|
|
534
|
+
depth -= 1
|
|
535
|
+
return index if open_seen && depth.zero?
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
char_index += 1
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
index += 1
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
nil
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def self.line_inside_attribute_application?(lines, line_index)
|
|
548
|
+
start_index = line_index
|
|
549
|
+
while start_index >= 0
|
|
550
|
+
if attribute_application_line?(lines[start_index])
|
|
551
|
+
end_index = attribute_application_end_line(lines, start_index)
|
|
552
|
+
return false unless end_index
|
|
553
|
+
|
|
554
|
+
return line_index <= end_index
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
break if function_line?(lines[start_index])
|
|
558
|
+
start_index -= 1
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
false
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def self.top_level_function_attached_to_attribute_start(lines, line_index)
|
|
565
|
+
return nil unless leading_indent_width(lines[line_index]).zero?
|
|
566
|
+
return nil unless attribute_application_line?(lines[line_index])
|
|
567
|
+
return nil if line_index.positive? && line_inside_attribute_application?(lines, line_index - 1)
|
|
568
|
+
|
|
569
|
+
cursor = line_index
|
|
570
|
+
loop do
|
|
571
|
+
end_index = attribute_application_end_line(lines, cursor)
|
|
572
|
+
return nil unless end_index
|
|
573
|
+
|
|
574
|
+
cursor = end_index + 1
|
|
575
|
+
cursor += 1 while cursor < lines.length && blank_line?(lines[cursor])
|
|
576
|
+
return nil if cursor >= lines.length
|
|
577
|
+
|
|
578
|
+
if attribute_application_line?(lines[cursor]) && leading_indent_width(lines[cursor]).zero?
|
|
579
|
+
next
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
return cursor if function_line?(lines[cursor]) && leading_indent_width(lines[cursor]).zero?
|
|
583
|
+
|
|
584
|
+
return nil
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def self.top_level_declaration_group(line)
|
|
589
|
+
stripped = line.strip
|
|
590
|
+
return nil if stripped.empty?
|
|
591
|
+
return nil unless leading_indent_width(line).zero?
|
|
592
|
+
|
|
593
|
+
first_token = stripped[/\A[A-Za-z_][A-Za-z0-9_]*/]
|
|
594
|
+
return nil unless first_token
|
|
595
|
+
|
|
596
|
+
case first_token
|
|
597
|
+
when "link", "include", "compiler_flag"
|
|
598
|
+
:directive
|
|
599
|
+
when "opaque"
|
|
600
|
+
:opaque
|
|
601
|
+
when "type"
|
|
602
|
+
:type
|
|
603
|
+
when "struct"
|
|
604
|
+
:struct
|
|
605
|
+
when "union"
|
|
606
|
+
:union
|
|
607
|
+
when "variant"
|
|
608
|
+
:variant
|
|
609
|
+
when "enum"
|
|
610
|
+
:enum
|
|
611
|
+
when "flags"
|
|
612
|
+
:flags
|
|
613
|
+
when "const"
|
|
614
|
+
:const
|
|
615
|
+
when "var"
|
|
616
|
+
:var
|
|
617
|
+
when "external"
|
|
618
|
+
stripped.match?(/\Aexternal\s+function\b/) ? :external_function : :external
|
|
619
|
+
else
|
|
620
|
+
nil
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def self.long_line_wrap_candidates(line_tokens, line)
|
|
625
|
+
stack = []
|
|
626
|
+
candidates = []
|
|
627
|
+
|
|
628
|
+
line_tokens.each_with_index do |token, index|
|
|
629
|
+
case token.type
|
|
630
|
+
when :lparen, :lbracket
|
|
631
|
+
stack << { token:, index:, depth: stack.length }
|
|
632
|
+
when :rparen, :rbracket
|
|
633
|
+
opening = stack.pop
|
|
634
|
+
next unless opening
|
|
635
|
+
next unless matching_delimiter_pair?(opening[:token].type, token.type)
|
|
636
|
+
|
|
637
|
+
candidate = build_long_line_wrap_candidate(
|
|
638
|
+
line,
|
|
639
|
+
line_tokens,
|
|
640
|
+
opening[:index],
|
|
641
|
+
index,
|
|
642
|
+
opening[:depth],
|
|
643
|
+
)
|
|
644
|
+
candidates << candidate if candidate
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
candidates
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
def self.logical_chain_wrap_candidate(line_tokens, line)
|
|
652
|
+
return nil if line_tokens.length < 4
|
|
653
|
+
|
|
654
|
+
header_length = case line_tokens.first.type
|
|
655
|
+
when :if, :while
|
|
656
|
+
1
|
|
657
|
+
when :else
|
|
658
|
+
line_tokens[1]&.type == :if ? 2 : 0
|
|
659
|
+
else
|
|
660
|
+
0
|
|
661
|
+
end
|
|
662
|
+
return nil if header_length.zero?
|
|
663
|
+
|
|
664
|
+
colon_token = line_tokens.reverse.find { |token| token.type == :colon }
|
|
665
|
+
return nil unless colon_token
|
|
666
|
+
|
|
667
|
+
expression_start_token = line_tokens[header_length]
|
|
668
|
+
return nil unless expression_start_token
|
|
669
|
+
|
|
670
|
+
start_char = expression_start_token.column - 1
|
|
671
|
+
return nil if line[start_char] == "("
|
|
672
|
+
|
|
673
|
+
nested_group_depth = 0
|
|
674
|
+
operators = []
|
|
675
|
+
segments = []
|
|
676
|
+
current_start = start_char
|
|
677
|
+
|
|
678
|
+
line_tokens[header_length...line_tokens.index(colon_token)].each do |token|
|
|
679
|
+
case token.type
|
|
680
|
+
when :lparen, :lbracket
|
|
681
|
+
nested_group_depth += 1
|
|
682
|
+
when :rparen, :rbracket
|
|
683
|
+
nested_group_depth -= 1 if nested_group_depth.positive?
|
|
684
|
+
when :and, :or
|
|
685
|
+
next unless nested_group_depth.zero?
|
|
686
|
+
|
|
687
|
+
segment = line[current_start...(token.column - 1)].to_s.strip
|
|
688
|
+
return nil if segment.empty?
|
|
689
|
+
|
|
690
|
+
segments << segment
|
|
691
|
+
operators << token.lexeme
|
|
692
|
+
current_start = token.column - 1 + token.lexeme.length
|
|
693
|
+
end
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
return nil if operators.empty?
|
|
697
|
+
|
|
698
|
+
final_segment = line[current_start...(colon_token.column - 1)].to_s.strip
|
|
699
|
+
return nil if final_segment.empty?
|
|
700
|
+
|
|
701
|
+
segments << final_segment
|
|
702
|
+
{
|
|
703
|
+
start_char:,
|
|
704
|
+
end_char: colon_token.column - 1,
|
|
705
|
+
operators:,
|
|
706
|
+
segments:,
|
|
707
|
+
}
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
def self.matching_delimiter_pair?(opening_type, closing_type)
|
|
711
|
+
(opening_type == :lparen && closing_type == :rparen) ||
|
|
712
|
+
(opening_type == :lbracket && closing_type == :rbracket)
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
def self.build_long_line_wrap_candidate(line, line_tokens, open_index, close_index, depth)
|
|
716
|
+
open_token = line_tokens[open_index]
|
|
717
|
+
close_token = line_tokens[close_index]
|
|
718
|
+
nested_group_depth = 0
|
|
719
|
+
comma_tokens = []
|
|
720
|
+
|
|
721
|
+
(open_index + 1...close_index).each do |index|
|
|
722
|
+
token = line_tokens[index]
|
|
723
|
+
case token.type
|
|
724
|
+
when :lparen, :lbracket
|
|
725
|
+
nested_group_depth += 1
|
|
726
|
+
when :rparen, :rbracket
|
|
727
|
+
nested_group_depth -= 1 if nested_group_depth.positive?
|
|
728
|
+
when :comma
|
|
729
|
+
comma_tokens << token if nested_group_depth.zero? && token.line == open_token.line
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
return nil if comma_tokens.empty?
|
|
734
|
+
|
|
735
|
+
start_char = open_token.column - 1
|
|
736
|
+
end_char = close_token.column
|
|
737
|
+
current_start = start_char + 1
|
|
738
|
+
arguments = []
|
|
739
|
+
|
|
740
|
+
comma_tokens.each do |comma|
|
|
741
|
+
argument = line[current_start...(comma.column - 1)].to_s.strip
|
|
742
|
+
return nil if argument.empty?
|
|
743
|
+
|
|
744
|
+
arguments << argument
|
|
745
|
+
current_start = comma.column
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
final_argument = line[current_start...(close_token.column - 1)].to_s.strip
|
|
749
|
+
return nil if final_argument.empty?
|
|
750
|
+
|
|
751
|
+
arguments << final_argument
|
|
752
|
+
{
|
|
753
|
+
depth:,
|
|
754
|
+
start_char:,
|
|
755
|
+
end_char:,
|
|
756
|
+
opening_delimiter: open_token.lexeme,
|
|
757
|
+
closing_delimiter: close_token.lexeme,
|
|
758
|
+
arguments:,
|
|
759
|
+
}
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
def self.wrappable_long_line_candidate_text?(line)
|
|
763
|
+
return true if line.include?("(") || line.include?("[")
|
|
764
|
+
return true if line.include?(" and ") || line.include?(" or ")
|
|
765
|
+
|
|
766
|
+
false
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
def self.non_trivia_tokens_on_line(tokens, line_number)
|
|
770
|
+
tokens.select { |token| token.line == line_number && !%i[newline indent dedent eof].include?(token.type) }
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
def self.non_trivia_tokens_by_line(tokens)
|
|
774
|
+
tokens.each_with_object(Hash.new { |hash, line| hash[line] = [] }) do |token, by_line|
|
|
775
|
+
next if %i[newline indent dedent eof].include?(token.type)
|
|
776
|
+
|
|
777
|
+
by_line[token.line] << token
|
|
778
|
+
end
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
def self.validate_mode!(mode)
|
|
782
|
+
return if MODES.include?(mode)
|
|
783
|
+
|
|
784
|
+
raise FormatterError, "unknown formatter mode #{mode.inspect}"
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
end
|