Perlでポインタの学習練習プログラム

PerlでC言語のポインタの学習用プログラムを作りました。

ポインタの学習に役立ててください。

gccとcatの使用出来る環境で使ってください。
Linuxが良いと思います。

```perl:ptr_lesson.pl
#!/usr/bin/perl
#
# C言語プログラミング
# ポインタの使い方の練習問題を自動生成するPerlスクリプト
#
#

$filename = "ptr_lesson";       # 生成するCプログラムのファイル名
$offset = 0;                    # 仮想的なメモリアドレスの先頭番地

######################################################################
# メイン処理
#

my $program;
srand(time|$$);                 # 乱数の初期化
open(OUT, ">$filename.c");
do {
    $program = &program;        # 練習問題の作成
} while(&count($program) < 10); # ポインタアクセスの少なすぎる問題は作り直し
print OUT $program;             # 生成したプログラムをファイルに出力
close(OUT);

system("cat $filename.c");      # ソースを画面表示
system("gcc -Wall -ansi -o $filename $filename.c");     # コンパイル
print "実行結果を見ますか? [ENTER] ";
$key = <>;                      # ENTERキー入力を待つ
system("./$filename");          # 実行


######################################################################
# 関数定義
#

# Cプログラムソースを生成する関数
sub program
{
    my $program = "\n";
    my $expression, $previous, $left;

    &make_variable_name;        # 使用できる変数の変数名を定義する
    &memory_allocate;           # 使用できる変数全部にメモリを割り当てる

    # main関数本体を生成する
    $previous = "";
    for(my $i = 0; $i < 10; $i++) {
        do {    # 左辺が直前の行と同じにはならないようにする
            $expression = &expression;
            $expression =~ /([^ ]+) =/;
            $left = $1;
        } while($left eq $previous);
        &exec_expression($expression);  # 変数の値を更新
        $program .= $expression;
        #$program .= &check;    # 途中経過をプログラム中にコメントとして挿入
        $previous = $left;      # 左辺を記憶
    }

    # 右辺の式が定数のみの演算である場合には、1個の定数に変換
    $program =~ s/([\*\w]+) = ([\d\+\-\* ]+);/$1." = ".eval($2).";"/ge;

    # 「+=」の記述に変換可能な箇所は変換
    $program =~ s/(\S+) = \1 \+ (\S+);/\1 += \2;/g;
    $program =~ s/(\S+) = (\S+) \+ \1;/\1 += \2;/g;

    # 「*=」の記述に変換可能な箇所は変換
    $program =~ s/(\S+) = \1 \* (\S+);/\1 *= \2;/g;
    $program =~ s/(\S+) = (\S+) \* \1;/\1 *= \2;/g;

    # 「-=」の記述に変換可能な箇所は変換
    $program =~ s/(\S+) = \1 \- (\S+);/\1 -= \2;/g;

    # 「++」や「--」の記述に変換可能な箇所は変換
    $program =~ s/(\S+) \+= 1;/\1++;/g;
    $program =~ s/(\S+) \-= 1;/\1--;/g;
    $program =~ s/\*(\w+)(\+\+|\-\-);/(*\1)\2;/g;

    $program .= &output_printf; # printf文を出力する
    $program .= "\n";
    $program .= "    return 0;\n";
    $program .= "}\n";

    # プログラムの先頭部分を追加する
    $program = &declaration($program).$program; # 変数宣言を生成する
    $program = "#include <stdio.h>\nint main()\n{\n".$program;  # 定型記述を追加する

    return $program;
}

# 使用できる変数名・演算子を定義する関数
sub make_variable_name
{
    my $type = &rnd(0, 3);
    if($type == 0) {
        @val = ('a', 'b', 'c');
        @ptr = ('p', 'q', 'r');
    } elsif($type == 1) {
        @val = ('x', 'y', 'z');

        @ptr = ('p', 'q', 'r');
    } elsif($type == 2) {
        @val = ('x', 'y', 'z');
        @ptr = ('ptr1', 'ptr2', 'ptr3');
    } else {
        @val = ('a', 'c', 'e');
        @ptr = ('b', 'd', 'f');
    }   
    @operator = ('+', '-', '*');
}

# 変数宣言を生成する関数
sub declaration
{
    my $program = $_[0]; # 値の代入はされていないがアドレス参照されている
                         # 値変数を見つけるために、プログラム記述本体を使用する
    my $variable;

    # 値変数の宣言を生成
    my $declaration = "    int ";
    foreach $variable (sort @val) {
        # 実際に使用されている変数だけ変数宣言を行なう
        if($memory[$address{$variable}] ne "X" ||
           $program =~ /&$variable/) {
            $declaration .= $variable.", ";
        }
    }
    $declaration =~ s/, $//;    # 余分な最後の「, 」を除去
    $declaration .= ";\n";
    $declaration =~ s/    int ;\n//;    # 万一、値変数が1つも無ければ、文全体を消去

    # ポインタ変数の宣言を生成
    foreach $variable (sort @ptr) {
        # 実際に使用されている変数だけ変数宣言を行なう
        if($memory[$address{$variable}] ne "X") {
            $declaration .= "    int* ".$variable.";\n";
        }
    }

    return $declaration;
}

# printf文を出力する関数
sub output_printf
{
    my $variable;
    my $printfs = "";

    # 値変数のprintf文
    foreach $variable (sort @val) {
        if($memory[$address{$variable}] ne "X") {
            $printfs .= "    printf(\"$variable = %d\\n\", $variable);\n";
        }
    }

    # ポインタ変数のprintf文
    foreach $variable (sort @ptr) {
        if($memory[$address{$variable}] ne "X" &&
           $memory[$memory[$address{$variable}]] ne "X") {
            $printfs .= "    printf(\"*$variable = %d\\n\", *$variable);\n";
        }
    }

    return $printfs;
}

# プログラム中のポインタアクセスの数を数える関数
sub count
{
    my $program = $_[0];
    my $count = $program =~ s/[\&\*][a-z]\w*//g;        # ポインタアクセスの数をカウント
    return $count;
}

# 代入式を生成する関数
sub expression

{
    my $expression, $type;
    if(&exist_defined_val == 1) {       # 既に値の設定されている変数が存在するならば
        $type = &rnd(0, 2);     # 「++」「+=」などの記述も選択肢に含める
    } else {
        $type = &rnd(0, 1);
    }
    if($type == 0) {            # ポインタ代入式を生成
        $expression = &expression_ptr;
    } elsif($type == 1) {       # 値代入式を生成
        $expression = &expression_val;
    } else {                    # 特殊な形の値代入式を生成
        $expression = &expression_vals;
    }
    return $expression;
}

# 特殊な形の値代入式を生成する関数
sub expression_vals
{
    my $left, $right, $item, $op, $value;
    do {
        $left = &item_val_left_and_right;       # 左辺にも右辺にも使用できる項
        $op = &operator;        # 演算子を生成
        if(&rnd(0, 2) == 0) {   # 確率約3分の1で「++」「--」の記述を生成する
            $item = "1";
        } else {                # それ以外の場合は、一般の右辺の項
            $item = &item_val_right;
        }
        $right = $left." ".$op." ".$item;       # 右辺
        $value = &calc_expression($right);      # 右辺の計算を実行し、結果を$valueに格納
    } while($value > 100 || $value < -100 ||
            $right =~ /\* 1$/ || $right =~ /\* 1 / ||
            $right =~ /(\S+) .*\- \1$/ || $right =~ /\- (\S+) .*\+ \1$/);
     # 数値の絶対値が大きくなりすぎる式、1との乗算、同じもの同士の減算は不許可

    return "    ".$left." = ".$right.";\n";
}

# 値代入式を生成する関数
sub expression_val
{
    my $left, $right, $item, $op, $value, $num, $i, $r;
    do {
        $left = &item_val_left; # 左辺
        $r = &rnd(0, 29);       # 右辺の項数を決定するための乱数
        if($r < 1) {            # 項数が少なくなる傾向にあるのを補正する
            $num = 1;
        } elsif($r < 8) {
            $num = 2;
        } else {
            $num = 3;
        }
        $right = "";            # 右辺
        for($i = 1; $i <= $num; $i++) { # num個の右辺項を生成
            $item = &item_val_right;
            $right .= $item;
            if($i < $num) {
                $op = &operator;        # 演算子を生成
                $right .= " ".$op." ";
            }
        }
        $value = &calc_expression($right);      # 右辺の計算を実行し、結果を$valueに格納
    } while($left eq $right || $value > 100 || $value < -100 ||
            $right =~ /\* 1$/ || $right =~ /\* 1 / || $right =~ / 1 \*/ || $right =~ /^1 \*/ ||
            $right =~ /(\S+) .*\- \1$/ || $right =~ /(\S+) .*\- \1 / ||
            $right =~ /\- (\S+) .*\+ \1$/ || $right =~ /\- (\S+) .*\+ \1 /);
     # 左辺と右辺が同じ式、数値の絶対値が大きくなりすぎる式、
     # 1との乗算、同じもの同士の減算(の可能性がある記述)は不許可

    return "    ".$left." = ".$right.";\n";
}

# ポインタ代入式を生成する関数
sub expression_ptr
{
    my $left, $right;
    do {
        $left = &item_ptr_left;         # 左辺
        $right = &item_ptr_right;       # 右辺
    } while($left eq $right);
    return "    ".$left." = ".$right.";\n";
}

# 値代入式の左辺の項を生成する関数
sub item_val_left
{
    my $item, $type;
    do {
        $type = &rnd(0, 2);
        if($type == 0) {
            $item = &val;
        } else {
            $item = "*".&ptr;
        }
    } while($item =~ /^\*([a-z]\w*)$/ && $memory[$address{$1}] eq "X");
    # 「*ポインタ変数」に代入するときは、ポインタ変数に値が設定されている必要がある

    return $item;
}

# 値代入式の右辺の項を生成する関数
sub item_val_right
{
    my $item, $type;
    do {
        $type = &rnd(0, 4);
        if($type == 0) {
            $item = &const;
        } elsif($type == 1) {
            $item = &val;
        } else {
            $item = "*".&ptr;
        }
    } while(&defined($item) == 0);      # 右辺の値は“定義値”である必要がある
    return $item;
}

# 値代入式の左辺にも右辺にも使用できる項を生成する関数
sub item_val_left_and_right
{
    my $item, $type;
    do {
        $type = &rnd(0, 1);
        if($type == 0) {
            $item = &val;
        } else {
            $item = "*".&ptr;
        }
    } while(&defined($item) == 0);  # 値が設定されている変数である必要がある

    return $item;
}

# ポインタ代入式の左辺の項を生成する関数
sub item_ptr_left
{
    my $item;
    $item = &ptr;       # ポインタ代入式の左辺になるのはポインタ変数のみ
    return $item;
}

# ポインタ代入式の右辺の項を生成する関数
sub item_ptr_right
{
    my $item, $type;
    do {
        $type = &rnd(0, 1);
        if($type == 0) {
            $item = &ptr;
        } else {
            $item = "&".&val;
        }
    } while(&defined($item) == 0);      # 右辺の値は“定義値”である必要がある
    return $item;
}

# ランダムな定数を生成する関数
sub const
{
    return &rnd(1, 10); # プログラム中に登場させる定数は1～10の範囲とする
}

# ランダムな値変数を生成する関数
sub val
{
    my $val = &rnd(0, @val - 1);
    return "$val[$val]";
}

# ランダムなポインタ変数を生成する関数
sub ptr
{
    my $ptr = &rnd(0, @ptr - 1);
    return "$ptr[$ptr]";
}

# ランダムな演算子を生成する関数
sub operator
{
    my $operator = &rnd(0, @operator - 1);
    return "$operator[$operator]";
}

# 変数のメモリ割り当てを行なう関数
sub memory_allocate
{
    my $address = $offset;      # メモリアドレスを初期値に設定
    my $variable;
    %address = ();      # アドレス変換テーブルを初期化
    @memory = ();       # (仮想的な)メモリを初期化

    foreach $variable (sort (@val, @ptr)) {     # 全変数のメモリ割り当てを行なう
        $address{$variable} = $address; # アドレス変換テーブルに登録
        $memory[$address] = "X";        # そのアドレスのメモリを“未定義値”に
        $address++;     # 割り当て用メモリアドレスを次の番地に
    }
}

# 代入式を実行する関数
sub exec_expression
{
    $_[0] =~ /\s*([\*\w]+) = ([^;]+);/; # 式を左辺と右辺に分離する
    my $left = $1;      # 左辺
    my $right = $2;     # 右辺
    if($left =~ /^[a-z]\w*$/) {         # 変数への代入
        # 左辺の変数に右辺の計算結果を格納する
        $memory[$address{$left}] = &calc_expression($right);
    } elsif($left =~ /^\*([a-z]\w*)$/) {# ポインタ参照先への代入
        # 左辺の参照先に右辺の計算結果を格納する
        $memory[$memory[$address{$1}]] = &calc_expression($right);
    } else {
        print "Error: function \"calc_expression\"\n";
        die;
    }   
}

# 式の値を求める関数
sub calc_expression
{
    my $expression = $_[0];
    $expression =~ s/([\*\&]?[a-z]\w*|\d+)/&calc_item($1)/ge; # 右辺の式中の変数を値に置換
    return eval($expression);   # 右辺の計算を実行
}

# 項の値を求める関数
sub calc_item
{
    if($_[0] =~ /^(\d+)$/) {
        # 数値定数は、その数値そのもの
        return $1;
    } elsif($_[0] =~ /^([a-z]\w*)$/) {
        # 「変数」は、それに格納されている値
        return $memory[$address{$1}];
    } elsif($_[0] =~ /^\*([a-z]\w*)$/) {
        # 「*変数」は、その参照先に格納されている値
        return $memory[$memory[$address{$1}]];
    } elsif($_[0] =~ /^&([a-z]\w*)$/) {
        # 「&変数」は、その変数のアドレス
        return $address{$1};
    } else {
        print "Error: function \"value\"\n";
        die;
    }
}

# “定義値”か“未定義値”かを判定する関数
#    ※定義値   = 数値
#      未定義値 = "X"
#
#    [引数]   数値定数 または 変数 または &変数 または *変数
#    [返戻値] 1:定義値 0:未定義値
sub defined
{
    if($_[0] =~ /^\d+$/) {      # 数値定数
        return 1;       # 常に“定義値”
    } elsif($_[0] =~ /^([a-z]\w*)$/) {  # 変数
        if($memory[$address{$1}] ne "X") {
            return 1;
        } else {
            return 0;
        }
    } elsif($_[0] =~ /^&[a-z]\w*$/) {   # &変数
        return 1;       # 常に“定義値”
    } elsif($_[0] =~ /^\*([a-z]\w*)$/) {        # *変数
        if($memory[$address{$1}] eq "X") {      # そのポインタ変数自体が未定義
            return 0;
        } elsif($memory[$memory[$address{$1}]] eq "X") {        # そのポインタ変数が指している先の値が未定義
            return 0;
        } else {
            return 1;
        }
    } else {
        print "Error: function \"defined\"\n";
        die;
    }
}

# 既に値の設定されている変数が存在するかどうかを調べる関数
sub exist_defined_val
{
    my $flag = 0;
    my $variable;
    foreach $variable (@val) {
        if(&defined($variable) == 1) {
            $flag = 1;
        }
    }
    return $flag;
}

# x以上y以下の範囲のランダムな整数を生成する関数
#
#   [引数]   第1引数: x
#            第2引数: y
#   [返戻値] x以上y以下の範囲のランダムな整数
sub rnd
{
    return int(rand() * ($_[1] - $_[0] + 1)) + $_[0];
}

# 変数名・メモリアドレス・そのアドレスの内容の
# 一覧を表示する関数 (デバッグor学習用)
sub check
{
    my $check_result = "    /* ";

    foreach my $variable (sort keys %address) {
        $check_result .= $variable."=";
        $check_result .= "[".$address{$variable}."]=";
        $check_result .= $memory[$address{$variable}]." ";
    }

    #$check_result .= "\n       ";
    #my $size = @memory;
    #for(my $address = $offset; $address < $offset + $size; $address++) {
        #$check_result .= "[".$address."]=".$memory[$address]." ";
    #}

    $check_result .= " */\n\n";

    return $check_result;
}

```

実行結果例

![ptr_lesson_pl_jpg.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615735/146c7c8d-5d78-3c36-241d-f3fac579f3c2.jpeg)