soukouki’s diary

誰かの役に立つ記事をかけたらいいなあ

セキュリティキャンプ2024 Cコンパイラゼミ 応募課題晒し

セキュリティキャンプ2024のCコンパイラゼミについて、選考に通過したので応募課題を公開したいと思います。

セキュリティキャンプに応募するのは今回が2回目です。前回はエントリーだけして、応募課題を前にして諦めてしまった記憶があります。今年の春になり、去年参加した友人の勧めで今年も応募しました。今は浪人・留年のない学部4年生なので、今年が応募できる最後のチャンスということになります。今年受かって本当に良かったです。

応募したのは開発コースの「Rust プログラム検証ゼミ」と「Cコンパイラゼミ」です。一応第3希望として他のゼミも応募したのですが、第2希望までの応募課題で力尽きてしまったので、この2つのコースに応募することになりました。

結果として、Rustプログラム検証ゼミは通過できず、Cコンパイラゼミに参加する運びとなりました。個人的には、Rustプログラム検証ゼミの方が今一番扱っているCoq言語との相性も良かったので少し残念です。ともかく、なんとか選考を通過することが出来たので、楽しんできたいと思います。

応募課題はRustプログラム検証ゼミは6000字、Cコンパイラゼミは13000字でした(コード込み)。今となっては、第1希望の方はもっと沢山書いた方が良かったのかなと思います。

今年の春休みに、Rui Ueyamaさんの「低レイヤを知りたい人のためのCコンパイラ作成入門」を読み、本文中に書かれているステップ28を超え、プリプロセッサの直前あたりまでCコンパイラの作成を進めました。この応募課題には、そこで得られた経験を元に書いているものが多いです。

前置きはこのくらいにして、以下が応募課題となります。

問 1: コンパイラを一つ選び、そのコンパイラがどのような過程でソースコードから実行バイナリを生成しているかを、現在知っている範囲で説明し、実際にコンパイラがその過程を経ているということを自分なりに検証してみてください。どのように検証したか、そして、その検証結果から何が言えるかを教えて下さい。どの言語をコンパイルするコンパイラかは、好きなものでかまいません。

gccをもとに説明します。gccコンパイルフェイズは以下になってると考えます。

  1. 字句解析
  2. プリプロセス
  3. 構文解析
  4. 意味解析
  5. アセンブリの生成
  6. オブジェクトファイルの生成
  7. リンク

字句解析とプリプロセスの順番について

gcc-Eオプションを使うことで、プリプロセス後のコードを確認できます。ここで、字句解析ではエラーになるがプリプロセスは許容されるなコードを書くことで、どちらが先に行われるかを確かめられます。以下がそのコードとエラー内容です。

#define CONSTANT_VALUE 42
int main() {
  return CONSTANT_VALUE;
}
/*
$ gcc -E test1.c
# 0 "test1.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "test1.c"

int main() {
  return 42;
test1.c:5:1: error: unterminated comment
    5 | /*
      | ^
}

このコードでは/* ... */形式のコメントの前半分を使い、コメントを終了しないようにしました。これをプリプロセスまで行うコンパイルオプション-Eとともにgccを実行すると、終了していないコメントというエラーが出ました。このエラーは字句解析で発生するエラーのため、字句解析はプリプロセスよりも前に行われていることがわかります。

(追記)

出力をよく見ると、returnの部分のCONSTANCE_VALUEが42に置き換わっているのがわかります。なので、このサンプルコードではどちらが先に行われているかを確認することはできません。代わりに、次のコードと、それを-Eオプションを使って実行した結果を見てみます。

/*
#define CONSTANT_VALUE 42
*/
int main() {
  return CONSTANT_VALUE;
}
$ gcc -E test11.c
# 0 "test11.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "test11.c"

int main() {
  return CONSTANT_VALUE;
}

このコードでは、#define CONSTANT_VALUE 42プリプロセス部分をコメントアウトしています。もしも字句解析より前にプリプロセスが動くなら、このコメントの処理よりも前にプリプロセスが働き、CONSTANT_VALUEは42に置き換えられるはずです。しかし実際はそうではなく、CONSTANT_VALUEは置き換えられずそのままになっています。このことから、プリプロセスより前に字句解析が行われ、コメントの除去が行われていることがわかります。

プリプロセスと構文解析の順番について

これについても-Eオプションを使い、プリプロセスでは許容されるが構文解析ではエラーになるコードを書くことで、どちらが先に行われるかを確かめます。以下がそのコードとエラー内容です。

#define CONSTANT_VALUE 42

int main() {
  return CONSTANT_VALUE + ;
}
$ gcc -E test2.c
# 0 "test2.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "test2.c"


int main() {
  return 42 + ;
}

このコードにはreturn <定数> + ;という誤った構文のコードが含まれています。これをプリプロセスまでを行うコンパイルオプション-Eとともにgccを実行すると、プリプロセスによる式の置き換えが行われます。そして構文の誤りによるエラーは無く、プリプロセスは構文解析よりも前に行われていることがわかります。

構文解析と意味解析の順番について

一般的には構文解析が先、意味解析が後になります。gccではエラーリカバリがよく行われていて、きっぱりと順番の説明がつくコードを作れませんでした。代わりに構文解析と意味解析の役割分担が現れるコードとそのコンパイルエラーについて説明します。

int main() {
  int void a;
  register auto int b;
}
$ gcc test3.c
test3.c: In function ‘main’:
test3.c:2:7: error: two or more data types in declaration specifiers
    2 |   int void a;
      |       ^~~~
test3.c:3:3: error: multiple storage classes in declaration specifiers
    3 |   register auto int b;
      |   ^~~~~~~~

C言語構文解析レベルにおいては、宣言指定子列(JIS X3010:2003 「プログラム言語C」での用語に合わせます。以下同様)には、記憶域クラス指定子、型指定子、型修飾子、関数指定子を好きな順番でいくつも並べられます。しかし、int void a;register auto int b;のように、矛盾した宣言指定子が来ることがあります。これについて、構文解析レベルでは好きな順番で受け入れ、意味解析レベルでそれらが矛盾していないかをチェックする、という2段階の解析が行われます。このソースコードでは矛盾した宣言指定子を指定したため、意味解析でエラーが出ています。

アセンブリの生成

gccでは、-Sオプションを使うことでアセンブリを生成できます。意味解析が先で、アセンブリの生成はその後になります。

// test5.c
int func();
int main() {
  return func();
}

gcc -S test5.cとすると、test5.sというアセンブリファイルが生成されます。このファイルを見ると、func関数の呼び出しを行うcall命令が含まれていることがわかります。

 .file "test5.c"
    .text
    .globl    main
    .type main, @function
main:
.LFB0:
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp
    .cfi_def_cfa_register 6
    movl $0, %eax
    call func@PLT // ここでfunc関数を呼び出しています。
    popq %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size main, .-main
    .ident    "GCC: (GNU) 13.2.1 20230801"
    .section  .note.GNU-stack,"",@progbits

このアセンブリにはfunc関数の定義は含まれておらず、リンクがまだ行われていないこともわかります。

オブジェクトファイルの生成

gccでは、-cオプションを使うことでオブジェクトファイルを生成できます。アセンブリの生成が先で、オブジェクトファイルの生成はその後になります。

gcc -c test5.sとすると、test5.oというオブジェクトファイルが生成されます。このオブジェクトファイルは、アセンブリファイルをリンクすることで実行可能ファイルが生成されます。

生成されたtest5.oと、比較用に次のコードを入力したtest6.oを見てみます。無理やりテキストファイルとして開いてみてみると、test5.oにはmainfuncという文字列が、test6.oにはfuncという文字列が含まれていることがわかります。ここから、オブジェクトファイルにはリンクするために必要な関数名などが含まれていることが推察できます。

// test6.c
int func() {
  return 42;
}

リンク

gcc test5.o test6.oとすると、実行可能ファイルa.outが生成されます。

試しにgcc test5.ogcc test6.oとすると、次のようなエラーが出ます。gcc test5.oではfunc関数が見つからないというエラーが、gcc test6.oではmain関数が見つからないというエラーが出ます。これらから、リンクはプログラムを合わせることで、異なるファイルにある関数呼び出しやグローバル変数などを統合する処理だということがわかります。

$ gcc test5.o
/usr/bin/ld: test5.o: in function `main':
test5.c:(.text+0xa): undefined reference to `func'
collect2: error: ld returned 1 exit status

$ gcc test6.o
/usr/bin/ld: /usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../lib/Scrt1.o: in function `_start':
(.text+0x1b): undefined reference to `main'
collect2: error: ld returned 1 exit status

gccコンパイルフェイズは、上記のような順番で行われると考えられます。

問 2: C言語コンパイラを書く際に、最も難しいポイントはどこだと思いますか?考えたことや、これまでのプログラミング経験をもとに、具体的に教えてください。

普通の言語では字句解析->構文解析->意味解析の流れで独立して処理できますが、C言語では例外がいくつかあり、その点が難しいと思います。

プリプロセッシングと字句解析・構文解析の関係

問1ではプリプロセッシングは字句解析と構文解析の間に行われると書きましたが、単純に1方向にデータを流すだけでは上手く行かない例が考えられます。

