kimera 0.1.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.
Files changed (171) hide show
  1. checksums.yaml +7 -0
  2. data/.kimera.yml.example +59 -0
  3. data/CHANGELOG.md +36 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +528 -0
  6. data/exe/kimera +22 -0
  7. data/lib/kimera/audit/operator_audit.rb +71 -0
  8. data/lib/kimera/cli/argv.rb +15 -0
  9. data/lib/kimera/cli/baseline.rb +87 -0
  10. data/lib/kimera/cli/changed.rb +27 -0
  11. data/lib/kimera/cli/ci.rb +27 -0
  12. data/lib/kimera/cli/completion.rb +37 -0
  13. data/lib/kimera/cli/doctor.rb +115 -0
  14. data/lib/kimera/cli/flag.rb +55 -0
  15. data/lib/kimera/cli/help_text.rb +45 -0
  16. data/lib/kimera/cli/init.rb +105 -0
  17. data/lib/kimera/cli/mutant.rb +82 -0
  18. data/lib/kimera/cli/report_file.rb +20 -0
  19. data/lib/kimera/cli/run/arguments.rb +58 -0
  20. data/lib/kimera/cli/run/cycle.rb +78 -0
  21. data/lib/kimera/cli/run/digest.rb +49 -0
  22. data/lib/kimera/cli/run/emission.rb +45 -0
  23. data/lib/kimera/cli/run/gate.rb +43 -0
  24. data/lib/kimera/cli/run/options.rb +85 -0
  25. data/lib/kimera/cli/run/pass.rb +100 -0
  26. data/lib/kimera/cli/run/sources.rb +56 -0
  27. data/lib/kimera/cli/run.rb +52 -0
  28. data/lib/kimera/cli/skill.rb +23 -0
  29. data/lib/kimera/cli/suggestion.rb +36 -0
  30. data/lib/kimera/cli/survivors/panel.rb +81 -0
  31. data/lib/kimera/cli/survivors.rb +89 -0
  32. data/lib/kimera/cli/synthesize.rb +66 -0
  33. data/lib/kimera/cli/workflows.rb +8 -0
  34. data/lib/kimera/cli.rb +132 -0
  35. data/lib/kimera/error.rb +3 -0
  36. data/lib/kimera/execution/baseline_failure.rb +47 -0
  37. data/lib/kimera/execution/baseline_pass.rb +123 -0
  38. data/lib/kimera/execution/boot.rb +50 -0
  39. data/lib/kimera/execution/callback.rb +33 -0
  40. data/lib/kimera/execution/child_process.rb +48 -0
  41. data/lib/kimera/execution/composite_isolation.rb +24 -0
  42. data/lib/kimera/execution/harness.rb +150 -0
  43. data/lib/kimera/execution/isolated.rb +64 -0
  44. data/lib/kimera/execution/isolated_child.rb +15 -0
  45. data/lib/kimera/execution/isolated_child_command.rb +44 -0
  46. data/lib/kimera/execution/isolated_child_minitest.rb +34 -0
  47. data/lib/kimera/execution/isolated_plan.rb +54 -0
  48. data/lib/kimera/execution/isolated_scheduling.rb +54 -0
  49. data/lib/kimera/execution/isolated_test_selection.rb +22 -0
  50. data/lib/kimera/execution/isolated_verdict.rb +72 -0
  51. data/lib/kimera/execution/isolated_watchdog.rb +59 -0
  52. data/lib/kimera/execution/isolation.rb +17 -0
  53. data/lib/kimera/execution/null_progress.rb +17 -0
  54. data/lib/kimera/execution/overlay_guard_modules.rb +95 -0
  55. data/lib/kimera/execution/overlay_guards.rb +56 -0
  56. data/lib/kimera/execution/parallel_test_databases.rb +64 -0
  57. data/lib/kimera/execution/pool_driver.rb +101 -0
  58. data/lib/kimera/execution/priority.rb +23 -0
  59. data/lib/kimera/execution/reload.rb +104 -0
  60. data/lib/kimera/execution/rig.rb +41 -0
  61. data/lib/kimera/execution/schedule.rb +105 -0
  62. data/lib/kimera/execution/schemata.rb +88 -0
  63. data/lib/kimera/execution/shift/attempt.rb +34 -0
  64. data/lib/kimera/execution/shift/coverage_channel.rb +43 -0
  65. data/lib/kimera/execution/shift/killer_memory.rb +20 -0
  66. data/lib/kimera/execution/shift/leak_guard.rb +34 -0
  67. data/lib/kimera/execution/shift.rb +137 -0
  68. data/lib/kimera/execution/sweep.rb +30 -0
  69. data/lib/kimera/execution/transaction_isolation.rb +32 -0
  70. data/lib/kimera/execution/trial.rb +16 -0
  71. data/lib/kimera/execution/verdicts.rb +78 -0
  72. data/lib/kimera/execution/worker_pool/fleet.rb +104 -0
  73. data/lib/kimera/execution/worker_pool/stillborn_guard.rb +40 -0
  74. data/lib/kimera/execution/worker_pool/worker.rb +32 -0
  75. data/lib/kimera/execution/worker_pool.rb +101 -0
  76. data/lib/kimera/frameworks/adapter.rb +82 -0
  77. data/lib/kimera/frameworks/minitest_adapter.rb +102 -0
  78. data/lib/kimera/frameworks/rspec_adapter.rb +123 -0
  79. data/lib/kimera/frameworks/rspec_group_index.rb +45 -0
  80. data/lib/kimera/incremental/git_diff.rb +98 -0
  81. data/lib/kimera/incremental/selection.rb +27 -0
  82. data/lib/kimera/incremental/session.rb +109 -0
  83. data/lib/kimera/memoization.rb +104 -0
  84. data/lib/kimera/operators/argument_drop.rb +19 -0
  85. data/lib/kimera/operators/arithmetic.rb +12 -0
  86. data/lib/kimera/operators/audit.rb +3 -0
  87. data/lib/kimera/operators/base.rb +40 -0
  88. data/lib/kimera/operators/binary_swap.rb +23 -0
  89. data/lib/kimera/operators/boolean.rb +26 -0
  90. data/lib/kimera/operators/chain_link_deletion.rb +25 -0
  91. data/lib/kimera/operators/collection_literal.rb +18 -0
  92. data/lib/kimera/operators/comparison.rb +13 -0
  93. data/lib/kimera/operators/conditional.rb +36 -0
  94. data/lib/kimera/operators/default_argument.rb +17 -0
  95. data/lib/kimera/operators/element_drop.rb +31 -0
  96. data/lib/kimera/operators/index_fetch.rb +15 -0
  97. data/lib/kimera/operators/kernel_coercion.rb +17 -0
  98. data/lib/kimera/operators/method_unwrap.rb +20 -0
  99. data/lib/kimera/operators/negation.rb +15 -0
  100. data/lib/kimera/operators/node_swap.rb +20 -0
  101. data/lib/kimera/operators/numeric_literal.rb +39 -0
  102. data/lib/kimera/operators/op_assign.rb +18 -0
  103. data/lib/kimera/operators/plugins.rb +27 -0
  104. data/lib/kimera/operators/rails_association.rb +24 -0
  105. data/lib/kimera/operators/rails_callback.rb +49 -0
  106. data/lib/kimera/operators/rails_declaration.rb +16 -0
  107. data/lib/kimera/operators/rails_permit.rb +34 -0
  108. data/lib/kimera/operators/rails_validation.rb +17 -0
  109. data/lib/kimera/operators/range.rb +15 -0
  110. data/lib/kimera/operators/regexp_literal.rb +16 -0
  111. data/lib/kimera/operators/respond_to_guard.rb +24 -0
  112. data/lib/kimera/operators/return_value.rb +36 -0
  113. data/lib/kimera/operators/safe_navigation.rb +15 -0
  114. data/lib/kimera/operators/selector_swap.rb +24 -0
  115. data/lib/kimera/operators/statement_deletion.rb +26 -0
  116. data/lib/kimera/operators/string_literal.rb +22 -0
  117. data/lib/kimera/operators/symbol_literal.rb +26 -0
  118. data/lib/kimera/operators/vocabulary.rb +82 -0
  119. data/lib/kimera/operators.rb +141 -0
  120. data/lib/kimera/plugins.rb +3 -0
  121. data/lib/kimera/registry/builder.rb +59 -0
  122. data/lib/kimera/registry/mutation_point.rb +146 -0
  123. data/lib/kimera/registry/numbering.rb +15 -0
  124. data/lib/kimera/registry/registry.rb +120 -0
  125. data/lib/kimera/registry/source_file.rb +71 -0
  126. data/lib/kimera/registry/tally.rb +11 -0
  127. data/lib/kimera/registry/walking.rb +55 -0
  128. data/lib/kimera/report/actions.rb +38 -0
  129. data/lib/kimera/report/coloring.rb +25 -0
  130. data/lib/kimera/report/formats.rb +78 -0
  131. data/lib/kimera/report/live.rb +56 -0
  132. data/lib/kimera/report/log.rb +46 -0
  133. data/lib/kimera/report/progress.rb +55 -0
  134. data/lib/kimera/report/screen.rb +44 -0
  135. data/lib/kimera/report/sections.rb +50 -0
  136. data/lib/kimera/report/tally.rb +84 -0
  137. data/lib/kimera/report/text.rb +104 -0
  138. data/lib/kimera/results/result.rb +72 -0
  139. data/lib/kimera/results/run_report.rb +114 -0
  140. data/lib/kimera/rewrite/ast_walk.rb +20 -0
  141. data/lib/kimera/rewrite/directive.rb +61 -0
  142. data/lib/kimera/rewrite/directive_extra_handlers.rb +52 -0
  143. data/lib/kimera/rewrite/directive_handlers.rb +138 -0
  144. data/lib/kimera/rewrite/directive_kwarg_handlers.rb +44 -0
  145. data/lib/kimera/rewrite/numbered_params.rb +50 -0
  146. data/lib/kimera/runtime.rb +51 -0
  147. data/lib/kimera/scope/config.rb +90 -0
  148. data/lib/kimera/scope/file_set.rb +51 -0
  149. data/lib/kimera/scope/ignore_list.rb +55 -0
  150. data/lib/kimera/self_protection.rb +56 -0
  151. data/lib/kimera/support/duration.rb +13 -0
  152. data/lib/kimera/support/errors.rb +8 -0
  153. data/lib/kimera/support/operator_protocol.rb +10 -0
  154. data/lib/kimera/support/syntax.rb +68 -0
  155. data/lib/kimera/support/syntax_types.rb +20 -0
  156. data/lib/kimera/support/unparse.rb +15 -0
  157. data/lib/kimera/support/version.rb +5 -0
  158. data/lib/kimera/support/warnings.rb +15 -0
  159. data/lib/kimera/synthesis/file_weave.rb +78 -0
  160. data/lib/kimera/synthesis/guard_weaver.rb +77 -0
  161. data/lib/kimera/synthesis/guardrail_dispatch.rb +68 -0
  162. data/lib/kimera/synthesis/guardrail_interpolation.rb +59 -0
  163. data/lib/kimera/synthesis/guardrail_value_objects.rb +46 -0
  164. data/lib/kimera/synthesis/overlay.rb +52 -0
  165. data/lib/kimera/synthesis/overlay_splice.rb +101 -0
  166. data/lib/kimera/synthesis/project.rb +58 -0
  167. data/lib/kimera/synthesis/source_map.rb +42 -0
  168. data/lib/kimera/version.rb +3 -0
  169. data/lib/kimera.rb +13 -0
  170. data/skills/kimera/SKILL.md +118 -0
  171. metadata +259 -0
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../rewrite/directive"
4
+ require_relative "coloring"
5
+ require_relative "screen"
6
+ require_relative "sections"
7
+ require_relative "actions"
8
+
9
+ module Kimera
10
+ module Report
11
+ end
12
+ end
13
+
14
+ class Kimera::Report::Text
15
+ include Kimera::Report::Coloring
16
+ include Kimera::Report::Sections
17
+
18
+ MAX_TESTS_SHOWN = 5
19
+
20
+ def initialize(registry, io: $stdout, color: nil)
21
+ @registry = registry
22
+ @io = io
23
+ @color = color
24
+ end
25
+
26
+ COVERAGE_SECTIONS = { hint: :hint, list: :missing }.freeze
27
+
28
+ def report(report, scope: nil, coverage: :hint, path: nil)
29
+ header(report, scope)
30
+ survivors(report)
31
+ __send__(COVERAGE_SECTIONS.fetch(coverage), report, path)
32
+ leaks(report.leaks)
33
+ Kimera::Report::Actions.new(io: @io).show(report, path: path)
34
+ end
35
+
36
+ private
37
+
38
+ def header(report, scope)
39
+ @io.puts(scope) if scope
40
+ @io.puts(summary(report))
41
+ end
42
+
43
+ def survivors(report)
44
+ section("Surviving mutants", report.survived) { |result| survivor(result) }
45
+ unjudged(report)
46
+ end
47
+
48
+ def summary(report)
49
+ bold(report.summary)
50
+ end
51
+
52
+ def section(title, results, &)
53
+ heading(title, results.sort_by(&:mutant_id)).each(&)
54
+ end
55
+
56
+ def heading(title, listed)
57
+ return listed if listed.empty?
58
+ @io.puts
59
+ @io.puts(bold("#{title} (#{listed.size}):"))
60
+ listed
61
+ end
62
+
63
+ def survivor(result)
64
+ resolved(result) { |id, mutant, point| display(id, mutant, point, result) }
65
+ end
66
+
67
+ def display(id, mutant, point, result)
68
+ @io.puts
69
+ spotted("survived", id, mutant, point)
70
+ diff(point, mutant)
71
+ covering(result)
72
+ end
73
+
74
+ def resolved(result)
75
+ id = result.mutant_id
76
+ pair = @registry.point(id)
77
+ yield(id, *pair) if pair
78
+ end
79
+
80
+ def diff(point, mutant)
81
+ original = point.original_source
82
+ variant = Kimera::Rewrite::Directive.render(original, mutant.directive)
83
+ source(red("-"), original)
84
+ source(green("+"), variant) if variant
85
+ end
86
+
87
+ def source(sign, text)
88
+ first, *rest = text.split("\n")
89
+ @io.puts(" #{sign} #{first}")
90
+ rest.each { |line| @io.puts(" #{line}") }
91
+ end
92
+
93
+ def covering(result)
94
+ tests = Array(result.covering_tests)
95
+ return if tests.empty?
96
+ @io.puts(" covered by #{tests.size} test(s): #{names(tests)}")
97
+ end
98
+
99
+ def names(tests)
100
+ shown = tests.first(MAX_TESTS_SHOWN)
101
+ more = tests.size - shown.size
102
+ "#{shown.join(", ")}#{" (+#{more} more)" if more.positive?}"
103
+ end
104
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Status
5
+ KILLING = %i[killed timeout error].freeze
6
+
7
+ UNJUDGED = %i[no_coverage ignored isolated_only harness_error].freeze
8
+
9
+ REPORTED = ([:survived] + (KILLING - [:killed]) + %i[no_coverage harness_error ignored isolated_only]).freeze
10
+
11
+ SELF_SUSPECT = ((KILLING - [:killed]) + [:survived]).freeze
12
+ end
13
+
14
+ MutantResult =
15
+ Struct.new(
16
+ :mutant_id, :status, :file, :duration, :failing_tests, :covering_tests,
17
+ :detail,
18
+ keyword_init: true
19
+ ) do
20
+ def duration
21
+ self[:duration] || 0.0
22
+ end
23
+
24
+ def killed?
25
+ Status::KILLING.include?(status)
26
+ end
27
+
28
+ def isolated(detail)
29
+ self.class.new(mutant_id: mutant_id, status: :isolated_only, file: file, duration: duration, detail: detail)
30
+ end
31
+
32
+ def covered?
33
+ !Status::UNJUDGED.include?(status)
34
+ end
35
+
36
+ def message
37
+ { t: "result", id: mutant_id, status: status.to_s }
38
+ .merge(ms: duration, fails: failing_tests, cover: covering_tests)
39
+ end
40
+
41
+ def to_h
42
+ identity.merge(outcome)
43
+ end
44
+
45
+ def identity
46
+ { "mutant_id" => mutant_id, "status" => status.to_s, "file" => file }
47
+ end
48
+
49
+ def outcome
50
+ { "duration" => duration, "failing_tests" => failing_tests }
51
+ .merge("covering_tests" => covering_tests, "detail" => detail)
52
+ end
53
+ class << self
54
+ def waived(id, file)
55
+ new(mutant_id: id, status: :ignored, file: file, detail: "marked equivalent (ignored)")
56
+ end
57
+
58
+ def from_h(hash)
59
+ new(**identity(hash), **outcome(hash))
60
+ end
61
+
62
+ def identity(hash)
63
+ { mutant_id: hash["mutant_id"], status: hash["status"].to_sym, file: hash["file"] }
64
+ end
65
+
66
+ def outcome(hash)
67
+ { duration: hash["duration"], failing_tests: hash["failing_tests"] }
68
+ .merge(covering_tests: hash["covering_tests"], detail: hash["detail"])
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../rewrite/directive"
4
+ require_relative "result"
5
+
6
+ module Kimera
7
+ LeakReport = Struct.new(:mutant_id, :detail, keyword_init: true)
8
+ end
9
+
10
+ class Kimera::RunReport
11
+ attr_reader :results, :leaks, :registry
12
+
13
+ CONDITIONAL = %i[harness_error ignored isolated_only leaks].freeze
14
+ LABELS = { total: "mutants", harness_error: "unjudged" }.freeze
15
+ SCHEMA_VERSION = 1
16
+
17
+ def initialize(results:, leaks: [], registry: nil)
18
+ @results = results
19
+ @leaks = leaks
20
+ @registry = registry
21
+ end
22
+
23
+ def statuses(status)
24
+ grouped.fetch(status, [])
25
+ end
26
+
27
+ def merge(other)
28
+ Kimera::RunReport.new(results: @results + other.results, leaks: @leaks, registry: @registry)
29
+ end
30
+
31
+ def killed
32
+ @results.select(&:killed?)
33
+ end
34
+
35
+ def survived
36
+ statuses(:survived)
37
+ end
38
+
39
+ def uncovered
40
+ statuses(:no_coverage)
41
+ end
42
+
43
+ def errors
44
+ statuses(:harness_error)
45
+ end
46
+
47
+ def covered
48
+ @results.select(&:covered?)
49
+ end
50
+
51
+ def score
52
+ total = covered.size
53
+ return 1.0 if total.zero?
54
+ Float(killed.size) / total
55
+ end
56
+
57
+ def counts
58
+ { total: @results.size, killed: killed.size, leaks: @leaks.size, **reported }
59
+ end
60
+
61
+ def reported
62
+ Kimera::Status::REPORTED.to_h { |status| [status, statuses(status).size] }
63
+ end
64
+
65
+ def summary
66
+ tallies = counts
67
+ tallies.keys.filter_map { |key| labeled(key) }.insert(tallies.size - CONDITIONAL.size, scoreline).join(" ")
68
+ end
69
+
70
+ def labeled(key)
71
+ count = counts[key]
72
+ return if CONDITIONAL.include?(key) && count.zero?
73
+ "#{LABELS.fetch(key, key)}=#{count}"
74
+ end
75
+
76
+ def document(metadata) = metadata ? to_h.merge("run" => metadata) : to_h
77
+
78
+ def to_h
79
+ { "schema_version" => SCHEMA_VERSION, "counts" => counts, "mutation_score" => score }
80
+ .merge("results" => @results.map { |result| detailed(result) }, "leaks" => @leaks.map(&:to_h))
81
+ end
82
+
83
+ private
84
+
85
+ def scoreline
86
+ return "score=n/a (0 of #{@results.size} mutants evaluated)" if covered.empty? && !@results.empty?
87
+ text = format("score=%.1f%%", score * 100)
88
+ count = uncovered.size
89
+ count.positive? ? "#{text} (#{count} uncovered not scored)" : text
90
+ end
91
+
92
+ def grouped
93
+ @_grouped ||= @results.group_by(&:status)
94
+ end
95
+
96
+ def detailed(result)
97
+ base = result.to_h
98
+ pair = @registry&.point(result.mutant_id)
99
+ pair ? base.merge(rewritten(pair)) : base
100
+ end
101
+
102
+ def rewritten(pair)
103
+ mutant, point = pair
104
+ place(point, mutant).merge(rendered(point.original_source, mutant))
105
+ end
106
+
107
+ def rendered(original, mutant)
108
+ { "original" => original, "mutated" => Kimera::Rewrite::Directive.render(original, mutant.directive) }
109
+ end
110
+
111
+ def place(point, mutant)
112
+ { "line" => point.location.start_line, "operator" => point.operator, "label" => mutant.label }
113
+ end
114
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Rewrite
5
+ module AstWalk
6
+ module_function
7
+
8
+ def rebuild(node, &)
9
+ return node unless node.is_a?(Parser::AST::Node)
10
+ yield(node.updated(nil, node.children.map { |child| rebuild(child, &) }), node)
11
+ end
12
+
13
+ def visit(node, &)
14
+ return unless node.is_a?(Parser::AST::Node)
15
+ yield(node)
16
+ node.children.each { |child| visit(child, &) }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../support/operator_protocol"
4
+ require_relative "../support/unparse"
5
+
6
+ require_relative "directive_handlers"
7
+ require_relative "numbered_params"
8
+
9
+ module Kimera
10
+ module Rewrite
11
+ TYPE = OperatorProtocol::TYPE
12
+ INDEX = OperatorProtocol::INDEX
13
+ STATEMENT_DELETION = OperatorProtocol::STATEMENT_DELETION
14
+
15
+ module Directive
16
+ module_function
17
+
18
+ def render(source, directive)
19
+ shortcut(directive) || attempt(source, directive)
20
+ end
21
+
22
+ def shortcut(directive)
23
+ type = directive[TYPE]
24
+ return "(statement deleted)" if type == STATEMENT_DELETION
25
+ return String(directive["value"]).inspect if type == "string_literal"
26
+ "#{directive["name"]} (default removed)" if type == "required_argument"
27
+ end
28
+
29
+ def attempt(source, directive)
30
+ Kimera::Unparse.unparse(apply(NumberedParams.normalize(Kimera::Unparse.parse(source)), directive))
31
+ rescue StandardError
32
+ nil
33
+ end
34
+
35
+ def table(*entries) = entries.each_slice(2).to_h.freeze
36
+
37
+ VALUE_HANDLERS = table(
38
+ "comparison", :swap, "op_swap", :swap, "selector_swap", :select,
39
+ "op_asgn_swap", :assign, "csend_to_send", :send, "index_to_fetch", :fetch,
40
+ "boolean_literal", :literal, "integer_literal", :integer, "float_literal", :float,
41
+ "string_literal", :string, "symbol_literal", :symbol, "regexp_literal", :regexp,
42
+ "boolean_connective", :retype, "range_flip", :retype, "condition", :retest
43
+ )
44
+ STRUCTURAL_HANDLERS = table(
45
+ STATEMENT_DELETION, :clear, "return_nil", :nothing, "empty_collection", :empty,
46
+ "unwrap_receiver", :receiver, "drop_receiver_link", :unlink, "drop_element", :remove,
47
+ "drop_argument", :drop, "unwrap_argument", :argument, "required_argument", :default,
48
+ "kwarg_element_drop", :keyword, "kwarg_pair_drop", :option
49
+ )
50
+ HANDLERS = VALUE_HANDLERS.merge(STRUCTURAL_HANDLERS).freeze
51
+
52
+ def apply(node, directive, unguard: DirectiveHandlers::IDENTITY)
53
+ DirectiveHandlers::Operation.new(node, directive, unguard).public_send(handler(directive))
54
+ end
55
+
56
+ def handler(directive)
57
+ HANDLERS.fetch(directive[TYPE]) { raise(ArgumentError, "unknown directive #{directive.inspect}") }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Rewrite
5
+ module DirectiveExtraHandlers
6
+ Operation =
7
+ Data.define(:node, :directive, :unguard) do
8
+ def trim(pair)
9
+ key, value = pair.children
10
+ elements = value.children.dup
11
+ elements.delete_at(Integer(directive[INDEX]))
12
+ pair.updated(nil, [key, value.updated(nil, elements)])
13
+ end
14
+
15
+ def fetch
16
+ node.type == :index ? index(node) : call(node)
17
+ end
18
+
19
+ def index(node)
20
+ receiver, *args = node.children
21
+ node.updated(:send, [receiver, :fetch, *args])
22
+ end
23
+
24
+ def call(node)
25
+ receiver, _selector, *args = node.children
26
+ node.updated(nil, [receiver, :fetch, *args])
27
+ end
28
+
29
+ def argument
30
+ node.children[2]
31
+ end
32
+
33
+ def symbol
34
+ node.updated(:sym, [directive["value"].to_sym])
35
+ end
36
+
37
+ def default
38
+ node.updated(node.type == :kwoptarg ? :kwarg : :arg, [node.children[0]])
39
+ end
40
+
41
+ def regexp
42
+ source = directive["source"].to_s
43
+ ast(:regexp, *(source.empty? ? [] : [ast(:str, source)]), ast(:regopt))
44
+ end
45
+
46
+ def ast(type, *children)
47
+ Parser::AST::Node.new(type, children)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "directive_extra_handlers"
4
+ require_relative "directive_kwarg_handlers"
5
+
6
+ module Kimera
7
+ module Rewrite
8
+ module DirectiveHandlers
9
+ IDENTITY = ->(node) { node }
10
+
11
+ Operation =
12
+ Data.define(:node, :directive, :unguard) do
13
+ def target = directive["to"]&.to_sym
14
+
15
+ def swap
16
+ receiver, _selector, *args = node.children
17
+ node.updated(:send, [receiver, target, *args])
18
+ end
19
+
20
+ def retype
21
+ node.updated(target, node.children)
22
+ end
23
+
24
+ def literal
25
+ node.updated(target, [])
26
+ end
27
+
28
+ def clear
29
+ node.updated(:nil, [])
30
+ end
31
+
32
+ def receiver
33
+ node.children[0]
34
+ end
35
+
36
+ def unlink
37
+ DirectiveKwargHandlers.block(node) do |call|
38
+ receiver, *rest = call.children
39
+ call.updated(nil, [unguard.call(receiver).children[0], *rest])
40
+ end
41
+ end
42
+
43
+ def send
44
+ DirectiveKwargHandlers.block(node) { |call| call.updated(:send, call.children) }
45
+ end
46
+
47
+ def retest
48
+ node.updated(nil, [ast(target), *node.children[1..]])
49
+ end
50
+
51
+ def integer
52
+ node.updated(:int, [Integer(directive["value"])])
53
+ end
54
+
55
+ def float
56
+ node.updated(:float, [Float(directive["value"])])
57
+ end
58
+
59
+ def string
60
+ node.updated(:str, [String(directive["value"])])
61
+ end
62
+
63
+ def empty
64
+ node.updated(nil, [])
65
+ end
66
+
67
+ def drop
68
+ DirectiveKwargHandlers.block(node) do |call|
69
+ receiver, selector, *args = call.children
70
+ args.delete_at(Integer(directive[INDEX]))
71
+ call.updated(nil, [receiver, selector, *args])
72
+ end
73
+ end
74
+
75
+ def nothing
76
+ node.updated(nil, [ast(:nil)])
77
+ end
78
+
79
+ def select
80
+ DirectiveKwargHandlers.block(node) do |call|
81
+ DirectiveKwargHandlers.select(call, target)
82
+ end
83
+ end
84
+
85
+ def remove
86
+ elements = node.children.dup
87
+ elements.delete_at(Integer(directive[INDEX]))
88
+ node.updated(nil, elements)
89
+ end
90
+
91
+ def assign
92
+ target, _operator, value = node.children
93
+ node.updated(nil, [target, self.target, value])
94
+ end
95
+
96
+ def keyword
97
+ kwargs { |pair| extra.trim(pair) }
98
+ end
99
+
100
+ def option
101
+ kwargs { |_pair| next }
102
+ end
103
+
104
+ def kwargs(&)
105
+ DirectiveKwargHandlers.block(node) { |call| DirectiveKwargHandlers.rewrite(call, directive["key"], &) }
106
+ end
107
+
108
+ def fetch
109
+ extra.fetch
110
+ end
111
+
112
+ def argument
113
+ extra.argument
114
+ end
115
+
116
+ def symbol
117
+ extra.symbol
118
+ end
119
+
120
+ def default
121
+ extra.default
122
+ end
123
+
124
+ def regexp
125
+ extra.regexp
126
+ end
127
+
128
+ def extra
129
+ DirectiveExtraHandlers::Operation.new(node, directive, unguard)
130
+ end
131
+
132
+ def ast(type, *children)
133
+ Parser::AST::Node.new(type, children)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Rewrite
5
+ module DirectiveKwargHandlers
6
+ module_function
7
+
8
+ def select(call, to)
9
+ receiver, _selector, *args = call.children
10
+ call.updated(nil, [receiver, to, *args])
11
+ end
12
+
13
+ def block(node)
14
+ return yield(node) unless %i[block numblock].include?(node.type)
15
+ call, *rest = node.children
16
+ node.updated(nil, [yield(call), *rest])
17
+ end
18
+
19
+ def rewrite(call, key, &)
20
+ receiver, selector, *args = call.children
21
+ position = index(args)
22
+ return call unless position
23
+ apply(args, position, key, &)
24
+ call.updated(nil, [receiver, selector, *args])
25
+ end
26
+
27
+ def index(args)
28
+ args.rindex { |a| a.is_a?(Parser::AST::Node) && %i[kwargs hash].include?(a.type) }
29
+ end
30
+
31
+ def apply(args, position, key)
32
+ hash = args[position]
33
+ pairs = hash.children.filter_map { |pair| matches?(pair, key) ? yield(pair) : pair }
34
+ pairs.empty? ? args.delete_at(position) : args[position] = hash.updated(nil, pairs)
35
+ end
36
+
37
+ def matches?(pair, key)
38
+ symbol = pair.children.first
39
+ pair.type == :pair && symbol.type == :sym &&
40
+ symbol.children.first.to_s == key
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ast_walk"
4
+
5
+ module Kimera
6
+ module Rewrite
7
+ module NumberedParams
8
+ module_function
9
+
10
+ PREFIX = "__kimera_"
11
+
12
+ def normalize(node)
13
+ AstWalk.rebuild(node) { |rebuilt, _original| dispatch(rebuilt) }
14
+ end
15
+
16
+ def dispatch(node)
17
+ case node.type
18
+ when :numblock then numbered(node)
19
+ when :itblock then implicit(node)
20
+ else node
21
+ end
22
+ end
23
+
24
+ def numbered(node)
25
+ call, count, body = node.children
26
+ renames = (1..count).to_h { |n| [:"_#{n}", :"#{PREFIX}#{n}"] }
27
+ node.updated(:block, [call, arguments(renames.values), rename(body, renames)])
28
+ end
29
+
30
+ def implicit(node)
31
+ call, _name, body = node.children
32
+ name = :"#{PREFIX}it"
33
+ node.updated(:block, [call, arguments([name]), rename(body, { it: name })])
34
+ end
35
+
36
+ def arguments(names)
37
+ args = names.map { Parser::AST::Node.new(:arg, [it]) }
38
+ args = [Parser::AST::Node.new(:procarg0, args)] if args.one?
39
+ Parser::AST::Node.new(:args, args)
40
+ end
41
+
42
+ def rename(node, renames)
43
+ AstWalk.rebuild(node) do |rebuilt, _original|
44
+ name = rebuilt.type == :lvar ? rebuilt.children.first : nil
45
+ renames.key?(name) ? rebuilt.updated(nil, [renames.fetch(name)]) : rebuilt
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kimera
4
+ module Runtime
5
+ SOURCE_PATH = File.expand_path(__FILE__)
6
+
7
+ class << self
8
+ attr_reader :active
9
+
10
+ def active=(id)
11
+ @active = id && Integer(id)
12
+ end
13
+
14
+ def active?(id)
15
+ @ledgers&.each { |ledger| ledger.push(id) }
16
+ @active == id
17
+ end
18
+
19
+ def start!
20
+ ledger = []
21
+ (@ledgers ||= []).push(ledger)
22
+ ledger
23
+ end
24
+
25
+ def drain!(ledger)
26
+ touched = ledger.uniq
27
+ clear!(ledger)
28
+ touched
29
+ end
30
+
31
+ def clear!(ledger)
32
+ ledger.clear
33
+ end
34
+
35
+ def stop!(ledger)
36
+ open = @ledgers
37
+ return unless open
38
+ open.delete_if { |l| l.equal?(ledger) }
39
+ @ledgers = nil if open.empty?
40
+ end
41
+
42
+ def reset!
43
+ @active = nil
44
+ end
45
+ end
46
+
47
+ reset!
48
+ end
49
+ end
50
+
51
+ MutantRuntime = Kimera::Runtime unless defined?(MutantRuntime)