C#に入門してみた（Mini Ruccolaコンパイラを書くための下調べ）

[C#で簡単な自作言語のコンパイラを書いた](https://qiita.com/sonota88/items/c0a5bf76d7eb3aa4c507) を書くために下調べしたことのメモです。

だいたいこのくらい分かれば Mini Ruccola コンパイラが書ける。


# インストール


Ubuntu Linux 22.04（実際は Docker コンテナ内）で作業します。

```
apt-get install -y --no-install-recommends mono-devel
```

apt で入れるのが一番簡単そうだったので今回はこれで。

以下のように表示されるのでバージョンはたぶん 4.5 です。

```
  $ mcs -help | grep VERSION
   -sdk:VERSION         Specifies SDK version of referenced assemblies
                        VERSION can be one of: 2, 4, 4.5 (default) or a custom value
```


# エディタ

Emacs




# コンパイル

```csharp
// sample_010.cs
using System;

public class Sample {
    static public void Main() {
        Console.WriteLine(42);
    }
}
```


mcs コマンドでコンパイル、mono コマンドで実行。

```sh
  $ mcs sample_010.cs
  #=> sample_010.exe ができる

  $ mono sample_010.exe
42
```

デバッグオプションを付けないとエラー発生時のスタックトレースに行番号が出ない。

```
mcs -debug
mono --debug
```





# ファイル分割

Mini Ruccola コンパイラの実装に必須ではないけど現実的にはできないと困るので。

```
- ./
  - sample_020.cs
  - sample_020_sub.cs
```

```csharp
// sample_020.cs
using System;

public class Sample {
    static public void Main() {
        Console.WriteLine(11);
        Lib.DoSomething(); // 別のファイルに書いた処理を利用する
    }
}
```

```csharp
// sample_020_sub.cs
using System;

public class Lib {
    static public void DoSomething() {
        Console.WriteLine(22);
    }
}
```

```sh
  $ mcs sample_020.cs sample_020_sub.cs
  #=> sample_020.exe ができる

  $ mono sample_020.exe
11
22
```


出力ファイル名は `-out` で指定することも可能。




# 標準出力・標準エラー出力に print

標準エラー出力の利用は Mini Ruccola コンパイラの実装に必須ではないけど使えた方が便利。

```java
// sample_030.cs
using System;

public class Sample {
    static public void Main() {
        Console.Write("out 1\n");
        Console.WriteLine("out 2");
        Console.WriteLine(123);

        // 標準エラー出力に出力
        Console.Error.Write("err 1\n");
    }
}
```

コンパイルして実行まで行う  `mrun.sh`  を用意した。
<!-- `docker run`  に  `-t`  オプションを付けないこと。 -->

```
  $ ./mrun.sh sample_030.cs | cat -n
     1  out 1
     2  out 2
     3  123
err 1
```




# 標準入力から1バイトずつ読む

1バイトずつじゃなくてまとめて読むでもいいんだけど、プリミティブな機能としてはこれなのでまずはこれで。

```csharp
// sample_050.cs
using System;
using System.IO;

public class Sample {
    static public void Main() {
        using (Stream inStream = Console.OpenStandardInput()) {
            byte[] bs = new byte[1];

            while (true) {
                int n = inStream.Read(bs, 0, 1);
                if (n == 0) {
                    break;
                }
                Console.WriteLine(bs[0]);
            }
        }
    }
}
```

```sh
  $ echo "あいAB" | ./mrun_stdin.sh sample_050.cs
227
129
130
227
129
132
65
66
10
```


# バイト配列を文字列に変換

これができれば上のと組み合わせて標準入力から全部読んで string を得られる（もっといい方法があると思いますがまずはこれで）。


```java
// sample_070.cs
using System;

public class Sample {
    static public void Main() {
        byte[] bytes = {
            227, 129, 130, // あ
            227, 129, 132, // い
            65, // A
            66, // B
            10, // LF
        };

        // bytes[] => string
        string str = System.Text.Encoding.UTF8.GetString(bytes);

        Console.Write(str);
    }
}
```

```
  $ ./mrun.sh sample_070.cs
あいAB
```

# 標準入力からUTF-8として全部読む

（2023-07-15 追記）

Stream, StreamReader を使ったもの。

```csharp
// sample_070.cs
using System;
using System.IO;

public class Sample {
    static public void Main() {
        string str = ReadStdinAll();
        Console.Write(str);
    }

    static string ReadStdinAll() {
        using (Stream stream = Console.OpenStandardInput()) {
            using (StreamReader sr = new StreamReader(stream, System.Text.Encoding.UTF8)) {
                return sr.ReadToEnd();
            }
        }
    }
}
```


```
  $ echo "あいAB" | ./mrun_stdin.sh sample_071.cs 
あいAB
```



# 文字列処理

<!-- 090 -->

- 文字列同士の一致
  - [文字列を比較する方法 - C# ガイド | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/csharp/how-to/compare-strings)
  - 今回は `==` で十分そう
- substring
  - [String.Substring メソッド (System) | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/api/system.string.substring?view=net-6.0)
- int → string
  - とりあえず `$"{n}"` を使うのが簡単そう
- string → int
  - [Int32.Parse メソッド (System) | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/api/system.int32.parse?view=net-6.0)
- 置換
  - [String.Replace メソッド (System) | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/api/system.string.replace?view=net-7.0)




# コマンドライン引数

<!-- Main() とコマンド ライン引数 | Microsoft Learn -->
https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/program-structure/main-command-line


# List

```csharp
using System;
using System.Collections.Generic;

public class Sample {
    static public void Main() {
        List<int> xs = new List<int>();
        xs.Add(11);
        xs.Add(22);
        Console.WriteLine(xs.Count); //=> 2
        Console.WriteLine(xs[1]); //=> 22

        foreach (int n in xs) {
            Console.WriteLine(n);
        }
    }
}
```