// test7.c
#include /* */ <stdio.h>
int main() {
  printf("Hello, World!\n");
}
// test8.c
#include
<stdio.h>
int main() {
  printf("Hello, World!\n");
}
test8.c:1:9: error: #include expects "FILENAME" or <FILENAME>
    1 | #include
      |         ^
test8.c:2:1: error: expected identifier or ‘(’ before ‘<’ token
    2 | <stdio.h>
      | ^

test7.c#include<stdio.h>の間にコメントがありますが、コンパイル可能です。しかし、test8.cは同じように#include<stdio.h>の間に改行がありますが、コンパイルエラーとなります。C言語の他の部分ではコメントや改行、空白はすべて字句解析で処理され、無視されます。しかし、前処理においてのみ、改行が意味のあるトークンとして扱われます。

これの回避策として思いつくのは、改行をトークンとする字句解析と、改行を読み飛ばす処理の2段階に分けて行うことです。1段階目の字句解析では、コメントや空白は除去しますが、改行はトークンとして残します。その後にプリプロセッシングを行います。更にその後に改行を読み飛ばす処理を行い、構文解析に移ります。このようにすることで、プリプロセッシングや構文解析をシンプルにしたまま、C言語の特殊な仕様に対応できると考えます。

構文解析と意味解析の関係

問1では構文解析の後に意味解析が行われるとかきましたが、これも単純に1方向にデータを流すだけでは上手く行かない例が考えられます。

typedef int A;
int f1() {
  int A;
  A++;
  A a; // Aは変数なので、aの前にセミコロンが必要だという構文解析のエラー
  a++;
}
int f2() {
  A a;
  a++;
  int A;
  A++;
}
$ gcc test4.c
test4.c: In function ‘f1’:
test4.c:5:4: error: expected ‘;’ before ‘a’
    5 |   A a; // Aは変数なので、aの前にセミコロンが必要だという構文解析のエラー
      |    ^~
      |    ;
test4.c:6:3: error: ‘a’ undeclared (first use in this function)
    6 |   a++;
      |   ^
test4.c:6:3: note: each undeclared identifier is reported only once for each function it appears in

関数f1ではint Aと変数を定義したことで、A aは変数Aと解釈され、構文解析コンパイルエラーが発生します。しかし、関数f2ではA aはA型の変数aを定義すると解釈され、コンパイルは成功します。typedefを使ったコードでは、このように変数を定義したかどうかで構文が変わる自体が発生します。

これの回避策として思いつくのは、構文解析中において、変数を定義した箇所で変数名を記録するようにし、以後その変数名と同じ文字列を持った識別子が来たときに処理を変えるというものです。教科書通りの構文解析からは外れてしまいますが、この方法はコードに落とし込むとかなり簡便になり、実装方法として有効だと考えます。

このような仕様について、この課題を解く中で調べたところ、Ruby言語にも同じようなポイントがあることがわかりました。以下はirbでのRubyプログラムの実行結果です。a -bという形の構文では、aがローカル変数であればa - bという形の構文として、aが関数であればa(-b)という形の構文として解釈されます。

irb(main):001:0> a = 1
=> 1
irb(main):002:0> b = 2
=> 2
irb(main):003:0> a -b
=> -1
irb(main):004:0> def f(x) return x; end
=> :f
irb(main):005:0> f -b
=> -2

この構文の実装はおそらく、構文解析中にローカル変数を保存しておき、その変数名と同じ識別子が来たときに構文解析の処理を変えることで実現されていると考えられます。その根拠として、Rubyのローカル変数を動的に変更するメソッドの制約があります。Bindingクラスのlocal_variable_setというメソッドを見てみます( https://docs.ruby-lang.org/ja/latest/method/Binding/i/local_variable_set.html )。このメソッドは動的にローカル変数を定義しますが、これを実行した後にそのローカル変数を使おうとすると、NameErrorが出て使用できません。この仕様は、ローカル変数か関数かを構文解析時に決定していることから来ているものと考えられます。構文解析時に決定しているため、動的なローカル変数の定義に対応できず、NameErrorを出すようになっていると考えられます。

これらの教科書通りの言語処理系では上手く行かない箇所がC言語の難しいポイントだと考えます。

問 3: C言語のどこが不便だと思いますか?どのような機能があったらそれを便利にできますか?その機能はどうやったら実装できると思いますか?一つ選んで説明してください。

リストやハッシュマップなどを利用する際に、voidポインターを扱う点が不便だと思います。voidポインターを扱うことで型チェックが効かず、動的型付け言語以下の型付けになっており、バグの原因になりやすいです。改善策として、2つ思いつきました。

1つ目は、C++で採用されているように、テンプレートを導入することです。テンプレートを使うことで、これまでvoidポインターを扱うしかなかった部分を型チェックできるようになります。しかし、この方法はこの不便な点を解決するためには少しやりすぎとも考えます。テンプレートは型によって実装を分ける必要があり、影響範囲が大きくなってしまいます。テンプレートを導入した場合、ある関数にテンプレートを使うとなるとその関数をすべて再実装するような大掛かりなことになります。

2つ目は、テンプレートのような構文を使いますが、関数の戻り値の型を補足するに留めるものです。テンプレートのある関数を定義する際にはvoidポインターとして扱い、使用する際に型チェックを走らせます。これにより、既存のコードをそのままに、関数プロトタイプのみ書き換えることで型チェックを行います。アイデアとしてコードを下にかきました。

struct list<T>;
typedef struct list<T> list<T>;
list<T>* list_new<T>();
T*       list_pop<T>(list<T>*);
void     list_push<T>(list<T>*, T*);

int main() {
  list<double>* l = list_new()
  double* a = 12.3, b = 45.6;
  list_push(l, &a);
  list_push(l, &b);
  double *d = list_pop(l) // 成功
  int    *i = list_pop(l) // コンパイルエラーを出す
}

テンプレート型を持っておく必要のあるlist構造体と、そのテンプレート型をやり取りする必要のあるリストの各関数には、<T>というテンプレート(に似たもの)を指定しています。各関数でT型を扱う際には、全てポインターで扱う必要があります。もともとはvoidポインターだった部分を、T型のポインターに変更しているためです。また、ポインタとして扱うことで、テンプレートのように関数を型違いで複数定義する必要がなくなります。この方法であれば、既存のコードをそのままに、型チェックを行うことができます。

問 4: 今までで一番苦労したデバッグの話を教えてください。それがどのようなバグで、どのようなアプローチを取った結果解決したのか、そして結局なにがバグの原因だったのかを書いてください。

Rui UeyamaさんのCコンパイラ作成入門のテキストを読みながらCコンパイラを作っている際に遭遇したバグです。最初発見した際は、(ローカル)変数定義と条件分岐を組み合わせたやや複雑なコードをコンパイルすると、思っていたとおりの出力が得られないというものでした。最初の対処法として、条件分岐を減らし、対象のプログラムを簡単にしていきました。すると、変数2つと条件分岐が残りました。この段階では、①push/pop周りのアセンブリの生成で失敗しているか、②ローカル変数のスタック周りのアセンブリの生成で失敗しているか、③条件分岐あたりの構文解析で失敗しているか、この3つを中心にして疑っていました。

次に、生成されたアセンブリを読み込み、実際にどのような経緯で意図しない動作が発生しているのかを突き止めようとしました。この作成中のコンパイラでは、生成したアセンブリの中に過剰にpush/popが含まれています。なので、生成されたアセンブリの中から余計なpush/popを一つづつ手で取り除く作業を行いました。すると、1回目は取り除くのに失敗して動作が変わり、2回目で見た感じ問題のないアセンブリが生成されていることがわかりました。この時点で、もうコンピュータが壊れたのではないかと思い、一旦落ち着くために散歩に出かけました。

散歩から帰ってきて、バグが起きてるコードと、すでに成功しているテストを見比べてみました。すると、バグが起きているコードはローカル変数を2つ定義していることに加え、初めてそれらの変数に代入していることに気づきました。バグの起きているコードはローカル変数aとbを定義し、aに値を代入した後にbに値を代入した後、aとbの値を使用していました。ここで、さらにプログラムを簡単にしていくと、bに値を代入した後にaの値が0になっていることに気づきました。このことから、条件分岐は関係なく、ローカル変数の代入周りでバグが発生しているのではないかという結論になりました。

ここで当時のローカル変数の仕様を整理すると、ローカル変数はすべてint型で、スタックに1変数あたり4バイトの空間を確保するようにしていました。そして、値に代入する命令はmov [rax] rdiとしていました。Cコンパイラ作成入門のテキストを読み返したところ、この命令は8バイトの空間に値を代入する命令であることがわかりました。

つまり、変数aに4バイト、変数bに4バイトの空間を確保していたのに対し、変数bに対して8バイトの空間の代入命令を使っていました。そのため、変数bに値を代入する際に変数aの値が破壊され、変数aの値が0になっていたのです。そして、変数に代入する命令をmov [rax] rdiからmov [rax] ediに変更し、バグを解決しました。

問 5: 既存のプログラミング言語マークアップ言語なども可)の仕様のうち、理不尽だと思うものを一つ選んでください。どうしてそのような仕様になってしまったのでしょうか?その理不尽さが発生しないようにするにはどのようにしておけばよかったのでしょうか、それとも本質的に解決不能なのでしょうか?歴史的経緯も含め、自分なりに調べて教えてください。

#include <stdio.h>
int main() {
  int a = 1;
  printf("%d %d %d\n", a++, a++, a++);
}

上記のコードをコンパイルした際に、gccでは3 2 1と表示されます。これは、C言語の関数呼び出しの仕様について、関数本体と関数の引数について、どの順番で処理するのかが定められていないことによります。この仕様は直感的ではなく、また他の言語でもこのような仕様は採用されていません。プログラムは前から実行していく流れが基本であり、関数呼び出しの仕様としても前から順番に評価されるのが自然だと考えます。

他の言語の関数呼び出しの副作用の例として、次のRubyのコードを考えます。このコードを実行すると1 2 3と出力されます。実際にRubyの規格書(JISX3017:2013「プログラム言語Ruby」)を読むと次のように書かれていて、関数呼び出しの引数は前から順番に評価されることが規定されています。

def func(a, b, c) puts "#{a} #{b} #{c}" end

a = 0
func((a += 1), (a += 1), (a += 1))

(《添字実引数リスト》について) プログラムテキストに現れる順に,《コマンド》,《演算子式リスト》の《演算子式》又は《連想リスト》を評価し,評価結果の値をLの末尾に追加する。 (JISX3017:2013「プログラム言語Ruby」p. 50)

C言語の規格書(JISX3010:2003「プログラム言語C」)を読むと、仕様として次のように書かれています。この仕様は、関数呼び出しの引数は評価順序が未規定であるとし、このような挙動が許容されていることを示しています。

関数指示子,実引数及び実引数内の部分式の評価順序は未規定とするが,実際に呼出しが行われる前に副作用完了点がある。 (JISX3010:2003「プログラム言語C」p. 52)

なぜこの仕様になっているのかを色々調べてみたのですが、その経緯は見つけられませんでした。ですが、探している際に見つけた仮説として、「関数の呼び出し時にスタックに積むときに有利」というものがありました。実際にSystem V ABI - Intel386 Architecture Processor Supplemenを読んでみると、引数はレジスタに加え、スタックフレームに積んでいることがわかりました。スタックフレームはHigh addressからLow addressの方向へ伸びていきますが、スタックフレームの引数は左側からLow addressからHigh addressの方向へ積まれています。これらを考えると、たしかに「関数の呼び出し時にスタックに積むときに有利」という仮説はある程度考えられると思います。

(追記)

この「関数の呼び出し時にスタックに積むときに有利」というのは、この課題について調べているときにTwitterで見つけた意見です。ABIを読んで関数呼び出しのスタックの作り方をみると、確かにそういう呼び出し順にすることで実装がやや簡単になりそうなのは分かったのですが、実際に何が原因でこうなっているのかまでは探し切れませんでした。ただ、調べてる中でSystem V ABIが読めるサイトを知り、コンパイラ作成に重要な説明がたくさん書いてあることに気づけたのは良い収穫でした。

問 6: 既存のプログラミング言語マークアップ言語なども可)の仕様のうち、「世間一般で流布している説明には、厳密にいうと誤りが含まれている」というものを一つ選んで、それについてしっかりと丁寧に説明してください。ただし、「誤った説明が世間に流布している」という主張を支えるためには、互いに無関係な 4 種類以上の文献や教材においてその誤った説明がされていることを示してください(書籍・Web サイト・動画など幅広く OK としますが、講師が検証しやすいように、public なインターネットからアクセス可能なものの方がありがたいです)。そして、「厳密にいうと誤っている」という主張を支えるためには、「流布している説明に基づいたときに期待される挙動」と「実際に動かしてみて得られた挙動」の差を示すだけではなく、仕様を規定するドキュメントを読み込んだり、実装のソースコードを確認するなどして、「世間一般で流布している説明はこうであるが、この挙動を規定する仕様書や実装はこうであり、このような点で異なる」ということを丁寧に論証してください。

