WinUI3で超簡易電卓を作る練習(入力データの妥当性チェック-チェック処理編)

:::note info
WinUI3を学びたく、練習として簡単な電卓を作ってみることにしました。
今回は入力データの妥当性チェック処理のうち、チェック処理の実装を行います。

C#とxamlに関しても初心者ですのでお手柔らかにお願いします。
:::

## 前回の記録

https://qiita.com/nogino_sanebou/items/707638481c565d3e2a9e

## やること

前回定義だけした`MainWindow.xaml.cs`の`ValidFormula`関数にチェック処理を追加していきます。
チェックする項目が複数ありますのでひとつずつクリアしていこうと思います。

チェック項目は以下のようになっています。

<table>
  <caption>エラーコード一覧</caption>
  <thead>
    <tr><th>エラーコード</th> <th>エラーメッセージ</th></tr>
  </thead>
  <tr><td>S1</td> <td>正常な式、メッセージなし</td></tr>
  <tr><td>E1</td> <td>式の先頭に「× ÷ . )」が出現しました。</td></tr>
  <tr><td>E2</td> <td>「×、÷、.」が連続して出現しました。</td></tr>
  <tr><td>E3</td> <td>「.」の後に数字以外が出現しました。</td></tr>
  <tr><td>E4</td> <td>末尾に数字、「)」以外が出現しました。</td></tr>
  <tr><td>E5</td> <td>先頭が「+ - (」の場合、<br>次に出現できるのは数字、「(」のみです。</td></tr>
  <tr><td>E6</td> <td>式の途中に連続する「+ -」が３個以上出現しました。</td></tr>
  <tr><td>E7</td> <td>カッコが対になっていません。</td></tr>
  <tr><td>E8</td> <td>数字、「. )」の次に「(」が出現しました。</td></tr>
  <tr><td>E9</td> <td>「(」の後に「× ÷ . )」が出現しました。</td></tr>
  <tr><td>E10</td> <td>記号のみで構成されています。</td></tr>
  <tr><td>E11</td> <td>先頭が「(」の場合、次に出現できるのは数字、「+ - (」のみです。</td></tr>
</table>


以下のコードが実装する関数のひな型になります。
```cs:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
        // 現在の入力データを取得します。
        string formula = InputViewModel.Formula;
        if (formula.Length == 0)
        {
            return ErrorViewModel.ERROR_CODE.S1;
        }

        // formulaを使用して式をチェックしていきます。

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```


## 文字数が１文字かつその文字が数字以外でないかをチェック

数字であるかのチェックは`int.TryParse`メソッドを使用して、数値に変換できるかチェックすることで判定します。
この時、`out`先はありませんので、`_`を指定して破棄します。

```diff_c_sharp:MainWindow.xaml.cs
private ErrorViewModel.ERROR_CODE ValidFormula()
{
    // 現在の入力データを取得します。
    string formula = InputViewModel.Formula;
    if (formula.Length == 0)
    {
        return ErrorViewModel.ERROR_CODE.S1;
    }

+   // 文字数が１文字で、かつその文字が数字以外の場合はエラーとします。
+   if (formula.Length == 1 && !(int.TryParse(formula[0].ToString(), out _)))
+   {
+       return ErrorViewModel.ERROR_CODE.E10;
+   }

    // エラーがなかった場合、正常として返します。
    return ErrorViewModel.ERROR_CODE.S1;
}
```


## カッコが対になっているかをチェック
これは文字列の中にある`(`の数と`)`の数が一致しているかを調べれば実現できそうです。

カッコの数は`string`クラスにある`Count`メソッドを使用します。
以下のように実装しました。

```diff_c_sharp:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
        // 現在の入力データを取得します。
        string formula = InputViewModel.Formula;
        if (formula.Length == 0)
        {
            return ErrorViewModel.ERROR_CODE.S1;
        }

        // 文字数が１文字で、かつその文字が数字以外の場合はエラーとします。
        if (formula.Length == 1 && int.TryParse(formula[0].ToString(), out _))
        {
            return ErrorViewModel.ERROR_CODE.E10;
        }

+      // 入力データのカッコが対になっていなかった場合はエラーとします。
+      if (formula.Count(c => c == '(') != formula.Count(c => c == ')'))
+      {
+          return ErrorViewModel.ERROR_CODE.E7;
+      }

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```

## 式の先頭が「× ÷ . )」でないかをチェック

式の１文字目をスライスして該当の文字がないかを比較します。
比較式を１回で済ませられるよう、"×÷.)"という文字列より`IndexOf`メソッドでスライスした文字が存在しているかをチェックします。

```diff_c_sharp:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
           // 省略

        // 入力データのカッコが対になっていなかった場合はエラーとします。
        if (formula.Count(c => c == '(') != formula.Count(c => c == ')'))
        {
            return ErrorViewModel.ERROR_CODE.E7;
        }

+       // 式の先頭が「× ÷ . )」の場合はエラーとします。
+       if ("×÷.)".IndexOf(formula[0]) > -1)
+       {
+           return ErrorViewModel.ERROR_CODE.E1;
+       }

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```

## 先頭が「+ -」の場合に次の文字が数字、「(」であるかをチェック
先頭が「+ -」であるかは`IndexOf`メソッドを使用します。

数字であるかのチェックは先ほどと同様、`int.TryParse`メソッドを使用します。

「(」であることは普通に`char`同士の比較を行います。

```diff_c_sharp:MainWindow.xaml.cs
   private ErrorViewModel.ERROR_CODE ValidFormula()
   {
           // 省略

       // 式の先頭が「× ÷ . )」の場合はエラーとします。
       if ("×÷.)".IndexOf(formula[0]) > -1)
       {
           return ErrorViewModel.ERROR_CODE.E1;
       }

+      // 式の先頭が「+ -」で、次の文字が数字、「(」以外の場合エラーとします。
+      if ("+-".IndexOf(formula[0]) > -1
+          && !(int.TryParse(formula[1].ToString(), out _) || formula[1] == '('))
+      {
+          return ErrorViewModel.ERROR_CODE.E5;
+      }

       // エラーがなかった場合、正常として返します。
       return ErrorViewModel.ERROR_CODE.S1;
   }
```


## 先頭が「(」の場合に次の文字が数字、「+ - (」であるかをチェック
先頭の比較処理は`char`同士の比較で、数字の比較は先ほどと同様に`int.TryParse`メソッドを使います。
そして「+ - (」比較処理は`IndexOf`メソッドを使用します。

```diff_c_sharp:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
           // 省略
          
        // 式の先頭が「+ -」で、次の文字が数字、「(」以外の場合エラーとします。
        if ("+-".IndexOf(formula[0]) > -1
            && !(int.TryParse(formula[1].ToString(), out _) || formula[1] == '('))
        {
            return ErrorViewModel.ERROR_CODE.E5;
        }

+       // 式の先頭が「(」で、次の文字が数字「+ - (」以外の場合エラーとします。
+       if (formula[0] == '('
+           && !(int.TryParse(formula[1].ToString(), out _) || "+-(".IndexOf(formula[1]) > -1))
+       {
+           return ErrorViewModel.ERROR_CODE.E11;
+       }

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```

## 末尾が数字、「)」以外でないかをチェック
ここからは式の中を探っていくので繰り返し処理を使ってチェックしていきます。

この処理では現在参照している位置が文字の末尾で、その文字が数字、「)」であるかをチェックします。
数字であるかは`int.TryParse`メソッドを使用し、「)」であるかは`char'型同士で比較します。

また、末尾がこの条件以外であった場合は正常終了させます。

```diff_c_sharp:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
            // 省略

        // 式の先頭が「(」で、次の文字が数字「+ - (」以外の場合エラーとします。
        if (formula[0] == '('
            && !(int.TryParse(formula[1].ToString(), out _) || "+-(".IndexOf(formula[1]) > -1))
        {
            return ErrorViewModel.ERROR_CODE.E11;
        }

+       for (int i = 0; i < formula.Length; i++)
+       {
+           // 末尾が数字、「)」以外の場合はエラーとします。
+           if (i == formula.Length - 1)
+           {
+               if (!(int.TryParse(formula[i].ToString(), out _) || formula[i] == ')'))
+               {
+                   return ErrorViewModel.ERROR_CODE.E4;
+               }
+               // そうでない場合、正常終了します。
+               else
+               {
+                   return ErrorViewModel.ERROR_CODE.S1;
+               }
+           }
+       }

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```

## 式の中に「× ÷ .」が連続して出現していないかをチェック

ここは単純に現在位置が「× ÷ .」であった場合に次の文字もそうであるかをチェックします。
「× ÷ .」であるかの比較には`IndexOf`メソッドを使用します。

```diff_c_sharp:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
           // 省略
           
        for (int i = 0; i < formula.Length; i++)
        {
            // 末尾が数字、「)」以外の場合はエラーとします。
            if (i == formula.Length - 1)
            {
                if (!(int.TryParse(formula[i].ToString(), out _) || formula[i] == ')'))
                {
                    return ErrorViewModel.ERROR_CODE.E4;
                }
                // そうでない場合、正常終了します。
                else
                {
                    return ErrorViewModel.ERROR_CODE.S1;
                }

            }

+           // 「× ÷ .」が連続して存在していた場合、エラーとします。
+           if ("×÷.".IndexOf(formula[i]) > -1 && "×÷.".IndexOf(formula[i + 1]) > -1)
+           {
+               return ErrorViewModel.ERROR_CODE.E2;
+           }
        }
        
        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```

## 「.」の後に数字以外が出現していないかをチェック
「.」であるかは`char`型同士の比較、数字であるかは`int.TryParse`メソッドを使用します。

```diff_c_sharp:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
         // 省略
         
        for (int i = 0; i < formula.Length; i++)
        {
               // 省略 

            // 「× ÷ .」が連続して存在していた場合、エラーとします。
            if ("×÷.".IndexOf(formula[i]) > -1 && "×÷.".IndexOf(formula[i + 1]) > -1)
            {
                return ErrorViewModel.ERROR_CODE.E2;
            }

+           // 「.」の次の文字が数字以外の場合、エラーとします。
+           if (formula[i] == '.' && !int.TryParse(formula[i + 1].ToString(), out _))
+           {
+               return ErrorViewModel.ERROR_CODE.E3;
+           }
        }

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```

## 連続する「+ -」が３個以上存在していないかをチェック
現在位置から次の次まで文字が存在するかを確認した上で`char`型同士の比較でチェックします。

```diff_c_sharp:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
           // 省略

        for (int i = 0; i < formula.Length; i++)
        {
               // 省略
        
            // 「.」の次の文字が数字以外の場合、エラーとします。
            if (formula[i] == '.' && !int.TryParse(formula[i + 1].ToString(), out _))
            {
                return ErrorViewModel.ERROR_CODE.E3;
            }

+           // 連続する「+ -」が３個以上存在した場合、エラーとします。
+           if (i + 2 < formula.Length - 1
+               && "+-".IndexOf(formula[i]) > -1
+               && "+-".IndexOf(formula[i + 1]) > -1
+               && "+-".IndexOf(formula[i + 2]) > -1)
+           {
+               return ErrorViewModel.ERROR_CODE.E6;
+           }
        }

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```

## 数字、「)」の次に「(」が出現していないかチェック

数値のチェックは`int.TryParse`メソッドを使用し、「)(」の出現チェックは`char`型同士での比較をします。

```diff_c_sharp:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
           // 省略
       
        for (int i = 0; i < formula.Length; i++)
        {
              // 省略
        
            // 連続する「+ -」が３個以上存在した場合、エラーとします。
            if (i + 2 < formula.Length - 1
                && "+-".IndexOf(formula[i]) > -1
                && "+-".IndexOf(formula[i + 1]) > -1
                && "+-".IndexOf(formula[i + 2]) > -1)
            {
                return ErrorViewModel.ERROR_CODE.E6;
            }

+           // 数字、「)」の次の文字が「(」であった場合、エラーとします。
+           if ((int.TryParse(formula[i].ToString(), out _) || formula[i] == ')')
+               && formula[i + 1] == '(')
+           {
+               return ErrorViewModel.ERROR_CODE.E8;
+           }
        }

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }

```

## 「(」の次の文字に「× ÷ )」が出現していないかチェック
「(」は`char`型同士の比較を行い、「× ÷ )」は`IndexOf`メソッドを使用して比較します。

```diff_c_sharp:MainWindow.xaml.cs
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
           // 省略

        for (int i = 0; i < formula.Length; i++)
        {
               // 省略
        
            // 数字、「)」の次の文字が「(」であった場合、エラーとします。
            if ((int.TryParse(formula[i].ToString(), out _) || formula[i] == ')')
                && formula[i + 1] == '(')
            {
                return ErrorViewModel.ERROR_CODE.E8;
            }

+           // 「(」の次の文字が「× ÷ )」であった場合、エラーとします。
+           if (formula[i] == '(' && "×÷)".IndexOf(formula[i + 1]) > -1)
+           {
+               return ErrorViewModel.ERROR_CODE.E9;
+           }
+       }

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```

## 取り合えず全部実装できた
今認識しているパターンについては実装できました。
今度進めていく内に不都合がでてきたら都度この記事を修正していきます。

最後に実装したコード全文を載せて終わりとさせていただきます。
<details><summary>コード全文</summary>

```cs:MainWindow.xaml.cs
    /// <summary>
    /// 入力データが式として成り立つかを検証します。
    /// 式として成り立たない場合、対応するエラーコードを返します。
    /// </summary>
    /// <returns></returns>
    private ErrorViewModel.ERROR_CODE ValidFormula()
    {
        // 現在の入力データを取得します。
        string formula = InputViewModel.Formula;
        if (formula.Length == 0)
        {
            return ErrorViewModel.ERROR_CODE.S1;
        }

        // 文字数が１文字で、かつその文字が数字以外の場合はエラーとします。
        if (formula.Length == 1 && !(int.TryParse(formula[0].ToString(), out _)))
        {
            return ErrorViewModel.ERROR_CODE.E10;
        }

        // 入力データのカッコが対になっていなかった場合はエラーとします。
        if (formula.Count(c => c == '(') != formula.Count(c => c == ')'))
        {
            return ErrorViewModel.ERROR_CODE.E7;
        }

        // 式の先頭が「× ÷ . )」の場合はエラーとします。
        if ("×÷.)".IndexOf(formula[0]) > -1)
        {
            return ErrorViewModel.ERROR_CODE.E1;
        }

        // 式の先頭が「+ -」で、次の文字が数字、「(」以外の場合エラーとします。
        if ("+-".IndexOf(formula[0]) > -1
            && !(int.TryParse(formula[1].ToString(), out _) || formula[1] == '('))
        {
            return ErrorViewModel.ERROR_CODE.E5;
        }

        // 式の先頭が「(」で、次の文字が数字「+ - (」以外の場合エラーとします。
        if (formula[0] == '('
            && !(int.TryParse(formula[1].ToString(), out _) || "+-(".IndexOf(formula[1]) > -1))
        {
            return ErrorViewModel.ERROR_CODE.E11;
        }

        for (int i = 0; i < formula.Length; i++)
        {
            // 末尾が数字、「)」以外の場合はエラーとします。
            if (i == formula.Length - 1)
            {
                if (!(int.TryParse(formula[i].ToString(), out _) || formula[i] == ')'))
                {
                    return ErrorViewModel.ERROR_CODE.E4;
                }
                // そうでない場合、正常終了します。
                else
                {
                    return ErrorViewModel.ERROR_CODE.S1;
                }
            }

            // 「× ÷ .」が連続して存在していた場合、エラーとします。
            if ("×÷.".IndexOf(formula[i]) > -1 && "×÷.".IndexOf(formula[i + 1]) > -1)
            {
                return ErrorViewModel.ERROR_CODE.E2;
            }

            // 「.」の次の文字が数字以外の場合、エラーとします。
            if (formula[i] == '.' && !int.TryParse(formula[i + 1].ToString(), out _))
            {
                return ErrorViewModel.ERROR_CODE.E3;
            }

            // 連続する「+ -」が３個以上存在した場合、エラーとします。
            if (i + 2 < formula.Length - 1
                && "+-".IndexOf(formula[i]) > -1
                && "+-".IndexOf(formula[i + 1]) > -1
                && "+-".IndexOf(formula[i + 2]) > -1)
            {
                return ErrorViewModel.ERROR_CODE.E6;
            }

            // 数字、「)」の次の文字が「(」であった場合、エラーとします。
            if ((int.TryParse(formula[i].ToString(), out _) || formula[i] == ')')
                && formula[i + 1] == '(')
            {
                return ErrorViewModel.ERROR_CODE.E8;
            }

            // 「(」の次の文字が「× ÷ )」であった場合、エラーとします。
            if (formula[i] == '(' && "×÷)".IndexOf(formula[i + 1]) > -1)
            {
                return ErrorViewModel.ERROR_CODE.E9;
            }
        }

        // エラーがなかった場合、正常として返します。
        return ErrorViewModel.ERROR_CODE.S1;
    }
```
</details>

## おわりに
今回の実装はそこまで行き詰まることはありませんでした。
次回はこの式をもって解析する処理を実装します。

## 次の記事

https://qiita.com/nogino_sanebou/items/87a6f73967b80b215b46