pythonでも中括弧{ }でブロックを作れるようにしてみた【php】

# 0. 動機
「pythonのブロックはc言語やjavaみたいに中括弧(`{` `}`)でやるんじゃなくて、インデントを使うんだよ」というのを そういうものだと受け入れたら、今までできたはずの言語でうっかり同じことをしてしまい、構文エラーしまくることになる事例を知った。
僕自身はpythonについて、ほとんどしらない。
~~唯一知ってるのは「pythonがインデントに意味を持たせたのは有害」ということだけ。~~
→pythonには「1行1ステートメント」という考え方があり、だからこそブロックの括弧やセミコロンは不要[1]というのは後から知った。

~~そこで、~~インデントの代わりに`{` `}`を使ってブロックを記述したpythonコードを、正しいpythonコードに置き換えるプログラムを作ろうという気になった。
phpがインストールされているサーバからみんなが使えるように、phpで作る。

# 1.プログラムの構成
1. テキストエリアから、インデントの代わりにブロックで書かれたpyファイルの中身を受け取る(元コンテンツと呼ぶことにする)
2. 元ファイルのブロックはインデントに置き換え、元ファイルのインデントは捨てる
3. 結果を表示

## 1-1.
次のようなblock2indent.htmlを作ってみた。

```html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<title>インデントが不要でブロックが使えるpython</title>
	</head>
	<body>
        ①インデントの代わりにブロックを用いたpythonコードを入力<br>
        <form method="post" action="block2indent.php">
            <textarea name="content" cols="50" rows="5"></textarea>
            <br>
            <br>
            ②改行コードを選択<br>
            <select name="returnCode">
                <option value="rn"> &#92;r&#92;n </option>
                <option value="n"> &#92;n </option>
                <option value="r"> &#92;r </option>
            </select>
            <br>
            <br>
            ③<input type="submit">
        </form>
	</body>
</html>
````
余談だが、Eclipseで「PHP 開発ツール (PDT)」をインストールし、エディタに「<」とだけ打ち込むと、入力候補として<html>という選択肢が表示される。これを無視してバックスペースキーを押して「<」を消すと、cssやhtmlなど様々なコードのテンプレートが選べるようになる。これでhtml 4.01 strictのコードなども容易にかける。

block2indent.htmlをクロームで開くと、図1.1.1のように表示され、「送信」を押すと、「ファイルが見つかりません」のエラーへ遷移した。
<img width="376" alt="1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319477/f3077c22-132c-4809-6712-fda3c4c7be0d.png">

図1.1.1 block2indent.html

## 1-2.
block2indent.htmlを名前変更しblock2indent.phpとした。
そして、次のように追記した。

```php
<?php
$samplePy=
"
class Man:{
    def __inin__(self, name):{
        self.name = name;
        print(\"インスタンス生成および初期化に成功\");
    }

    def hello(self):{
        print(\"Hello \" + self.name + \"!\")
    }
}

m = Man(\"David\");
m.hello();
";

/*/
if(isset($_POST['content']))
    print block2indent(killIndent($_POST['content']));
/*/
if(true)
    print block2indent(killIndent($samplePy));
//*/
else printHtml();


function printHtml()
{
    print
'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<title>インデントが不要でブロックが使えるpython</title>
	</head>
	<body>
        ①インデントの代わりにブロックを用いたpythonコードを入力<br>
        <form method="post" action="block2indent.php">
            <textarea name="content" cols="50" rows="5"></textarea>
            <br>
            <br>
            ②改行コードを選択<br>
            <select name="returnCode">
                <option value="rn"> &#92;r&#92;n </option>
                <option value="n"> &#92;n </option>
                <option value="r"> &#92;r </option>
            </select>
            <br>
            <br>
            ③<input type="submit">
        </form>
	</body>
</html>';
}

    function killIndent($indentAlive)
    {
        $indentDying = preg_replace("/\A( |\t)+/i", "", $indentAlive);
        return preg_replace("/(\n|\r|(\r\n))( |\t)+/i", "$1", $indentDying);
    }

    function block2indent($blockAlive)
    {
        //行頭以外でブロックが開始していたら、ブロック開始直前に改行挿入
        //同様に、ブロックの終わりも改行を強制
        $blockDying = putNewLineBeforeBlockOpen($blockAlive);
        $blockDying = putNewLineAfterBlockClose($blockDying);

        //各行ごとの「深さ」を調べ、その分だけスペース4つを挿入
        $blockDying = createIndent($blockDying);

        //ブロックをなくす
        return killBlock($blockDying);
    }

        function putNewLineBeforeBlockOpen($blockOpenWhileARow)
        {
            return preg_replace("/(.+){/i", "$1\r\n{", $blockOpenWhileARow);
        }

        function putNewLineAfterBlockClose($blockCloseWhileARow)
        {
            return preg_replace("/}(.+)/i", "}\r\n$1", $blockCloseWhileARow);
        }

        function createIndent($noIndent)
        {
            //改行コードを統一
            $returnCode
            =
            strcmp($_POST['returnCode'], "r")===0 ? "\r":
            strcmp($_POST['returnCode'], "n")===0 ? "\n":
            "\r\n";

            $noIndent = preg_replace("/\r([^\n])/i", $returnCode."$1", $noIndent);
            $noIndent = preg_replace("/([^\r])\n/i", "$1".$returnCode, $noIndent);

            //改行区切りで配列に入れる
            $lines = explode($returnCode, $noIndent);

            //各行に対して、深さを調べる
            $i = 0;
            $depth = 0;
            $lineIdx2depth = array();
            foreach($lines as $line)
            {
                if(strpos($line, "{")===0)
                    $depth++;

                $lineIdx2depth[$i] = $depth;

                if(strpos($line, "}")!==false)
                    $depth--;

                $i++;
            }

            //書き込む
            $i = 0;
            $ownIndent = "";
            foreach($lines as $line)
            {
                for($j=0; $j < $lineIdx2depth[$i]*4; $j++)
                    $ownIndent .= " ";
                $ownIndent .= $line;
                $ownIndent .= $returnCode;
                $i++;
            }

            return $ownIndent;

        }


        function killBlock($blockAlive)
        {
            $returnCode
            =
            strcmp($_POST['returnCode'], "r")===0 ? "\r":
            strcmp($_POST['returnCode'], "n")===0 ? "\n":
            "\r\n";

            return preg_replace("/".$returnCode."( |\t)*{( |\t)*/i", "", preg_replace("/".$returnCode."( |\t)*}( |\t)*".$returnCode."/i", "", $blockAlive));

        }

```

`printHtml`関数が実行されると、1-1でみたblock2indent.htmlの内容がphpにより出力される。
しかし今回は`if(true)`に対する`else`として記述されているため、`printHtml`は呼び出されない。その代わり、`$samplePy`に対して`killIndent`関数や`block2indent`関数が適用されたものが出力される。

`$samplePy`の内容は、次の通り、`{` `}`を用いて書かれた「謝ったpythonコード」である。

```python
class Man:{
    def __inin__(self, name):{
        self.name = name;
        print("インスタンス生成および初期化に成功");
    }

    def hello(self):{
        print("Hello " + self.name + "!")
    }
}

m = Man("David");
m.hello();
``` 

これに`killIndent`関数が適用されると、次のようになる。

```python
class Man:{
def __inin__(self, name):{
self.name = name;
print("インスタンス生成および初期化に成功");
}

def hello(self):{
print("Hello " + self.name + "!")
}
}

m = Man("David");
m.hello();
```

さらに`block2indent`関数が適用されると、次のように正しいpythonコードが出力される。

```python

class Man:
    def __inin__(self, name):
        self.name = name;
        print("インスタンス生成および初期化に成功");    
    
    def hello(self):
        print("Hello " + self.name + "!")    

m = Man("David");
m.hello();
```


実際には次の手順に従って実行されている。

1. `{`の直前と`}`の直後に改行がなければ挿入
2. 各行ごとに`{` `}`の深さ`$depth`の取得
3. 各行ごとに4×`$depth`個だけ空白を先頭に追加
4. `{`や`}`を行ごと削除

# 1-3

プログラム自体は完成したので、後はサーバにアップロードして、外部からhttp通信でアクセスできるようにすることを考えよう。

1-2で書いたblock2indent.phpに対し、

```php
/*/
if(isset($_POST['content']))
    print block2indent(killIndent($_POST['content']));
/*/
if(true)
    print block2indent(killIndent($samplePy));
//*/
else printHtml();
```
の部分の先頭に`/`を追加し、

```php
//*/
if(isset($_POST['content']))
    print block2indent(killIndent($_POST['content']));
/*/
if(true)
    print block2indent(killIndent($samplePy));
//*/
else printHtml();
```
として、サーバへアップロードするだけでよい。
こうすることで、サーバ上のblock2indent.phpは次のように振舞うことになる。

1. 自身へ何もpost送信されていない場合(＝最初にアクセスされたとき)
 1. 1-1で書いたblock2indent.htmlと同じものを出力
 2. 送信ボタンが押されると、 自分自身へテキストエリア内の文字列をpost送信
2. 自身へテキストエリア内の文字列がpost送信された場合
 1. 1-2のアルゴリズム通り、pythonコードへ変換を行う
 2. 結果を出力する

<img width="376" alt="2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319477/1649d407-0612-195e-8aa6-ee81d0ea5fec.png">
図1.3.1 「誤ったpythonコード」を入力する例

実際、図1.3.1のように入力し、送信ボタンを押すと、次のように正しいコードが出力された。(但し、改行コードの選択肢に&lt;br>は含めていないので、「ソースを表示」から見る必要がある。)

```python
class Woman:
    def __init__(self, name):
        self.name = name;
        print("インスタンス生成および初期化に成功");    
    
    def hello(self):
        print("Hello " + self.name + "!")    

m = Man("Alice");
m.hello();
```

入力例のインデントを多少変えても、出力は同じになる。

# 2. このプログラムを利用したい方

つまらないものですが、http://rights-for.men/block2indent.php からどうぞ。


# 3.参考
[1]https://python.keicode.com/lang/control-basic-rule.php