一方の「インタプリタ」はいわば「同時通訳者」、話し言葉を1文ずつリアルタイムに翻訳していくイメージです。プログラムを実行しながら、下図の赤枠部分のようにソースコードを1行ずつ機械語に変換します。

https://www.sejuku.net/blog/124720コンパイラとは?基礎知識やインタプリタとの違いをわかりやすく解説」侍エンジニア編集部

インタプリタ言語は、コードを実行する際に1行ずつ機械語に翻訳していく言語です。

https://qiita.com/tomokichi_ruby/items/73b0e7924a9f83fe45c6インタプリタ言語とコンパイラ言語」@tomokichi_ruby

インタプリター型言語のソースコードを、1行ずつオブジェクトコードに変換しながら実行するソフトウェア。 出典 ASCII.jpデジタル用語辞典

https://kotobank.jp/word/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%97%E3%83%AA%E3%82%BF%E3%83%BC-12389 コトバンクインタプリタ

インタプリタinterpreter)は、コンピュータでプログラムを処理する方法の一つです。プログラムの実行時にソースコードを1行ずつ機械語プログラムに変換するプログラムのことでもあり、コードを読み込みながらその場で処理・実行していきます。

https://www.ntt-west.co.jp/business/glossary/words-00246.html NTT西日本 ICT用語集「インタプリタ

これら4つの記事では、インタプリタの説明として、「ソースコードを1行づつ読み込み、機械語に変換しながら実行する」と説明されています。しかし、これは誤りです。

この説明を否定するためにいくつか仕様書を読んでみたのですが、はっきりと否定している箇所を見つけられませんでした。

(追記)

インタプリタを作ったことがあれば明らかに誤りだとわかるこれらの説明ですが、明確に誤りだとわかる仕様書は見つかりませんでした。この誤りを選んだ時にはRubyの仕様書を読めば書いてあるだろ!と意気込んでいたのですが、一通り読んでみた所インタプリタの仕組みについては全く書いてありませんでした。それも当然で、Rubyの仕様書はRubyの言語の仕様書であって、それをどう実行させるかについては書いてありません。

この問題を残したまま応募締め切りの日になってしまったため、この問題には明確に答えられないままとなりました。アフェリエイトブログの「調べてみましたが、分かりませんでした!」ってやつと同じですね。

(さらに追記)

JavaScript(ECMAScript)の企画書にはこれを否定する内容が書いてあるそうです。

また、実際のインタプリタ言語の実装を例にとって説明する手もあるそうです。ただ、インタプリタの実装を挙げるにしてもコードベースが膨大で、そこからコードを引用して説明するのもなかなか骨が折れそうです。

問 7: どのプログラミング言語を使って C 言語のコンパイラを実装する予定ですか?講師が予習するためにも、具体的に教えてください。

C言語で作成する予定です。セルフホストに挑戦したいです。

(追記)

自分はこの2年間Coq言語を中心に書いているので、Coq言語で書いてみたいと思ったのですが、流石に無理だと判断してC言語と答えました。C言語だとセルフホストという明確な目標が出来るので、その点でも良いのかなと思います。

問 8: これまでのプログラミング歴について好きなだけ語ってください。何か作ったものがあれば、それについても教えてください。また、何か他にアピールしたいことがあれば、それも自由に書いてください。

高校生の頃からプログラミング言語に興味があり、C言語RubySchemeなどを触り、特にRubyについては標準ライブラリのドキュメントを何周もするなどしていました。

