C#で簡単な自作言語のコンパイラを書いた を書くために下調べしたことのメモです。
だいたいこのくらい分かれば 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
コンパイル
// sample_010.cs
using System;
public class Sample {
static public void Main() {
Console.WriteLine(42);
}
}
mcs コマンドでコンパイル、mono コマンドで実行。
$ 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
// sample_020.cs
using System;
public class Sample {
static public void Main() {
Console.WriteLine(11);
Lib.DoSomething(); // 別のファイルに書いた処理を利用する
}
}
// sample_020_sub.cs
using System;
public class Lib {
static public void DoSomething() {
Console.WriteLine(22);
}
}
$ mcs sample_020.cs sample_020_sub.cs
#=> sample_020.exe ができる
$ mono sample_020.exe
11
22
出力ファイル名は -out
で指定することも可能。
標準出力・標準エラー出力に print
標準エラー出力の利用は Mini Ruccola コンパイラの実装に必須ではないけど使えた方が便利。
// 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
を用意した。
$ ./mrun.sh sample_030.cs | cat -n
1 out 1
2 out 2
3 123
err 1
標準入力から1バイトずつ読む
1バイトずつじゃなくてまとめて読むでもいいんだけど、プリミティブな機能としてはこれなのでまずはこれで。
// 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]);
}
}
}
}
$ echo "あいAB" | ./mrun_stdin.sh sample_050.cs
227
129
130
227
129
132
65
66
10
バイト配列を文字列に変換
これができれば上のと組み合わせて標準入力から全部読んで string を得られる(もっといい方法があると思いますがまずはこれで)。
// 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 を使ったもの。
// 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
文字列処理
- 文字列同士の一致
- 文字列を比較する方法 - C# ガイド | Microsoft Learn
- 今回は
==
で十分そう
- substring
- int → string
- とりあえず
$"{n}"
を使うのが簡単そう
- とりあえず
- string → int
- 置換
コマンドライン引数
List
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);
}
}
}