bparity 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/.rubocop.yml +61 -0
- data/LICENSE.txt +21 -0
- data/README.md +146 -0
- data/Rakefile +36 -0
- data/docs/application_example.md +25 -0
- data/docs/formal_assurance_limits.md +21 -0
- data/exe/bparity +7 -0
- data/fixtures/scenarios/01_pure_function/adapter.rb +13 -0
- data/fixtures/scenarios/01_pure_function/boundary.rb +17 -0
- data/fixtures/scenarios/01_pure_function/legacy/dead_gem.rb +9 -0
- data/fixtures/scenarios/01_pure_function/legacy/slugifier.rb +17 -0
- data/fixtures/scenarios/01_pure_function/replacement/broken.rb +15 -0
- data/fixtures/scenarios/01_pure_function/replacement/good.rb +19 -0
- data/fixtures/scenarios/01_pure_function/spec/slugifier_spec.rb +20 -0
- data/fixtures/scenarios/01_pure_function/test/slugifier_test.rb +10 -0
- data/fixtures/scenarios/02_stateful_client/adapter.rb +19 -0
- data/fixtures/scenarios/02_stateful_client/boundary.rb +10 -0
- data/fixtures/scenarios/02_stateful_client/legacy/client.rb +29 -0
- data/fixtures/scenarios/02_stateful_client/replacement/broken.rb +14 -0
- data/fixtures/scenarios/02_stateful_client/replacement/good.rb +23 -0
- data/fixtures/scenarios/02_stateful_client/spec/client_spec.rb +31 -0
- data/fixtures/scenarios/03_external_boundary/adapter.rb +13 -0
- data/fixtures/scenarios/03_external_boundary/boundary.rb +11 -0
- data/fixtures/scenarios/03_external_boundary/legacy/dead_formatter.rb +7 -0
- data/fixtures/scenarios/03_external_boundary/legacy/receipt.rb +11 -0
- data/fixtures/scenarios/03_external_boundary/replacement/broken.rb +11 -0
- data/fixtures/scenarios/03_external_boundary/replacement/good.rb +11 -0
- data/fixtures/scenarios/03_external_boundary/spec/receipt_spec.rb +10 -0
- data/fixtures/scenarios/04_intentional_divergence/adapter.rb +12 -0
- data/fixtures/scenarios/04_intentional_divergence/adapter_unwaived.rb +10 -0
- data/fixtures/scenarios/04_intentional_divergence/boundary.rb +8 -0
- data/fixtures/scenarios/04_intentional_divergence/legacy/dead_identity.rb +7 -0
- data/fixtures/scenarios/04_intentional_divergence/legacy/identity.rb +9 -0
- data/fixtures/scenarios/04_intentional_divergence/replacement/broken.rb +9 -0
- data/fixtures/scenarios/04_intentional_divergence/replacement/good.rb +9 -0
- data/fixtures/scenarios/04_intentional_divergence/spec/identity_spec.rb +10 -0
- data/fixtures/scenarios/05_formal_negative/adapter.rb +15 -0
- data/fixtures/scenarios/05_formal_negative/boundary.rb +16 -0
- data/fixtures/scenarios/05_formal_negative/legacy/dead_lock.rb +5 -0
- data/fixtures/scenarios/05_formal_negative/legacy/turnstile.rb +24 -0
- data/fixtures/scenarios/05_formal_negative/replacement/broken.rb +18 -0
- data/fixtures/scenarios/05_formal_negative/replacement/formal_broken.rb +24 -0
- data/fixtures/scenarios/05_formal_negative/replacement/good.rb +24 -0
- data/fixtures/scenarios/05_formal_negative/spec/turnstile_spec.rb +21 -0
- data/lib/bparity/adapter.rb +115 -0
- data/lib/bparity/adequacy.rb +78 -0
- data/lib/bparity/boundary.rb +97 -0
- data/lib/bparity/cli/formal_commands.rb +428 -0
- data/lib/bparity/cli/verification_commands.rb +242 -0
- data/lib/bparity/cli.rb +262 -0
- data/lib/bparity/corpus.rb +45 -0
- data/lib/bparity/errors.rb +12 -0
- data/lib/bparity/formal/assumptions.rb +131 -0
- data/lib/bparity/formal/bounded.rb +543 -0
- data/lib/bparity/formal/contract.rb +81 -0
- data/lib/bparity/formal/deductive.rb +481 -0
- data/lib/bparity/formal/lts.rb +344 -0
- data/lib/bparity/formal/result.rb +50 -0
- data/lib/bparity/formal.rb +8 -0
- data/lib/bparity/recording.rb +412 -0
- data/lib/bparity/reporting.rb +128 -0
- data/lib/bparity/spec_bundle.rb +208 -0
- data/lib/bparity/synthesis.rb +487 -0
- data/lib/bparity/verification.rb +310 -0
- data/lib/bparity/version.rb +5 -0
- data/lib/bparity.rb +42 -0
- metadata +125 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Bparity.adapter do
|
|
4
|
+
subject "Identity" do
|
|
5
|
+
construct { Replacement::Identity }
|
|
6
|
+
operation "#label" do
|
|
7
|
+
invoke { |identity, args, _kwargs| identity.display(value: args.fetch(0)) }
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
waive "bc-000001", reason: "The replacement uses inclusive terminology",
|
|
11
|
+
approved_by: "migration-owner", approved_at: "2026-08-28"
|
|
12
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Bparity.adapter do
|
|
4
|
+
subject "PureToken" do
|
|
5
|
+
construct { Replacement::PureTokenService }
|
|
6
|
+
operation("#token") { invoke { |service, args, _kwargs| service.shift(*args) } }
|
|
7
|
+
end
|
|
8
|
+
subject "Turnstile" do
|
|
9
|
+
construct { Replacement::Gate.new }
|
|
10
|
+
state { |gate| { locked: gate.phase == :secured } }
|
|
11
|
+
operation("#unlock") { invoke { |gate, _args, _kwargs| gate.release } }
|
|
12
|
+
operation("#lock") { invoke { |gate, _args, _kwargs| gate.secure } }
|
|
13
|
+
operation("#enter") { invoke { |gate, _args, _kwargs| gate.pass } }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Bparity.boundary do
|
|
4
|
+
observe "Legacy::PureToken" do
|
|
5
|
+
methods :token
|
|
6
|
+
end
|
|
7
|
+
observe "Legacy::Turnstile" do
|
|
8
|
+
methods :unlock, :lock, :enter
|
|
9
|
+
state { |turnstile| { locked: turnstile.locked } }
|
|
10
|
+
end
|
|
11
|
+
driver :rspec, files: "fixtures/scenarios/05_formal_negative/spec/**/*_spec.rb"
|
|
12
|
+
formal do
|
|
13
|
+
pure_fragment "Legacy::PureToken#token"
|
|
14
|
+
lts_learning :active
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "dead_lock"
|
|
4
|
+
|
|
5
|
+
module Legacy
|
|
6
|
+
class PureToken
|
|
7
|
+
def token(value) = value + 1
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Turnstile
|
|
11
|
+
attr_reader :locked
|
|
12
|
+
|
|
13
|
+
def initialize = @locked = RetiredLock::DEFAULT_LOCKED
|
|
14
|
+
def unlock = (@locked = false; :ok)
|
|
15
|
+
def lock = (@locked = true; :ok)
|
|
16
|
+
|
|
17
|
+
def enter
|
|
18
|
+
return :denied if locked
|
|
19
|
+
|
|
20
|
+
@locked = true
|
|
21
|
+
:entered
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Replacement
|
|
4
|
+
module PureTokenService
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def shift(value) = value + 1
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Gate
|
|
11
|
+
attr_reader :phase
|
|
12
|
+
|
|
13
|
+
def initialize = @phase = :secured
|
|
14
|
+
def release = (@phase = :open; :ok)
|
|
15
|
+
def secure = (@phase = :secured; :ok)
|
|
16
|
+
def pass = phase == :secured ? :denied : :entered
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Replacement
|
|
4
|
+
module PureTokenService
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def shift(value) = value + 2
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Gate
|
|
11
|
+
attr_reader :phase
|
|
12
|
+
|
|
13
|
+
def initialize = @phase = :secured
|
|
14
|
+
def release = (@phase = :open; :ok)
|
|
15
|
+
def secure = (@phase = :secured; :ok)
|
|
16
|
+
|
|
17
|
+
def pass
|
|
18
|
+
return :denied if phase == :secured
|
|
19
|
+
|
|
20
|
+
@phase = :secured
|
|
21
|
+
:entered
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Replacement
|
|
4
|
+
module PureTokenService
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def shift(value) = value + 1
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Gate
|
|
11
|
+
attr_reader :phase
|
|
12
|
+
|
|
13
|
+
def initialize = @phase = :secured
|
|
14
|
+
def release = (@phase = :open; :ok)
|
|
15
|
+
def secure = (@phase = :secured; :ok)
|
|
16
|
+
|
|
17
|
+
def pass
|
|
18
|
+
return :denied if phase == :secured
|
|
19
|
+
|
|
20
|
+
@phase = :secured
|
|
21
|
+
:entered
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rspec"
|
|
4
|
+
require_relative "../legacy/turnstile"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Legacy::Turnstile do
|
|
7
|
+
it "exposes a pure fragment for F2 and F4 negative checks" do
|
|
8
|
+
expect(Legacy::PureToken.new.token(1)).to eq(2)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "covers every operation from locked and unlocked states" do
|
|
12
|
+
turnstile = described_class.new
|
|
13
|
+
expect(turnstile.lock).to eq(:ok)
|
|
14
|
+
expect(turnstile.enter).to eq(:denied)
|
|
15
|
+
expect(turnstile.unlock).to eq(:ok)
|
|
16
|
+
expect(turnstile.unlock).to eq(:ok)
|
|
17
|
+
expect(turnstile.lock).to eq(:ok)
|
|
18
|
+
expect(turnstile.unlock).to eq(:ok)
|
|
19
|
+
expect(turnstile.enter).to eq(:entered)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module Bparity
|
|
6
|
+
module Adapter
|
|
7
|
+
Operation = Struct.new(:name, :invoker, :return_mapper, :error_mapper, keyword_init: true) do
|
|
8
|
+
def invoke(subject, args, kwargs, &block)
|
|
9
|
+
return invoker.call(subject, args, kwargs, &block) if invoker
|
|
10
|
+
|
|
11
|
+
subject.public_send(name.delete_prefix("#"), *args, **kwargs, &block)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def map_return(value) = return_mapper ? return_mapper.call(value) : value
|
|
15
|
+
|
|
16
|
+
def map_error(error)
|
|
17
|
+
error_mapper ? error_mapper.call(error) : { class: error.class.name, message: Bparity.exception_message(error) }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class OperationDsl
|
|
22
|
+
def initialize(operation)
|
|
23
|
+
@operation = operation
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def invoke(&block) = @operation.invoker = block
|
|
27
|
+
def map_return(&block) = @operation.return_mapper = block
|
|
28
|
+
def map_error(&block) = @operation.error_mapper = block
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Subject
|
|
32
|
+
attr_reader :name, :operations
|
|
33
|
+
attr_accessor :constructor, :state_projection
|
|
34
|
+
|
|
35
|
+
def initialize(name)
|
|
36
|
+
@name = name
|
|
37
|
+
@operations = {}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def construct(&block) = self.constructor = block
|
|
41
|
+
def state(&block) = self.state_projection = block
|
|
42
|
+
|
|
43
|
+
def operation(name, &block)
|
|
44
|
+
item = Operation.new(name: name)
|
|
45
|
+
OperationDsl.new(item).instance_eval(&block) if block
|
|
46
|
+
operations[name] = item
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def build(context = {})
|
|
50
|
+
unless constructor
|
|
51
|
+
raise ConfigurationError, "Adapter subject #{name} has no constructor. Add a construct block to the adapter."
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
constructor.call(context)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Waiver = Struct.new(:id, :reason, :approved_by, :approved_at, keyword_init: true)
|
|
59
|
+
|
|
60
|
+
class External
|
|
61
|
+
attr_reader :source, :target, :call_mappers
|
|
62
|
+
|
|
63
|
+
def initialize(source, target)
|
|
64
|
+
@source = source
|
|
65
|
+
@target = target
|
|
66
|
+
@call_mappers = {}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def map_call(name, &block)
|
|
70
|
+
call_mappers[name.to_s] = block
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class Definition
|
|
75
|
+
attr_reader :spec, :subjects, :waivers, :externals
|
|
76
|
+
|
|
77
|
+
def initialize(spec: nil)
|
|
78
|
+
@spec = spec
|
|
79
|
+
@subjects = {}
|
|
80
|
+
@waivers = {}
|
|
81
|
+
@externals = []
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def subject(name, &block)
|
|
85
|
+
item = Subject.new(name)
|
|
86
|
+
item.instance_eval(&block) if block
|
|
87
|
+
subjects[name] = item
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def waive(id, reason:, approved_by:, approved_at:)
|
|
91
|
+
values = { "id" => id, "reason" => reason, "approved_by" => approved_by, "approved_at" => approved_at }
|
|
92
|
+
missing = values.filter_map { |name, value| name if value.to_s.strip.empty? }
|
|
93
|
+
unless missing.empty?
|
|
94
|
+
raise ConfigurationError,
|
|
95
|
+
"Waiver metadata is missing #{missing.join(', ')}. Add an ID, reason, approver, and approval date."
|
|
96
|
+
end
|
|
97
|
+
begin
|
|
98
|
+
Date.iso8601(approved_at.to_s)
|
|
99
|
+
rescue Date::Error
|
|
100
|
+
raise ConfigurationError,
|
|
101
|
+
"Waiver #{id} has an invalid approval date. Use YYYY-MM-DD."
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
waivers[id] = Waiver.new(id:, reason:, approved_by:, approved_at:)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def external(mapping, &block)
|
|
108
|
+
source, target = mapping.first
|
|
109
|
+
item = External.new(source, target)
|
|
110
|
+
item.instance_eval(&block) if block
|
|
111
|
+
externals << item
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module Bparity
|
|
6
|
+
module Adequacy
|
|
7
|
+
class MutantBridge
|
|
8
|
+
SCORE = /Mutation coverage:\s*([\d.]+)%/
|
|
9
|
+
|
|
10
|
+
def available?
|
|
11
|
+
_output, _error, status = Open3.capture3("bundle", "exec", "mutant", "--version")
|
|
12
|
+
status.success?
|
|
13
|
+
rescue Errno::ENOENT
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
return { "status" => "skipped", "reason" => "mutant is not installed" } unless available?
|
|
19
|
+
|
|
20
|
+
output, error, status = Open3.capture3("bundle", "exec", "mutant", "run")
|
|
21
|
+
match = output.match(SCORE)
|
|
22
|
+
score = match[1].to_f if match
|
|
23
|
+
{ "status" => status.success? ? "completed" : "failed", "score" => score,
|
|
24
|
+
"output" => output, "error" => error }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module CoverageReconciler
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
def call(gaps:, surviving_mutants: [])
|
|
32
|
+
risks = gaps.map { |gap| "Legacy gap: #{gap['location'] || gap['note'] || gap['kind']}" }
|
|
33
|
+
risks + surviving_mutants.map { |mutant| "Unconstrained replacement behavior: #{mutant}" }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Analyzer
|
|
38
|
+
def initialize(bundle:, results:, mutation: nil)
|
|
39
|
+
@bundle = bundle
|
|
40
|
+
@results = results
|
|
41
|
+
@mutation = mutation || { "status" => "skipped", "reason" => "not requested" }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def call
|
|
45
|
+
examples = @bundle.fetch("subjects").flat_map { |subject| subject.fetch("operations") }
|
|
46
|
+
.flat_map { |operation| operation.fetch("examples") }
|
|
47
|
+
evaluated = @results.length
|
|
48
|
+
{
|
|
49
|
+
"specification_coverage" => ratio(evaluated, examples.length),
|
|
50
|
+
"recorded_examples" => examples.length,
|
|
51
|
+
"generated_checks" => generated_checks,
|
|
52
|
+
"formal_assurance" => Reporting::AssuranceMatrix.new(@bundle).to_h,
|
|
53
|
+
"constraint_strength" => @mutation,
|
|
54
|
+
"residual_risks" => CoverageReconciler.call(gaps: @bundle.fetch("gaps", []),
|
|
55
|
+
surviving_mutants: mutation_risks),
|
|
56
|
+
"waiver_count" => @results.count { |result| result.status == :waived }
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def generated_checks
|
|
63
|
+
@bundle.fetch("subjects").sum do |subject|
|
|
64
|
+
subject.fetch("operations").sum { |operation| operation.fetch("invariants", []).length }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def ratio(numerator, denominator) = denominator.zero? ? 1.0 : (numerator.to_f / denominator).round(4)
|
|
69
|
+
|
|
70
|
+
def mutation_risks
|
|
71
|
+
score = @mutation["score"]
|
|
72
|
+
return [] unless score && score < 100
|
|
73
|
+
|
|
74
|
+
["mutation score #{score}% (inspect the mutant output for survivors)"]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bparity
|
|
4
|
+
module Boundary
|
|
5
|
+
Subject = Struct.new(:name, :method_names, :excluded, :state_projection, :return_projections, keyword_init: true) do
|
|
6
|
+
def initialize(name:, **)
|
|
7
|
+
super(name:, method_names: [], excluded: [], return_projections: {})
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def methods(*names)
|
|
11
|
+
self.method_names = names.map(&:to_sym)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def exclude(*names)
|
|
15
|
+
self.excluded |= names.map(&:to_sym)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def state(&block)
|
|
19
|
+
self.state_projection = block
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def project_return(class_name, &block)
|
|
23
|
+
return_projections[class_name] = block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def observed_methods(target)
|
|
27
|
+
(method_names.empty? ? target.public_instance_methods(false) : method_names) - excluded
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
External = Struct.new(:name, :method_names, keyword_init: true) do
|
|
32
|
+
def initialize(name:, **)
|
|
33
|
+
super(name:, method_names: [])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def methods(*names)
|
|
37
|
+
self.method_names = names.map(&:to_sym)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Definition
|
|
42
|
+
attr_reader :subjects, :externals, :canonicalization, :driver_config, :formal_config
|
|
43
|
+
|
|
44
|
+
def initialize
|
|
45
|
+
@subjects = {}
|
|
46
|
+
@externals = {}
|
|
47
|
+
@canonicalization = {}
|
|
48
|
+
@formal_config = {}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def observe(name, &block)
|
|
52
|
+
subject = Subject.new(name: name)
|
|
53
|
+
subject.instance_eval(&block) if block
|
|
54
|
+
subjects[name] = subject
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def external(name, &block)
|
|
58
|
+
item = External.new(name: name)
|
|
59
|
+
item.instance_eval(&block) if block
|
|
60
|
+
externals[name] = item
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def canonicalize(&)
|
|
64
|
+
CanonicalizationDsl.new(canonicalization).instance_eval(&)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def driver(name, **options)
|
|
68
|
+
@driver_config = { name: name.to_sym, **options }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def formal(&)
|
|
72
|
+
FormalDsl.new(formal_config).instance_eval(&)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class CanonicalizationDsl
|
|
77
|
+
def initialize(config)
|
|
78
|
+
@config = config
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def freeze_time(value) = @config[:freeze_time] = value
|
|
82
|
+
def random_seed(value) = @config[:random_seed] = value
|
|
83
|
+
def uuid_placeholder(value) = @config[:uuid_placeholder] = value
|
|
84
|
+
def float_tolerance(value) = @config[:float_tolerance] = value
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class FormalDsl
|
|
88
|
+
def initialize(config)
|
|
89
|
+
@config = config
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def pure_fragment(name) = (@config[:pure_fragments] ||= []) << name
|
|
93
|
+
def bounded_scope(**scope) = @config[:bounded_scope] = scope
|
|
94
|
+
def lts_learning(mode) = @config[:lts_learning] = mode
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|