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.
- checksums.yaml +7 -0
- data/.kimera.yml.example +59 -0
- data/CHANGELOG.md +36 -0
- data/LICENSE.txt +21 -0
- data/README.md +528 -0
- data/exe/kimera +22 -0
- data/lib/kimera/audit/operator_audit.rb +71 -0
- data/lib/kimera/cli/argv.rb +15 -0
- data/lib/kimera/cli/baseline.rb +87 -0
- data/lib/kimera/cli/changed.rb +27 -0
- data/lib/kimera/cli/ci.rb +27 -0
- data/lib/kimera/cli/completion.rb +37 -0
- data/lib/kimera/cli/doctor.rb +115 -0
- data/lib/kimera/cli/flag.rb +55 -0
- data/lib/kimera/cli/help_text.rb +45 -0
- data/lib/kimera/cli/init.rb +105 -0
- data/lib/kimera/cli/mutant.rb +82 -0
- data/lib/kimera/cli/report_file.rb +20 -0
- data/lib/kimera/cli/run/arguments.rb +58 -0
- data/lib/kimera/cli/run/cycle.rb +78 -0
- data/lib/kimera/cli/run/digest.rb +49 -0
- data/lib/kimera/cli/run/emission.rb +45 -0
- data/lib/kimera/cli/run/gate.rb +43 -0
- data/lib/kimera/cli/run/options.rb +85 -0
- data/lib/kimera/cli/run/pass.rb +100 -0
- data/lib/kimera/cli/run/sources.rb +56 -0
- data/lib/kimera/cli/run.rb +52 -0
- data/lib/kimera/cli/skill.rb +23 -0
- data/lib/kimera/cli/suggestion.rb +36 -0
- data/lib/kimera/cli/survivors/panel.rb +81 -0
- data/lib/kimera/cli/survivors.rb +89 -0
- data/lib/kimera/cli/synthesize.rb +66 -0
- data/lib/kimera/cli/workflows.rb +8 -0
- data/lib/kimera/cli.rb +132 -0
- data/lib/kimera/error.rb +3 -0
- data/lib/kimera/execution/baseline_failure.rb +47 -0
- data/lib/kimera/execution/baseline_pass.rb +123 -0
- data/lib/kimera/execution/boot.rb +50 -0
- data/lib/kimera/execution/callback.rb +33 -0
- data/lib/kimera/execution/child_process.rb +48 -0
- data/lib/kimera/execution/composite_isolation.rb +24 -0
- data/lib/kimera/execution/harness.rb +150 -0
- data/lib/kimera/execution/isolated.rb +64 -0
- data/lib/kimera/execution/isolated_child.rb +15 -0
- data/lib/kimera/execution/isolated_child_command.rb +44 -0
- data/lib/kimera/execution/isolated_child_minitest.rb +34 -0
- data/lib/kimera/execution/isolated_plan.rb +54 -0
- data/lib/kimera/execution/isolated_scheduling.rb +54 -0
- data/lib/kimera/execution/isolated_test_selection.rb +22 -0
- data/lib/kimera/execution/isolated_verdict.rb +72 -0
- data/lib/kimera/execution/isolated_watchdog.rb +59 -0
- data/lib/kimera/execution/isolation.rb +17 -0
- data/lib/kimera/execution/null_progress.rb +17 -0
- data/lib/kimera/execution/overlay_guard_modules.rb +95 -0
- data/lib/kimera/execution/overlay_guards.rb +56 -0
- data/lib/kimera/execution/parallel_test_databases.rb +64 -0
- data/lib/kimera/execution/pool_driver.rb +101 -0
- data/lib/kimera/execution/priority.rb +23 -0
- data/lib/kimera/execution/reload.rb +104 -0
- data/lib/kimera/execution/rig.rb +41 -0
- data/lib/kimera/execution/schedule.rb +105 -0
- data/lib/kimera/execution/schemata.rb +88 -0
- data/lib/kimera/execution/shift/attempt.rb +34 -0
- data/lib/kimera/execution/shift/coverage_channel.rb +43 -0
- data/lib/kimera/execution/shift/killer_memory.rb +20 -0
- data/lib/kimera/execution/shift/leak_guard.rb +34 -0
- data/lib/kimera/execution/shift.rb +137 -0
- data/lib/kimera/execution/sweep.rb +30 -0
- data/lib/kimera/execution/transaction_isolation.rb +32 -0
- data/lib/kimera/execution/trial.rb +16 -0
- data/lib/kimera/execution/verdicts.rb +78 -0
- data/lib/kimera/execution/worker_pool/fleet.rb +104 -0
- data/lib/kimera/execution/worker_pool/stillborn_guard.rb +40 -0
- data/lib/kimera/execution/worker_pool/worker.rb +32 -0
- data/lib/kimera/execution/worker_pool.rb +101 -0
- data/lib/kimera/frameworks/adapter.rb +82 -0
- data/lib/kimera/frameworks/minitest_adapter.rb +102 -0
- data/lib/kimera/frameworks/rspec_adapter.rb +123 -0
- data/lib/kimera/frameworks/rspec_group_index.rb +45 -0
- data/lib/kimera/incremental/git_diff.rb +98 -0
- data/lib/kimera/incremental/selection.rb +27 -0
- data/lib/kimera/incremental/session.rb +109 -0
- data/lib/kimera/memoization.rb +104 -0
- data/lib/kimera/operators/argument_drop.rb +19 -0
- data/lib/kimera/operators/arithmetic.rb +12 -0
- data/lib/kimera/operators/audit.rb +3 -0
- data/lib/kimera/operators/base.rb +40 -0
- data/lib/kimera/operators/binary_swap.rb +23 -0
- data/lib/kimera/operators/boolean.rb +26 -0
- data/lib/kimera/operators/chain_link_deletion.rb +25 -0
- data/lib/kimera/operators/collection_literal.rb +18 -0
- data/lib/kimera/operators/comparison.rb +13 -0
- data/lib/kimera/operators/conditional.rb +36 -0
- data/lib/kimera/operators/default_argument.rb +17 -0
- data/lib/kimera/operators/element_drop.rb +31 -0
- data/lib/kimera/operators/index_fetch.rb +15 -0
- data/lib/kimera/operators/kernel_coercion.rb +17 -0
- data/lib/kimera/operators/method_unwrap.rb +20 -0
- data/lib/kimera/operators/negation.rb +15 -0
- data/lib/kimera/operators/node_swap.rb +20 -0
- data/lib/kimera/operators/numeric_literal.rb +39 -0
- data/lib/kimera/operators/op_assign.rb +18 -0
- data/lib/kimera/operators/plugins.rb +27 -0
- data/lib/kimera/operators/rails_association.rb +24 -0
- data/lib/kimera/operators/rails_callback.rb +49 -0
- data/lib/kimera/operators/rails_declaration.rb +16 -0
- data/lib/kimera/operators/rails_permit.rb +34 -0
- data/lib/kimera/operators/rails_validation.rb +17 -0
- data/lib/kimera/operators/range.rb +15 -0
- data/lib/kimera/operators/regexp_literal.rb +16 -0
- data/lib/kimera/operators/respond_to_guard.rb +24 -0
- data/lib/kimera/operators/return_value.rb +36 -0
- data/lib/kimera/operators/safe_navigation.rb +15 -0
- data/lib/kimera/operators/selector_swap.rb +24 -0
- data/lib/kimera/operators/statement_deletion.rb +26 -0
- data/lib/kimera/operators/string_literal.rb +22 -0
- data/lib/kimera/operators/symbol_literal.rb +26 -0
- data/lib/kimera/operators/vocabulary.rb +82 -0
- data/lib/kimera/operators.rb +141 -0
- data/lib/kimera/plugins.rb +3 -0
- data/lib/kimera/registry/builder.rb +59 -0
- data/lib/kimera/registry/mutation_point.rb +146 -0
- data/lib/kimera/registry/numbering.rb +15 -0
- data/lib/kimera/registry/registry.rb +120 -0
- data/lib/kimera/registry/source_file.rb +71 -0
- data/lib/kimera/registry/tally.rb +11 -0
- data/lib/kimera/registry/walking.rb +55 -0
- data/lib/kimera/report/actions.rb +38 -0
- data/lib/kimera/report/coloring.rb +25 -0
- data/lib/kimera/report/formats.rb +78 -0
- data/lib/kimera/report/live.rb +56 -0
- data/lib/kimera/report/log.rb +46 -0
- data/lib/kimera/report/progress.rb +55 -0
- data/lib/kimera/report/screen.rb +44 -0
- data/lib/kimera/report/sections.rb +50 -0
- data/lib/kimera/report/tally.rb +84 -0
- data/lib/kimera/report/text.rb +104 -0
- data/lib/kimera/results/result.rb +72 -0
- data/lib/kimera/results/run_report.rb +114 -0
- data/lib/kimera/rewrite/ast_walk.rb +20 -0
- data/lib/kimera/rewrite/directive.rb +61 -0
- data/lib/kimera/rewrite/directive_extra_handlers.rb +52 -0
- data/lib/kimera/rewrite/directive_handlers.rb +138 -0
- data/lib/kimera/rewrite/directive_kwarg_handlers.rb +44 -0
- data/lib/kimera/rewrite/numbered_params.rb +50 -0
- data/lib/kimera/runtime.rb +51 -0
- data/lib/kimera/scope/config.rb +90 -0
- data/lib/kimera/scope/file_set.rb +51 -0
- data/lib/kimera/scope/ignore_list.rb +55 -0
- data/lib/kimera/self_protection.rb +56 -0
- data/lib/kimera/support/duration.rb +13 -0
- data/lib/kimera/support/errors.rb +8 -0
- data/lib/kimera/support/operator_protocol.rb +10 -0
- data/lib/kimera/support/syntax.rb +68 -0
- data/lib/kimera/support/syntax_types.rb +20 -0
- data/lib/kimera/support/unparse.rb +15 -0
- data/lib/kimera/support/version.rb +5 -0
- data/lib/kimera/support/warnings.rb +15 -0
- data/lib/kimera/synthesis/file_weave.rb +78 -0
- data/lib/kimera/synthesis/guard_weaver.rb +77 -0
- data/lib/kimera/synthesis/guardrail_dispatch.rb +68 -0
- data/lib/kimera/synthesis/guardrail_interpolation.rb +59 -0
- data/lib/kimera/synthesis/guardrail_value_objects.rb +46 -0
- data/lib/kimera/synthesis/overlay.rb +52 -0
- data/lib/kimera/synthesis/overlay_splice.rb +101 -0
- data/lib/kimera/synthesis/project.rb +58 -0
- data/lib/kimera/synthesis/source_map.rb +42 -0
- data/lib/kimera/version.rb +3 -0
- data/lib/kimera.rb +13 -0
- data/skills/kimera/SKILL.md +118 -0
- 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)
|