大学生になり、プログラミング言語を作る側に回ってみようと思い立ち、「Go言語でつくるインタプリタ」という本を読み、Go言語でyokan( https://github.com/soukouki/yokan )という言語を作ってみました。この言語は、四則演算や数値の大小比較、変数の代入と使用、関数定義と呼び出しをサポートしています。ループは再帰を、条件分岐はif関数(then節とelse節をラムダ式で渡す)を使うという、仕様を減らしてコード側で頑張るようなインタプリタを作成しました。実際にその言語上でfizzbuzzを書き、実行することができました。

学部1年の冬頃、Go言語でつくるインタプリタで得た知識をもとに、インタプリタを作成する勉強会を開催しました。baby-interpreter( https://github.com/soukouki/baby-interpreter )という、機能を意図的に削ったインタプリタを作成し、参加者に足りない機能を追加してもらう形式にしました。また、この勉強会の資料をもとに、技術書典に合同誌の1章として寄稿しました。

学部2年になり、大学の講義でCoq言語に出会いました。Coq言語はカリー・ハワード同型対応を利用した定理証明支援系であり、プログラムを書くことで数学の証明を書くことができ、型チェックをすることでその証明を検証することができます。私はこのCoq言語にドハマりし、Coqを書くことに没頭しました。2年の夏ごろに、クイックソートがソートできることの証明を書きました( https://github.com/soukouki/coq-quick_sort )。その後はCoqの勉強もかねて、松坂先生の「集合・位相入門」という数学書をもとに、Coqに集合論を形式化する取り組みを始めました( https://github.com/soukouki/mathlib のEnsemble以下)。3年になってからは毎日Coqに取り組むようになり、3年の夏にはCoqの標準ライブラリの証明を短くし、コントリビュートをしました( https://github.com/coq/coq/pull/17940 )。

3年の春休みには、今まで避けてきたCコンパイラの自作に挑戦しました。Rui Ueyamaさんの「低レイヤを知りたい人のためのCコンパイラ作成入門」をもとに、C言語でCコンパイラを作成しました( https://github.com/soukouki/10cc )。四則演算やif・while・forといった制御構文、関数定義に関数呼び出し、グローバル変数に文字列リテラル、構造体・列挙型にtypedefなどを実装しました。セルフホストを目標にしていたのですが、プリプロセッサの実装がなかなか思いつかず、途中で放置してしまいました。この課題をやる中で、字句解析・構文解析プリプロセッサの関係がつかめてきたので、この後実装してみようと思っています。また、関数呼び出しで7つ以上の引数を渡したり、可変長引数の関数へもまだ対応していないので、これらについても挑戦していきたいと思っています。

セキュリティ・キャンプでCコンパイラ自作のコースに参加できたら、今度こそセルフホストを達成したいと思っています。前回のCコンパイラ自作でできたコードを書き直すなり、今回また新しくコードを作るなりして、前回の自作コンパイラの状況を超えたいと思っています。

pdfをネガポジ反転するワンライナー

ImageMagickを使ってpdfをネガポジ反転するワンライナー

270ページの文章の例。ページ数に合わせて269の部分を変える必要があります。

for i in {000..269}; do convert -density 300 "origin.pdf[$i]" -alpha remove -negate np${i}.png; done; convert np*.png np.pdf

一度にpdfをネガポジ反転したり、pngに変換するとメモリが足りなくなったので、1ページごとの処理にしています。

-density 300でdpiを上げています。

難点・要改善点

  • 文字埋め込みではなく画像になるのでファイルサイズが増大する
  • 画像の画質が荒い
  • 最後のconvertでpngをまとめる段階でメモリを大量に消費して、場合によっては失敗する

参考文献

PDFのネガポジ反転(白黒反転) Junichiro NIIMI https://jun-systems.info/articles/convert-negaposi-pdf/

ImageMagickでpdfファイルの1ページ目だけをjpegにする matoken https://matoken.org/blog/2021/03/16/make-the-first-page-of-the-pdf-file-jpeg-with-imagemagick/

imagemagicでpdfをjpgにconvertしたらbackgroundが黒くなるとき prex-uchida https://qiita.com/prex-uchida/items/8443c5350e80c25c4efa

また、今回のコードでは出てないですが、一回ハマったのでこちらも置いておきます。

ImageMagick7 の negate(ネガポジ変換)で透明になる件 yoya https://qiita.com/yoya/items/9301c447093354d92185

動作環境

$ neofetch
OS: Arch Linux x86_64
Shell: zsh 5.9

$ convert --version
Version: ImageMagick 7.1.0-57 (Beta) Q16-HDRI x86_64 20701 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenCL OpenMP(4.5)
Delegates (built-in): bzlib cairo djvu fontconfig freetype heic jbig jng jp2 jpeg jxl lcms lqr ltdl lzma openexr pangocairo png raqm raw rsvg tiff webp wmf x xml zip zlib
Compiler: gcc (12.2)

Simutrans-Extendedの機能・datパラメータ紹介(信号・信号扱所編)

soukouki.hatenablog.jp の信号・信号扱所編です。

https://forum.simutrans.com/index.php/topic,14848.0.html を翻訳・追記しました。

このガイドは、Simutrans-Extendedバージョン12.x以降にのみ適用されます。

Extendedで追加された機能の紹介

片方向信号

信号機は片方向信号となり、逆方向の列車の通過を防ぐことはできなくなりましたが、逆方向の列車が通過する際には無視されます。しかし、運転台信号、移動式閉塞、軌道回路閉塞の動作方法を使用すれば、双方向信号を作ることができます。

(訳者補足:片方向の信号の一方通行にする機能はなくなりました。一方通行標識を忘れるとデッドロックの原因になるので、慣れないうちは意識的に一方通行標識を置くようにしましょう。)

方向予約

(訳者注:このセクションは方向予約に関する解説を独立させたものです。翻訳元の文章では、軌道回路方式の説明の一部に入っていました。)

(訳者補足)Simutrans-Extendedではこれまでの線路の予約に加え、ある1方向に限定された閉塞の予約を行えるようになりました。

  • 方向予約は、閉塞予約表示(Pak128.Britain-Exではbキー)で青色で表示されます。
  • 方向予約は次の一方通行標識で終了します。
  • 方向予約のあるタイルは、同じ方向に進もうとする列車によって(通常の(赤)ブロック予約として)予約されることがあります。

信号扱所

信号機は、以前のバージョンのように、ただ設置するだけでは作れません。信号機を作るには、信号扱所が必要です。信号扱所とは現実世界では信号を制御する建物のことで、新しい技術により、より多くの信号の数や遠隔制御が可能な、より効率的な信号システムを実現することができるようになりました。Simutransでは、信号機は線路の近くに置かれるプレイヤーの建物としてコーディングされています。

信号扱所のリファレンスは建物・停留所編を参照してください。(訳者注:建物・停留所編はまだ翻訳していません。英語版は http://forum.simutrans.com/index.php?topic=15222.msg150137;topicseen#msg150137 を閲覧してください。)

また、信号と信号扱所を解説する動画(英語)もあります。https://www.youtube.com/watch?v=2XLi1tNaG28

信号現示

信号は、進行信号と代替経路を示す振り分け信号(進路表示機付き信号)のほかに、最大5つの画像をもつことができるようになりました。 通常の5つの現示は以下の通りです。

  • 停止
  • 警戒
  • 注意
  • 減速
  • 進行

他に追加できる表示として、以下のようなものがあります。

  • 誘導信号機や、許容信号機など、前方に列車が居ても無閉塞運転ができる進行現示(呼び出し)
  • 代替経路(振り分け信号)の進行・注意現示
  • 主経路(振り分け信号)の進行・注意現示
  • などなど・・・

表示の組み合わせ方の詳細は、この下のdatファイルのリファレンスに記述されています。

許容信号機

許容信号機を許容閉塞モードで動作させることができるようになりました。これは、軌道回路方式、双信閉塞方式、車内信号方式のいずれかを組み合わせるものです。線路の容量を増やすために使われる現実的な信号システムです。ロンドンの地下鉄では今でも使われていると思いますし、いずれにせよ、ごく最近まで使われていました。(訳者注:日本では線路容量を増やすために使われているという話は聞きません。信号機が故障した際に行われますが、衝突事故が発生したため禁止している事業者も存在します。)

許容信号機による閉塞方式では、停止信号で停止させられた列車は、前方の列車を見て停止できるほどゆっくり走れば、信号が危険であっても進むことができます。これは、信号が一方向の枝分かれのない区間を制御している場合にのみ可能です。

現実には、列車をこのように進行させるための「呼び出し」(call on)を示す補助信号アームが存在する場合と存在しない場合があります。has_call_on=1 は、そのようなアスペクトのためのグラフィックの存在を示します。 呼びかけの表示がない許容信号機もあります。この場合、列車が通過するときに警告現示を表示しつづけます。Pak128.Britain-Exには、このような方法で動作する地下の許容信号機があります。

Simutrans-Extendedでは、次の信号までの経路に分岐がない場合のみ、許容信号機が機能します。交差点がある場合、許容信号機は無効になり、通常の停止信号として機能します。Pak128.Britain-Exでは、この呼び出し信号の視認速度が低くなっているので、そのように機能しない場所に設置するのは実に不利なのです。

次の信号までの線路に分岐がないことを条件に、列車が信号で立ち止まると、呼び出しを行い、目視による走行方式で進行できるようになります。これにより、複数の列車が1つの区間に進入することが可能となり、例えば、混雑した貨物ループや低速高密度都市旅客線などで有効です。

振り分け信号は、先に分岐があることが想定されるため、許容信号機にはできません。ただし、将来的には、許容信号機として定義されていない信号機にも、軽機関車の動きや列車の分割・併合に対応するための呼び出し機能を持たせることが計画されています。

振り分け信号

振り分け信号がさらに進化しました。駅の別のプラットフォームへ列車を送る機能の他に、駅の使用中の線路から反対側の端まで、列車を迂回させるための空いている経路を探すこともできるようになりました。この機能は、駅の出口に設置された「振り分け終端」標識と一緒に使用されます。列車が振り分け信号に近づくと、その列車は目的地までの全行程、または「振り分け終端」標識のどちらか先に遭遇したほうの経路が示されます。

また、この信号は、振り分け信号として動作するか、通常の信号として動作するかで、異なるグラフィックスを持つことができるようになりました。

ステーション信号

working_method=time_interval(時間間隔方式) または working_method=time_interval_with_telegraph(電信時間間隔方式) と、 is_longblocksignal=1 を持つ信号は、駅タイルに置かれると、ステーション信号として動作します。

駅信号は、5分以内(または、simuconf.tab で時間間隔信号のために指定された他の間隔)に、どのプラットフォームからも同じ方向の列車が発車しなかった場合のみ、進行現示になります。 駅信号の定義は双方向性であり、どちらの方向でも動作するため、すべてのプラットフォームで1つの駅信号だけが必要です。

電信時間間隔方式でのステーション信号はさらに、(以前と同様に)方向予約を行うことができない限り、進行現示になることはありません。

原則的には、通常の時間間隔方式の駅信号は、駅より先のタイルが視認距離の分確保さ れるまでは発車されません。

ステーション信号は、小さな駅や単線の駅に最も適しています。大きな駅では、各ホームに一般的な信号機を設置したほうがよいでしょう。

また、ステーション信号は、駅に向かうための信号ではありません。振り分け信号が必要です。

さらに、駅信号の実現には、以下のパラメータを設定する必要があります。

is_longblocksignal=1
has_selective_choose=1
aspects=3

また、以下のようなグラフィックも必要です(詳細はリファレンスのグラフィックの項を見てください)。

  • 停止現示
  • 順方向への進行・注意現示
  • 逆方向への進行・注意現示

信号・運転方式の紹介

無閉塞運転

列車が発車すると、自動的に最初の運転方法となります。運転手は目で前方を確認し、線路が空いていることを確認します。この方法は、例えば都市部の路面電車に最適で、前方の列車に近づいて運転できます。

  • 列車は、視認距離内で停止するよりも速く走ることはできません。
  • この作業方法では、列車が動作するために全く信号が必要ありません。
  • デッドロックが発生しやすいです。
  • 列車が発車するときや、信号のないホームを離れるとき、あるいは working_method=drive_by_sight(無閉塞運転) と書かれた標識の前を通るとき、自動的に無閉塞運転になります。

また、無閉塞運転を解説する動画(英語)もあります。https://youtu.be/gLY-hW377LY

時間間隔方式

(訳者補足) 時間間隔法は、前方列車との間に十分な間隔を開けることで衝突を回避する運転方法です。この方式では、閉塞を使用せず、信号機をその向きで最後に通過した列車からの経過時間で信号現示が変わります。時間間隔法の信号機の間には、複数の編成が入れます。

(訳者補足) 時間間隔法では線路を予約しないため、単線での運行はできません。単線で運行したい場合は、電信時間間隔法を使用します。

  • 信号機はデフォルトで進行現示です。
  • 列車が通過すると、信号機は停止現示に変わります。
  • 列車が通過してから5分後に注意現示に戻ります。
  • 列車が通過してから10分後に信号は進行現示に戻ります。
  • 列車は注意現示の信号を、線路速度の半分または信号の視認速度の半分の、どちらか低い方の速度で通過しなくてはなりません。
  • 遠方信号は中継器として有効で、停止信号の視認距離を延長する役割を果たします。
  • この方式では、列車が前方の列車に追突した場合、両方の列車がしばらく停止した後、無閉塞運転で運転が続けられます。
  • 振り分け信号には振り分け機能がありますが、この方式では終着駅でのデッドロックに注意する必要があります。(訳者注:ホームが満線の場合、停止位置を指定しているホームに侵入し、デッドロックを引き起こします。)

電信時間間隔方式

(このセクションの全体はsou7による補足です。翻訳元の文章では省略されていました)

電信時間間隔法は、時間間隔法の動作に電信による経路の予約を追加したものです。列車が信号機を通過する前に、次の信号までの間に列車を妨げるような予約が存在しないことを確かめます。また、列車が信号機を通過すると、次の信号までの間に方向予約を行います。

  • 列車が通過する前に、経路が他の列車に予約されていないか確認をします。
  • 最後にその方向に列車が通過してから5分間は、その方向への通過はできません(停止現示)。
  • 最後にその方向に列車が通過してから5分後から10分後までは、注意現示での通過になります。
  • 列車が通過してから10分後以降は、進行現示での通過になります。
  • 列車は注意現示の信号を、線路速度の半分または信号の視認速度の半分の、どちらか低い方の速度で通過しなくてはなりません。
  • 遠方信号は中継器として有効で、停止信号の視認距離を延長する役割を果たします。
  • 列車が通過すると、方向予約を行います。
  • 時間間隔法と同じく、振り分け信号にはデッドロックに注意する必要があります。

スタフ閉塞方式

スタフ閉塞とは、線路の全区間を一本の列車しか通れないようにする方式です。スタフ(staff = スタッフ)と言っても、従業員ではなく棒のようなものです。

運転士がスタッフを所持している列車だけが区間に入ることができるという、非常にシンプルなシステムです。したがって、このシステムでは、すべての列車の出入り口は一つでなければならず、そうしないとスタフが間違った場所に置き去りにされてしまうことになります。これは、1つの列車しか走っていない路線全体(非常に基本的な鉄道で、列車の通常の経路ではなく、車庫からの出口に置かれる)か、内部に信号がない単線の行き止まり区間にしか、本当に役に立ちません。

Simutrans-Extendedでは、スタフ棚は1つの区間に1つ以上存在することもできますが、1つとして機能するためには互いにすぐに隣接するタイルに配置する必要があります。

また、列車が戻ってきて再びキャビネットに到達するまで(または現在と同じスケジュールの時点に到達するまで、どちらか早いほう)、列車の全ルートを予約することによって機能します。

タブレット閉塞方式

タブレット閉塞方式は、スタフ閉塞方式よりはるかに高度なシステムで、単線線路の制御にも使用できます。

実際の鉄道では、列車の運転士が区間に入る前に物理的なトークンを持つ必要があります。しかし、このトークンは1つではないため、多くの列車が一方向に次々と区間を通過することができます。トークンは1回に1つしか出せません。これは、区間の両端にある電信機によって確保されます。残りのトークンはタブレット箱にロックされます。これにより、複数の区間と複数の出入口ポイントを持つ単線路線の信号化が可能になります。

Simutrans-Extendedでは、列車は次の信号までしかルートを予約しませんが、次の信号を通過するまではルートを予約解除することはありません。

双信閉塞方式

双信閉塞方式とは、同じ閉塞内に同時に複数の列車が入らないようにする方式です。特に線路が2本以上ある路線で利用されています。

通常は、電話や電信を使って信号所間で連絡を取り合い、信号所にいる人間がすべてをチェックします。場合によっては、電気機械式や電気リレー式など、信号や地点が誤った時刻に移動しないよう、ある程度の保護が施されていることもあります。一般に2現示信号しかないのが特徴ですが、3現示信号も存在し、遠方の現示は次の信号所から制御されるため、「複合信号」(コード:is_presignal=1 and aspects=3)と呼ばれています。

通常の遠方信号は、同じ信号扱所によって制御されているすべての信号の現示を示します。これは、遠方信号は信号扱所が制御する「最初の」信号の前に置かれるべきことを意味します。もし信号機が制御している信号が危険現示である場合、遠方信号は注意現示になります。すべての信号が正常である場合のみ、遠方信号は正常を示します。

中間信号は、信号扱所を追加することなく、2つの信号場の間の区間を2つに分割することができます。

軌道回路方式

軌道回路閉塞の大きな特徴は、自動信号機であることと、単線の線路で動作することです。線路が自動的に列車を検知し、それをもとに信号が作動します。

信号と信号が交差していないまっすぐな線路では、信号の後ろのブロックに列車がいると、信号は自動的に注意現示に戻ります。次のブロックに列車がいなければ、信号は進行現示になります。ただし、国によっては、列車が接近するまで信号機は進行現示ではなく停止現示になることもあります (normal_danger=1 と併用)。線路回路閉塞の分岐点にある信号は半自動で、他の信号へのルートが予約されるまで停止現示のままとなります。

遠方信号は常に次の信号の状態を表すようになり、これまでの双信閉塞信号のように、すべての停止信号が同じ信号扱所で制御されるわけではありません。つまり、先のブロックが空いているかどうかだけでなく、その次のブロック(あるいは3つ目、4つ目のブロック)が空いているかどうかを1つの信号で表示する、複数の進行現示(多灯式の信号機による現示)が利用できるようになりました。

  • 列車が双方向信号を含む区間を予約するとき、方向予約を行います。
  • 軌道回路閉塞方式の双方向線(単線など)は、双方向信号を使う必要があります。

is_presignal=1が定義されている場合、aspects=2しか持つことができません。

車内信号方式

運転台信号は、軌道回路閉塞多灯式信号と全く同じように扱われますが、視認距離は無制限です。

ERTMS方式の信号でグラフィックに信号現示が見えない場合は、必要以上に先に予約するのを防ぐため、現示の種類を2にしておくことをお勧めします。(訳者注:ERTMSは、欧州鉄道交通管理システムの略で、ヨーロッパ全体で共通に使用できる信号保安システムです。)

移動閉塞方式

  • datファイルの max_distance_to_signalbox パラメータは、信号自体の間隔の最大値として使用されます(信号機の範囲は通常通り動作します)。
  • 信号と信号の間にこの最大距離以上のスペースがある場合、列車はその距離の終了した地点から無閉塞運転に戻ります。
  • 振り分け信号は、双信閉塞方式/軌道回路方式と同様に、閉塞全体が空いていないと列車を通過させません。
  • 終着駅でのデッドロックを防ぐために、上記のような振り分け信号の動作が必要です。移動閉塞信号を使用する場合は、必ず終着駅の手前で振り分け信号を使用してください。

datパラメータの紹介

信号機向けのdatパラメータ

全般

obj=roadsign
name=
copyright=
waytype=track
intro_year=
intro_month=
retire_year=
retire_month=
cost=
maintenance

この信号が1ヶ月にかかるシムクレジットの1/100の量。

signal_upgrade_cost

このシグナルにアップグレードするために必要なシムクレジットの1/100の量。指定しない場合のデフォルトは購入金額全額。

upgrade_group

本信号と同じアップグレードグループに属する別の信号は、以下の条件で自動的に本信号にアップグレードされます。

  1. 他の信号が廃止されている。
  2. この信号が建設可能であり、廃止されていない。
  3. 基盤となる線路が更新または交換されていること。

予測できない結果を避けるために、同じ信号グループ内の信号は機能的に同一であることを確認し、同様に、予測できない結果を避けるために、すべてのアップグレードグループ内の1つの信号だけが同時に最新であることを確認するように、十分に注意してください。

upgrade_group=4
working_method

この信号が使用する保安方法です。指定しない場合のデフォルトは軌道回路方式(track_circuit_block)です。無閉塞運転(drive_by_sight)を使用すると、この信号は通常の意味での信号ではなく、信号の終了標識になります (それ以降は無閉塞運転方式で列車が運転されます)。

working_method=drive_by_sight (無閉塞運転)
working_method=time_interval (時間間隔方式)
working_method=time_interval_with_telegraph (電信時間間隔方式)
working_method=one_train_staff (スタフ閉塞方式)
working_method=token_block (タブレット閉塞方式)
working_method=absolute_block (双信閉塞方式)
working_method=track_circuit_block (軌道回路方式・デフォルト)
working_method=cab_signalling (車内信号方式)
working_method=moving_block (移動閉塞方式)
is_signal

これが正常な信号であるかどうか。Standardと同様、is_presignal=1が定義されるのと同時に、is_signal=1を定義してはいけません。例外は複合信号の場合です(下記参照)。

is_signal=1
is_presignal

この信号は遠方信号または中継信号です(working methodに依存)。この信号が2つ以上の現示を持ち、working_method=absolute_block(双信閉塞方式) を使っている場合、複合信号として扱われます (つまり、遠方信号と停止信号の両方を同時に持つ信号です)。停止信号の部分は常に動作しますが、遠方信号の部分は列車のルート上の次の信号扱所が範囲内にある場合のみ動作します。信号が working_method=track_circuit_block(軌道回路方式) の場合、is_presignal=1aspects=2 の時のみ対応しています。

is_longblocksignal

このパラメータを持つ信号は、単一方向の信号であるにもかかわらず、両向きの軌道回路閉塞信号であったかのように方向予約を作成しようとします。

現在、このパラメータは電信時間間隔方式と、方向予約を提供する他の作業方法にのみ使用することができます。

このパラメータを電信無しの時間間隔方式と一緒に使用すると、この信号は「ステーション信号」になります。

is_longblocksignal=1
aspects

信号が持つ現示の種類 (デフォルトは遠方・中継信号を除く全ての信号で2、最大現示数は5)。

aspects=4
max_speed

列車がこの信号に安全に接近できる最高速度をkm/hで指定します。ある速度以上で信号が読みにくくなることをシミュレートするために使用します(指定しない場合のデフォルトは160)。

free_route

この信号は、振り分け信号です。

free_route=1
has_selective_choose

この信号が振り分け信号として動作しているとき、または通常信号として動作しているときに、特別なグラフィックスが提供されるかどうか。

has_selective_choose=1
permissive

この信号が 停止現示の場合、列車は無閉塞運転でこの信号の先に進むことができます。

has_call_on=1と一緒に使うことで、信号の先に列車が進んだときに特定のグラフィックを表示することができます。

permissive=1
has_call_on

信号が特定の「呼び出し」グラフィックが定義されているかどうか。

has_call_on=1
max_distance_to_signalbox

この信号を設置できる、信号所からの最大距離 (メートル)。ただし、working_method=moving_block(移動閉塞方式) の信号では、この項目で移動閉塞信号/ビーコン間の最大距離を指定します ( これ以上の距離があると、その距離を過ぎると列車は無閉塞運転に戻ります)。0 を指定すると、信号扱所までの距離が無限大になります。

signal_groups

これらの番号のいずれかが信号扱所のsignal_groupsのカンマ区切りリストで指定された番号と一致する場合、この信号はその信号扱所から作成できます。0(デフォルト)または1(既知のバグ)の場合、この信号機は信号扱所なしで構築できることを意味します。したがって、使用可能な最小値は2です。

signal_groups=2,5,17,24
allow_underground

この信号が地下、地上、またはその両方に建設される可能性があるかどうか。0は、地上にのみ建設可能であることを意味します。1は、地下にのみ建設可能であることを意味します。2は、地下と地上のどちらにも建設可能であることを意味します。デフォルトは2です。

intermediate_block

working_method=absolute_block(双信閉塞方式) とともに、この信号は「中間閉塞」信号であり、信号扱所および既存の信号機を交換せずに、半自動信号を使って2つの信号扱所の間の双信閉塞区間に追加の閉塞区間を挿入できます。

intermediate_block=1
normal_danger

この信号は、列車が通過した後、進行現示にリセットされません。working_method=track_circuit_block(軌道回路方式) または working_method=cab_signalling(車内信号方式) と組み合わせて、デフォルトで信号が注意現示でなければならない国をシミュレートするために使用されます。

normal_danger=1
double_block

working_method=absolute_block(双信閉塞方式), working_method=track_circuit_block(軌道回路方式), working_method=cab_signal(車内信号方式) でのみ使用できます。

この信号は Simutrans Standard の「プレシグナル」の機能に似ています。次の信号も進行現示の場合に限り、この信号も進行現示になります。これは、保安方法を変更したときに、新しい保安方法の信号がある場所とは別の位置で列車を停車させたい場合に特に便利です。

single_way

一方通行標識になります。

single_way=1
is_private

この標識の先の線路は、限られた会社にしか利用できなくなります。

is_private=1
end_of_choose

振り分け信号は、この信号までの駅を通過する経路を見つけることができます。

end_of_choose=1

グラフィック

上記のどのパラメータを含むかによって、グラフィックパラメータの順序が異なります。

Image[0]から順に、各現示数(2-5)において、各方向4種類の画像を以下の順番で定義します。代替経路は(代替)、主経路は(主)と省略しています。

  • 通常の信号
    • 2現示: 停止、進行
    • 3現示: 停止、進行、注意
    • 4現示: 停止、進行、注意、減速(他にも、停止、進行、警戒、注意)
    • 5現示: 停止、進行、警戒、注意、減速(他にも、高速進行をいれるなどの設定方法も考えられる)
  • has_selective_choose=1が定義されている信号
    • 2現示: 停止、進行(代替)、進行(主)
    • 3現示: 停止、進行(代替)、注意(代替)、進行(主)、注意(主)
    • 4現示: 停止、進行(代替)、注意(代替)、減速(代替)、進行(主)、注意(主)、減速(主)
    • 5現示: 停止、進行(代替)、警戒(代替)、注意(代替)、減速(代替)、進行(主)、警戒(主)、注意(主)、減速(主)
  • has_call_on=1が定義されている信号
    • 2現示: 停止、進行、呼び出し
    • 3現示: 停止、進行、注意、呼び出し
    • 4現示: 停止、進行、注意、減速、呼び出し
    • 5現示: 停止、進行、警戒、注意、減速、呼び出し
  • is_presignal=1aspects=2 の信号
    • 2現示: 注意、進行
  • ステーション信号は、これらのグラフィックス定義が必要になります
    • 3現示: 停止、進行(逆方向)、注意(逆方向)、進行(順方向)、注意(順方向)
    • (訳者注:inverseを逆方向、obverseを順方向と訳したんですが、この向きで合ってるかちょっと自信ないです。)

(訳者補足:日本では警戒・注意・減速現示の信号を通ると、それぞれの速度まで減速するという仕組みです。しかしSimutrans-Extendedでは、時間間隔方式・電信時間間隔方式を除いて、予約できる距離に応じた速度への減速が行われます。)

Pak128.Britain-Exでの例:4現示色灯式赤色振り分け信号

# 停止
Image[0]=images/signal-1-aspect-led-square-route-indicator-danger.1.2,42,-2  
Image[1]=images/signal-1-aspect-led-square-route-indicator-danger.1.0,-37,12  
Image[2]=images/signal-1-aspect-led-square-route-indicator-danger.1.1,-7,-18  
Image[3]=images/signal-1-aspect-led-square-route-indicator-danger.1.3,10,27  
# 進行(代替経路)
Image[4]=images/signal-1-aspect-led-square-route-indicator-clear.1.2,42,-2  
Image[5]=images/signal-1-aspect-led-square-route-indicator-clear.1.0,-37,12  
Image[6]=images/signal-1-aspect-led-square-route-indicator-clear.1.1,-7,-18  
Image[7]=images/signal-1-aspect-led-square-route-indicator-clear.1.3,10,27  
# 注意(代替)
Image[8]=images/signal-1-aspect-led-square-route-indicator-caution.1.2,42,-2  
Image[9]=images/signal-1-aspect-led-square-route-indicator-caution.1.0,-37,12  
Image[10]=images/signal-1-aspect-led-square-route-indicator-caution.1.1,-7,-18  
Image[11]=images/signal-1-aspect-led-square-route-indicator-caution.1.3,10,27  
# 警戒(代替経路)
Image[12]=images/signal-1-aspect-led-square-route-indicator-preliminary-caution.1.2,42,-2  
Image[13]=images/signal-1-aspect-led-square-route-indicator-preliminary-caution.1.0,-37,12  
Image[14]=images/signal-1-aspect-led-square-route-indicator-preliminary-caution.1.1,-7,-18  
Image[15]=images/signal-1-aspect-led-square-route-indicator-preliminary-caution.1.3,10,27  
# 進行(主経路)
Image[16]=images/signal-1-aspect-led-square-route-indicator-clear-main.1.2,42,-2  
Image[17]=images/signal-1-aspect-led-square-route-indicator-clear-main.1.0,-37,12  
Image[18]=images/signal-1-aspect-led-square-route-indicator-clear-main.1.1,-7,-18  
Image[19]=images/signal-1-aspect-led-square-route-indicator-clear-main.1.3,10,27  
# 注意(主経路)
Image[20]=images/signal-1-aspect-led-square-route-indicator-caution-main.1.2,42,-2  
Image[21]=images/signal-1-aspect-led-square-route-indicator-caution-main.1.0,-37,12  
Image[22]=images/signal-1-aspect-led-square-route-indicator-caution-main.1.1,-7,-18  
Image[23]=images/signal-1-aspect-led-square-route-indicator-caution-main.1.3,10,27  
# 警戒(主経路)
Image[24]=images/signal-1-aspect-led-square-route-indicator-preliminary-caution-main.1.2,42,-2  
Image[25]=images/signal-1-aspect-led-square-route-indicator-preliminary-caution-main.1.0,-37,12  
Image[26]=images/signal-1-aspect-led-square-route-indicator-preliminary-caution-main.1.1,-7,-18  
Image[27]=images/signal-1-aspect-led-square-route-indicator-preliminary-caution-main.1.3,10,27  
Icon=> images/signal-icons.6.8  
Cursor=images/signal-icons.6.9

信号扱所向けのdatパラメータ

信号ボックスは建物として扱われ、通常の建物パラメータが適用されます。

(訳者注:建物・停留所編はまだ翻訳していません。 http://forum.simutrans.com/index.php?topic=15222.msg150137#msg150137 を参照してください。)

全般

obj=building
type=signalbox
name=
copyright=
intro_year=
intro_month=
retire_year=
retire_month=
level=
cost=
maintenance=
population_and_visitor_demand_capacity=
employment_capacity=
mail_demand=
allow_underground

この建物が地下、地上、またはその両方のいずれに建設されるかを指定します。0は地下にのみ建設可能であることを意味します。1は、地下にのみ建設可能であることを意味します。2は、地下と地上のどちらにも建設可能であることを意味します。デフォルトは2です。

allow_underground=0 (地上のみ)
allow_underground=1 (地下のみ)
allow_underground=2 (地上にも地下にも)
signal_groups

信号と同様:上記参照

signal_groups=16,24
radius

この信号機に接続された信号を配置できる最大距離(メートル)。

capacity

この信号機に接続可能な信号の最大数

To be updated........(訳者注:2022年12月27日現在での翻訳元の更新日時は、2017年6月11日でした。)

Simutrans-Extendedの機能・datパラメータ紹介(産業・貨物編)

soukouki.hatenablog.jp の産業・貨物編です。

https://forum.simutrans.com/index.php/topic,15229.0.html を翻訳しました。

このガイドは、Simutrans-Extendedバージョン12.x以降にのみ適用されます。

Extendedで追加された機能の紹介

地域

これは、マップの一部を独自の名前を持つ独立した地域として定義することができるシステムです。現在のところ、次のようなものがあります。

  1. 町名
  2. 通り名/停留所名
  3. どのような市内建築物を建設できるか
  4. どのような産業を興すことができるのか

地域は現在、長方形を重ねただけの単純なシステムになっています。市区町村のリストを使って地域の市区町村名と通り名・丁目名を定義するには、次のように括弧内に数字を追加します。[ x ](実際は空白なし)をファイル名の最後、_[language]の部分の直前に追加します。

例えば、地域番号 1 の英語の都市リストには citylist[1]_en.txt を、地域番号 2 のフランス語の道路/停留所リストには streetlist[2]_fr.txt を使用します。地域固有のリストが定義されていない場合、基本リスト (すなわち、角括弧内の任意の番号のないもの) が使用されます。

産業のパラメータに対してのヒント

工場は、通常の市内建築物やプレイヤーの建物と多くのパラメータを共有しており、以下のリファレンス(当記事)にあるいくつかのパラメータは、datファイルのリファレンスを読むことを示唆する内容を含んでいます。建物と停留所編を読んで、全体像を把握することをお勧めします。

(訳者注: 現在、建物と停留所編はまだ翻訳していません。英語のリファレンスは https://forum.simutrans.com/index.php?topic=15222.msg150137#msg150137 にあります。)

それ以外では、工場はSimutrans ExtendedではSimutrans Standardとほぼ同じようにコード化されていますが、いくつかの相違点があります。

訪問者・居住者・労働者

訪問者、居住者、労働者のシステムは他の建物と同じです。したがって、建物と停留所編をお読みください。 (訳者注: 現在、まだ翻訳していません。)

バージョン11.x以前からあります。

貨物の収益について

貨物は、その移動距離に応じて複数の収益を持つことができます。 これは、その財が、指定された距離まで収益を上げ、次の指定された距離まで別の収益を上げる、というように指定されます。これにより、ある財を短距離か長距離のどちらか一方しか輸送できないようにすることが可能になります。

datパラメータの紹介

産業向けのdatパラメータ

全般

obj=factory
name=
copyright=
intro_year=
intro_month=
retire_year=
retire_month=
climates:{S}

この産業が建設可能な気候を カンマ区切りで列挙します。可能な気候は、以下の例の通りです。指定しない場合のデフォルトはすべての気候です。

climates=desert,tundra,tropic
climates=arctic
指定するときの名称 日本語名称 備考
desert 砂漠気候
tropic 熱帯気候
mediterran 地中海性気候
temperate 温暖気候
tundra ツンドラ気候
rocky ? 日本語訳不明
arctic 氷雪気候
water 海岸 沿岸部のみに生成されます
location:{S}

この産業が建設される可能性のある場所を指定します。

  • location=land
    • 市街地の外に建設されます。
    • 注意:location=land かつ climates=water の場合、海岸線にのみ建設されます。
  • location=city
    • 市街地内に建設されます。
  • location=water
    • 広い水面がある場所に建設されます。
distributionweight:{S}

このパラメータは、産業が表示される頻度を指定します。値が大きければ大きいほど、より頻繁に出現します。

max_distance_to_consumer:{E}

この数値はキロメートル単位で設定され、この産業が消費者から建設できる最大距離を指定します。

mapcolor:{S}

このテーブルに従って、ミニマップに表示するマップカラーを設定します。 指定する値は https://simutrans-germany.com/wiki/wiki/en_FactoryDef#The_Parameter_MapColor や日本語wiki https://japanese.simutrans.com/index.php?cmd=read&page=%A5%A2%A5%C9%A5%AA%A5%F3%B3%AB%C8%AF%2Fdat%A5%D5%A5%A1%A5%A4%A5%EB%B5%AD%BD%D2%A5%EA%A5%D5%A5%A1%A5%EC%A5%F3%A5%B9%2Ffactory%28%BB%BA%B6%C8%29#k1e850f5 を参照してください。

upgrade[X]:{E}

(Xは0から始まる255以下の数字です。) 現在の工場が閉鎖されたときにアップグレードされる可能性のある工場のリストを指定します。

upgrade[0]=Bookshop1860
upgrade[1]=Bookshop1920
level:{M}

設定には多くの機能があり、その多くは他のパラメータで上書きすることができます。 主にsimuconf.tabで指定された乗数、またはSimutransにハードコードされた乗数と一緒に使用されます。

  1. 求人数、人数、郵便量のパラメータが指定されていない場合、simuconf.tabの乗算を使ってデフォルト値を設定します。
  2. 建物の上や下に通路を作る場合の通路のコストを計算します(simuconf.tab の値 cost_buy_land に建物のレベルを掛けた値の1/5)。
  3. simuconf.tab のパラメータ max_elevated_way_building_level を用いて,橋や高架を建設することができる建物の最大サイズ (サイズとは "level" の値です) を決めます。
regions:{E}

regions パラメータは、産業を構築することができる地域を設定します。これは、simuconf.tabで指定された地域の番号です。このパラメータを指定しない場合、産業はどの地域でも建設することができます。このパラメータは、すべての建物に適用されます。

regions=1,2,5

貨物の生産と消費

到着する貨物

これらのパラメータは、産業が生産するために必要な原材料を指定します。4つのパラメータは、産業が必要とする貨物の種類ごとにすべて必要です。パラメータの各セットは、0から始まる角括弧内に与えられたインデックス番号と関連付けられています(以下、Xで表されます)。

InputGood[0]=Bretter
InputCapacity[0]=32
InputFactor[0]=87   
InputSupplier[0]=2 
InputGood[1]=Stahl
InputCapacity[1]=16
InputFactor[1]=13
InputSupplier[1]=2
inputgood[X]:{S}

貨物の名前。

inputcapacity[X]:{S}

この貨物をどれだけ産業で保管できるか。

inputfactor[X]:{S}

生産されるものに対して、投入される貨物がどれだけ必要か(パーセントで指定します)。

inputsupplier[X]:{S}

供給元の数を指定します。もし0なら、目的の貨物を供給する各産業が供給者として選ばれます。

出荷する貨物

これらのパラメータは、その産業が製造する貨物を指定します。3つのパラメータはすべて、産業が生産する貨物の種類ごとに必要です。パラメータの各セットは、0から始まる角括弧内に与えられたインデックス番号に関連付けられています(以下、Xで表されます)。

OutputGood[0]=Moebel
OutputCapacity[0]=80
OutputFactor[0]=222
outputgood[X]:{S}

貨物の名前

outputcapacity[X]:{S}

この貨物をどれだけ産業で保管できるか

outputfactor[X]:{S}

生産品に対して原料がどれだけ必要かをパーセントで表したもの。

productivity:{S}

産業で生産される貨物の最小量を定義します。

range:{S}

産業界の実際の生産率がどの範囲にあるかを定義します。最小生産率はproductivity、最大生産率はproductivity+rangeの値になります。

electricity_amount:{S}

生産単位あたりの最大電力消費量。

electricity_proportion:{E}

産業が生産量に対して消費する電力の割合をパーセントで表したもの。100はSimutrans-Standardの値に相当します。これにより、異なる産業で同等の生産量に対して異なる量の電気を使用することができます。デフォルト:17

electricity_boost:{S}

電気が供給された場合の生産量の増加率を1/1000で指定します。デフォルトは1000。

passenger_demand:{S}

この産業が生産単位と時間あたりどれだけの乗客を要求するか。注意(2016/1/28現在):この設定は、まだ完全な産業ブースト機構ではないため、Simutrans Extendedから削除、置き換え、修正される可能性があります。

passenger_boost:{S}

乗客が到着した場合、生産量を1/1000単位で増加させます。デフォルトは0。

mail_demand:{S}

この産業が、生産単位と時間あたりどれだけの郵便物を要求するか。

mail_boost:{S}

メールが届いた場合の生産量の増加率を1/1000で指定します。デフォルトは0。

population_and_visitor_demand_capacity:{E}

この産業が必要とする訪問者の数を指定します。まだ完全に組み込まれた機能ではありませんが(2016/1/28現在)、この設定は最終的におそらく産業ブースト機構に干渉することになるでしょう。

employment_capacity:{E}

この産業が必要とする労働者の数を指定します。まだ完全に組み込まれた機能ではないのですが(2016/1/28現在)、この設定は最終的におそらく産業ブースト機構に干渉することになるでしょう。

max_distance_to_suppplier:{E}

このパラメータは、その産業がどの供給者からも離れることができる最大距離をキロメートル単位で設定します。しかし、この距離は、その産業の現在の地域に供給者が存在しない投入貨物については適用されません。

産業の増強(フィールドを使わないもの)

expand_probability:{S}

増強する確率です。10.000より大きい場合、生産中に常に拡大します。

expand_minimum:{S}

生産時の最小の増強率を指定します。

expand_range:{S}

この値から0までの間の乱数で生産量が増加します。

expand_times:{S}

この産業が発展する最大の回数を指定します。

フィールドを使う産業

(以下訳者より)

Standardからの変化はないので、日本語wikihttps://japanese.simutrans.com/index.php?cmd=read&page=%A5%A2%A5%C9%A5%AA%A5%F3%B3%AB%C8%AF%2Fdat%A5%D5%A5%A1%A5%A4%A5%EB%B5%AD%BD%D2%A5%EA%A5%D5%A5%A1%A5%EC%A5%F3%A5%B9%2Ffactory%28%BB%BA%B6%C8%29#fe83d366 を参照してください。

翻訳元の記事では start_fields は書かれていませんでしたが、Pak128.Britain-Exに start_fields を指定している箇所がありました。恐らく記事が書かれた後に追加されたのだと思われます。

煙を使う産業

(以下訳者より)

同じくStandardからの変化はないので、日本語wikihttps://japanese.simutrans.com/index.php?cmd=read&page=%A5%A2%A5%C9%A5%AA%A5%F3%B3%AB%C8%AF%2Fdat%A5%D5%A5%A1%A5%A4%A5%EB%B5%AD%BD%D2%A5%EA%A5%D5%A5%A1%A5%EC%A5%F3%A5%B9%2Ffactory%28%BB%BA%B6%C8%29#k35565c1 を参照してください。

グラフィック

工場のグラフィックは、通常の建物と同じようにコード化されていますが、最大4つの回転がありますが、サイズの制限はなく、アイコンとカーソルはありません。パラメータは以下の通りです。

(訳者注:建物・停留所編はまだ翻訳していません。 http://forum.simutrans.com/index.php?topic=15222.msg150137#msg150137 を参照してください。)

needs_ground=
animation_time=
seasons=
dims=
backimage[0][0][0][0][0][0]=
frontimage[0][0][0][0][0][0]=

貨物向けのdatパラメータ

全般

obj=good
name:{S}

貨物の名前。パックセットにどのような貨物を組み込むかは完全に自由ですが、3つの必須貨物は必ず作成する必要があります。それらは name=passagiere, name=post, name=noneです。「none」は実際の貨物ではなく(輸送できない)、他のすべてのパラメータを0に設定することができます。これはSimutransの設計によるものです。

metric:{S}

この貨物の1つの「単位」を、バレル、トン、または他の方法で何と呼ぶか。

metric=tonnen
metric=kilolitres
catg:{S}

0から7までの数字で、この貨物がどのカテゴリに属しているかを表します。catg=0を除いて、同じカテゴリーに属するすべての貨物は、同じ車両で輸送することができます。catg=0が指定された場合、それは「特殊貨物」とみなされ、その貨物を運ぶために、nameパラメータで特定される特殊車両が必要となります。 name=passagierename=postname=noneも特殊貨物として扱われるため、catg=0が必要であることに注意してください。

weight_per_unit:{S}

この商品の1つの「単位」の重さをKgで表します。metric=tonnesなどの重量単位が使われている場合は、必ずその重量をKg単位で入力してください。

mapcolor:{S}

このテーブルに従って、ミニマップに表示するマップカラーを設定します。 指定する値は https://simutrans-germany.com/wiki/wiki/en_FactoryDef#The_Parameter_MapColor や日本語wiki https://japanese.simutrans.com/index.php?cmd=read&page=%A5%A2%A5%C9%A5%AA%A5%F3%B3%AB%C8%AF%2Fdat%A5%D5%A5%A1%A5%A4%A5%EB%B5%AD%BD%D2%A5%EA%A5%D5%A5%A1%A5%EC%A5%F3%A5%B9%2Ffactory%28%BB%BA%B6%C8%29#k1e850f5 を参照してください。

貨物の収益

各パラメータの組は、0から始まる角括弧内のインデックス番号と関連付けられています(以下、Xで表します)。 value[X] は、 to_distance[X] で設定された距離まで1キロメートルごとに支払われる実際のシムクレジットの1/100の量を定義します(キロメートルで指定されます)。 次のエントリは、そのキロメートル数までの値を定義します。最後のエントリは、無限の長さをシミュレートするために、to_distance[X]=0 という値を持つ必要があります。

例 この貨物の場合、最初の16kmは60c/km、次の32kmは50c/km、それ以降は47c/kmとなります。

value[0]=60
to_distance[0]=16

value[1]=50
to_distance[1]=32

value[2]=47
to_distance[2]=0

クラスについて

このセクションは、name=passagierename=post の場合にのみ使用されます。

number_of_classes=5
class_revenue_percent[0]=60
class_revenue_percent[1]=100
class_revenue_percent[2]=133
class_revenue_percent[3]=150
class_revenue_percent[4]=200
number_of_classes:{E}

この貨物が持つクラスの数。最大255個。

class_revenue_percent[X]

これは、この特定のクラスがどれだけの収益を生み出すかを計算するための修正値です。class_revenue_percent[X]=100 の場合、このクラスは value[X]のセクションで述べた収益の100%を生成することを意味します。

Simutrans-Extendedのdatパラメータ紹介(一部)

この記事はSimutrans Advent Calendarの11日目の記事です。

adventar.org

前座

soukouki.hatenablog.jp

本編

soukouki.hatenablog.jp 本当は全種類やりたかったんですが、さすがに大遅刻をかましているので諦めました。

soukouki.hatenablog.jp (2022-12-25)追加しました!

soukouki.hatenablog.jp (2022-12-28)追加しました!

datファイルのキーについて

最初はSimutrans-Extendedのソースコードを漁ってパラメータの一覧を書く予定だったんですが、全タイプに共通する部分と、obj=bridgeについて纏めただけでかなり疲れたので諦めました。全タイプに共通する処理の部分だけ載せておきます。

全タイプ共通

obj=<後述>

  • オブジェクトのタイプ
  • 設定できる値は次のどれか
    1. bridge
    2. building
    3. citycar
    4. crossing
    5. factory
    6. good
    7. ground
    8. ground_obj
    9. pedestrian
    10. pier (extで追加された橋脚ツール)
    11. roadsign
    12. menu
    13. cursor
    14. symbol
    15. smoke
    16. field
    17. misc (その他各オブジェクトに属さないもの)
    18. sound
    19. tree
    20. tunnel
    21. vehicle
    22. way-object
    23. way
  • name=<任意の文字列>
  • オブジェクトの名前です

cell_size=<整数>

  • オプショナル
    • デフォルトの値はmakeobjで指定した値
  • 画像のサイズを指定します
  • 普通はmakeobj pak128 hoge.pak hoge.datのようにmakeobjのコマンドラインでpakサイズを指定しますが、datファイル内でも指定できます。(cell_size側が優先されます)

copyright=<任意の文字列>

  • オプショナル
    • デフォルトの値は空文字列
  • 製作者の名前を指定します