ありがとうございます。やはり Devel::Cover は実行時の情報をキャプチャーして活用している
Tool Interactions
Tool: Write /home/hkoba/db/monthly/202512/tmp/Devel--Cover/examples/static_logic_analyzer.pl
#!/usr/bin/env perl
use strict;
use warnings;
use v5.12;
#
# 静的にPerlコードの論理構造を解析し、真理値表を生成するサンプル
# Devel::Cover の手法を参考に、実行せずに B::Deparse で解析
#
package LogicAnalyzer;
use B qw(main_cv svref_2object OPf_KIDS);
use B::Deparse;
# 論理演算子の OP 名
my %LOGIC_OPS = map { $_ => 1 } qw(and or dor xor andassign orassign dorassign cond_expr);
sub new {
my $class = shift;
my $self = bless {
deparse => B::Deparse->new,
conditions => [], # 検出した条件式
}, $class;
return $self;
}
#------------------------------------------------------------------------------
# サブルーチンを解析
#------------------------------------------------------------------------------
sub analyze_sub {
my ($self, $coderef) = @_;
my $cv = svref_2object($coderef);
return unless $cv->isa('B::CV');
$self->{deparse}{curcv} = $cv;
$self->{conditions} = [];
my $root = $cv->ROOT;
return unless $root && !$root->isa('B::NULL');
$self->_walk_op($root);
return $self->{conditions};
}
#------------------------------------------------------------------------------
# OP ツリーを再帰的に走査
#------------------------------------------------------------------------------
sub _walk_op {
my ($self, $op) = @_;
return unless $op && !$op->isa('B::NULL');
my $name = $op->name;
# 論理演算子を検出
if ($LOGIC_OPS{$name}) {
my $info = $self->_analyze_logop($op);
push @{$self->{conditions}}, $info if $info;
}
# 子ノードを再帰的に走査
if ($op->flags & OPf_KIDS) {
for (my $kid = $op->first; $kid && !$kid->isa('B::NULL'); $kid = $kid->sibling) {
$self->_walk_op($kid);
}
}
}
#------------------------------------------------------------------------------
# 論理演算子を解析
#------------------------------------------------------------------------------
sub _analyze_logop {
my ($self, $op) = @_;
my $name = $op->name;
my $left_op = $op->first;
return unless $left_op && !$left_op->isa('B::NULL');
my $right_op;
if ($name eq 'cond_expr') {
# 三項演算子: cond ? true : false
my $cond_op = $left_op;
my $true_op = $cond_op->sibling;
my $false_op = $true_op ? $true_op->sibling : undef;
return {
type => 'cond_expr',
op => '?:',
condition => $self->_deparse_op($cond_op),
true_branch => $self->_deparse_op($true_op),
false_branch => $self->_deparse_op($false_op),
line => $self->_get_line($op),
};
}
$right_op = $left_op->sibling;
return unless $right_op && !$right_op->isa('B::NULL');
my $op_symbol = $self->_op_to_symbol($name);
my $left_text = $self->_deparse_op($left_op);
my $right_text = $self->_deparse_op($right_op);
# 右辺が定数かどうか判定
my $right_is_const = $self->_is_constant($right_op);
return {
type => $name,
op => $op_symbol,
left => $left_text,
right => $right_text,
right_const => $right_is_const,
line => $self->_get_line($op),
# 真理値表のタイプを決定
tt_type => $self->_determine_tt_type($name, $right_is_const),
};
}
#------------------------------------------------------------------------------
# OP をテキストに変換
#------------------------------------------------------------------------------
sub _deparse_op {
my ($self, $op) = @_;
return '' unless $op && !$op->isa('B::NULL');
local $@;
my $text = eval { $self->{deparse}->deparse($op, 0) };
return '' if $@;
# 前後の空白を除去
$text =~ s/^\s+//;
$text =~ s/\s+$//;
return $text;
}
#------------------------------------------------------------------------------
# OP 名からシンボルへ
#------------------------------------------------------------------------------
sub _op_to_symbol {
my ($self, $name) = @_;
my %symbols = (
and => '&&',
andassign => '&&=',
or => '||',
orassign => '||=',
dor => '//',
dorassign => '//=',
xor => 'xor',
);
return $symbols{$name} // $name;
}
#------------------------------------------------------------------------------
# 定数かどうか判定
#------------------------------------------------------------------------------
sub _is_constant {
my ($self, $op) = @_;
return 0 unless $op && !$op->isa('B::NULL');
my $name = $op->name;
# const, undef, refgen などは定数扱い
return 1 if $name =~ /^(?:const|undef|s?refgen|null)$/;
# die, return, last, next, redo, goto も「評価されない」扱い
return 1 if $name =~ /^(?:die|return|last|next|redo|goto)$/;
return 0;
}
#------------------------------------------------------------------------------
# 真理値表タイプを決定
#------------------------------------------------------------------------------
sub _determine_tt_type {
my ($self, $op_name, $right_is_const) = @_;
if ($op_name eq 'xor') {
return 'xor_4';
} elsif ($op_name =~ /^(?:and|andassign)$/) {
return $right_is_const ? 'and_2' : 'and_3';
} elsif ($op_name =~ /^(?:or|orassign|dor|dorassign)$/) {
return $right_is_const ? 'or_2' : 'or_3';
}
return 'unknown';
}
#------------------------------------------------------------------------------
# 行番号を取得
#------------------------------------------------------------------------------
sub _get_line {
my ($self, $op) = @_;
# COP (statement) から行番号を取得するのは複雑なので、簡易版
return 0; # 実際にはより複雑な処理が必要
}
#==============================================================================
# 真理値表生成
#==============================================================================
package TruthTable;
sub new {
my ($class, $condition) = @_;
return bless { condition => $condition }, $class;
}
sub generate {
my ($self) = @_;
my $cond = $self->{condition};
my $type = $cond->{tt_type} // '';
if ($type eq 'and_3') {
return $self->_and_3_table($cond);
} elsif ($type eq 'and_2') {
return $self->_and_2_table($cond);
} elsif ($type eq 'or_3') {
return $self->_or_3_table($cond);
} elsif ($type eq 'or_2') {
return $self->_or_2_table($cond);
} elsif ($type eq 'xor_4') {
return $self->_xor_4_table($cond);
} elsif ($cond->{type} eq 'cond_expr') {
return $self->_cond_expr_table($cond);
}
return [];
}
sub _and_3_table {
my ($self, $cond) = @_;
my ($l, $r, $op) = @{$cond}{qw(left right op)};
return [
{ inputs => [0, 'X'], result => 0, desc => "!($l)" },
{ inputs => [1, 0], result => 0, desc => "($l) $op !($r)" },
{ inputs => [1, 1], result => 1, desc => "($l) $op ($r)" },
];
}
sub _and_2_table {
my ($self, $cond) = @_;
my ($l, $op) = @{$cond}{qw(left op)};
return [
{ inputs => [0], result => 0, desc => "!($l)" },
{ inputs => [1], result => 1, desc => "($l)" },
];
}
sub _or_3_table {
my ($self, $cond) = @_;
my ($l, $r, $op) = @{$cond}{qw(left right op)};
return [
{ inputs => [1, 'X'], result => 1, desc => "($l)" },
{ inputs => [0, 1], result => 1, desc => "!($l) $op ($r)" },
{ inputs => [0, 0], result => 0, desc => "!($l) $op !($r)" },
];
}
sub _or_2_table {
my ($self, $cond) = @_;
my ($l, $op) = @{$cond}{qw(left op)};
return [
{ inputs => [1], result => 1, desc => "($l)" },
{ inputs => [0], result => 0, desc => "!($l)" },
];
}
sub _xor_4_table {
my ($self, $cond) = @_;
my ($l, $r) = @{$cond}{qw(left right)};
return [
{ inputs => [0, 0], result => 0, desc => "!($l) xor !($r)" },
{ inputs => [0, 1], result => 1, desc => "!($l) xor ($r)" },
{ inputs => [1, 0], result => 1, desc => "($l) xor !($r)" },
{ inputs => [1, 1], result => 0, desc => "($l) xor ($r)" },
];
}
sub _cond_expr_table {
my ($self, $cond) = @_;
my ($c, $t, $f) = @{$cond}{qw(condition true_branch false_branch)};
return [
{ inputs => [1], result => $t, desc => "($c) ? $t : ..." },
{ inputs => [0], result => $f, desc => "!($c) ? ... : $f" },
];
}
sub format_text {
my ($self) = @_;
my $table = $self->generate;
return '' unless @$table;
my $cond = $self->{condition};
my @lines;
# ヘッダー
my $expr = $cond->{type} eq 'cond_expr'
? "$cond->{condition} ? $cond->{true_branch} : $cond->{false_branch}"
: "$cond->{left} $cond->{op} $cond->{right}";
push @lines, "Expression: $expr";
push @lines, "Type: $cond->{tt_type}" if $cond->{tt_type};
push @lines, "-" x 60;
# 入力ヘッダー
my $n_inputs = @{$table->[0]{inputs}};
my @headers = map { chr(ord('A') + $_) } 0 .. $n_inputs - 1;
push @lines, sprintf("| %s | Result | Description", join(" | ", @headers));
push @lines, "-" x 60;
# 各行
for my $row (@$table) {
my $inputs = join(" | ", map { $_ eq 'X' ? 'X' : $_ } @{$row->{inputs}});
push @lines, sprintf("| %s | %s | %s", $inputs, $row->{result}, $row->{desc});
}
push @lines, "-" x 60;
return join("\n", @lines);
}
#==============================================================================
# メイン
#==============================================================================
package main;
# テスト用サブルーチン
sub test_simple_and {
my ($a, $b) = @_;
return $a && $b;
}
sub test_simple_or {
my ($a, $b) = @_;
return $a || $b;
}
sub test_or_with_const {
my ($a) = @_;
return $a || die "error";
}
sub test_complex {
my ($a, $b, $c) = @_;
return ($a || $b) && $c;
}
sub test_ternary {
my ($x) = @_;
return $x ? "yes" : "no";
}
sub test_xor {
my ($a, $b) = @_;
return $a xor $b;
}
sub test_dor {
my ($a, $b) = @_;
return $a // $b;
}
# 解析実行
my $analyzer = LogicAnalyzer->new;
print "=" x 60, "\n";
print "Static Logic Structure Analyzer\n";
print "=" x 60, "\n\n";
my @test_subs = (
[\&test_simple_and, 'test_simple_and: $a && $b'],
[\&test_simple_or, 'test_simple_or: $a || $b'],
[\&test_or_with_const, 'test_or_with_const: $a || die'],
[\&test_complex, 'test_complex: ($a || $b) && $c'],
[\&test_ternary, 'test_ternary: $x ? "yes" : "no"'],
[\&test_xor, 'test_xor: $a xor $b'],
[\&test_dor, 'test_dor: $a // $b'],
);
for my $test (@test_subs) {
my ($coderef, $name) = @$test;
print "### $name\n\n";
my $conditions = $analyzer->analyze_sub($coderef);
if (@$conditions) {
for my $cond (@$conditions) {
my $tt = TruthTable->new($cond);
print $tt->format_text, "\n\n";
}
} else {
print "(No logical conditions found)\n\n";
}
}
# 文字列からコードを解析するデモ
print "=" x 60, "\n";
print "Analyzing code from string\n";
print "=" x 60, "\n\n";
my $code = q{
sub complex_logic {
my ($x, $y, $z) = @_;
if ($x && $y || $z) {
return 1;
}
return 0;
}
};
print "Source code:\n$code\n";
# eval でコードをコンパイル (実行はしない)
eval $code;
if ($@) {
die "Compile error: $@";
}
my $conditions = $analyzer->analyze_sub(\&complex_logic);
for my $cond (@$conditions) {
my $tt = TruthTable->new($cond);
print $tt->format_text, "\n\n";
}
Results
Result:
File created successfully at: /home/hkoba/db/monthly/202512/tmp/Devel--Cover/examples/static_logic_analyzer.pl
サンプルコードを書きました。実行して結果を確認してみましょう。
uuid: assistant: f0d2a0ce-018f-4539-8449-18d2c54af0c0, assistant: af324036-ebe7-467e-8151-8f6ec15feccd