Charlyプログラミング言語
この記事は、ゲストライターシリーズの最初の記事です。もしCrystalを使って何か素晴らしいものを構築し、ブログであなたの経験を共有したい場合は、お知らせください!
今日のゲスト著者はLeonard Schützです。彼はプログラミング言語の作り方を学ぶ手段としてCharlyプログラミング言語を作成しました。Rubyでの最初のイテレーションの後、言語インタープリターを実装するためにCrystalに移行しました。この記事では、言語を紹介し、その仕組みと、実装にCrystalを選択した理由を示します。
はじめに
Charlyは動的型付けのオブジェクト指向プログラミング言語です。構文は主にJavaScriptやRubyのような言語に触発されていますが、記述する際の自由度が高くなっています。最初に気づくかもしれない違いは、セミコロンがないことや、ほとんどの言語の制御構造で括弧が必要ないことです。Charlyは現在のところ、余暇に書かれたおもちゃの言語に過ぎません。
どのように見えるのか?
以下は、Charlyで書かれたバブルソートアルゴリズムの実装です。これは、Charlyで書かれた標準ライブラリの一部です。
func sort(sort_function) {
const sorted = @copy()
let left
let right
@length().times(->(i) {
(@length() - 1).times(->(y) {
left = sorted[i]
right = sorted[y]
if sort_function(left, right) {
sorted[i] = right
sorted[y] = left
}
})
})
sorted
}
このプログラムは、60x180サイズのボックスにマンデルブロ集合を出力します。
60.times(func(a) {
180.times(func(b) {
let x = 0
let y = 0
let i = 0
while !(x ** 2 + y ** 2 > 4 || i == 99) {
x = x ** 2 - y ** 2 + b / 60 - 1.5
y = 2 * x * y + a / 30 - 1
i += 1
}
if i == 99 {
write("#")
} else if i <= 10 {
write(" ")
} else {
write(".")
}
})
write("\n")
})
このリンクは、完全にCharlyで記述された式パーサー/インタープリターに移動します。整数値の加算と乗算をサポートしています。
どのように動作するのか?
まず、Charlyはソースファイルをトークンのリストに変換します。トークンは基本的に型を持つ文字列にすぎません。簡単なhello-worldプログラムは、次のトークンで構成される場合があります。
$ cat test/debug.ch print("Hello World") $ charly test/debug.ch -f lint -f tokens 1:1:5 │ Identifier │ print 1:6:1 │ LeftParen │ ( 1:7:13 │ String │ "Hello World" 1:20:1 │ RightParen │ ) 1:21:1 │ Newline │ 2:1:1 │ EOF │
プログラムのこの部分は、レクサー(字句解析)と呼ばれます。ソースコードを論理的な文字グループに変換します。たとえば、print識別子は、文字列printを含むIdentifier型のトークンになります。印刷されるテキストも同様です。これは、文字列Hello Worldを含むString型のトークンになります。
プログラム全体がトークンのリストに変換された後、パーサーはそれらをAST(抽象構文木)に変換します。ASTは、プログラムをツリー構造として表現する方法です。各ノードには型があり、0個以上の子があります。式1 + 2 * 3
は、次のようなASTを生成します
オブジェクトに対するメソッド呼び出しなど、より高度なものは次のようになります
プログラム全体がASTに変換されると、インタープリターはこの構造を再帰的にトラバースし始めます。この手順は、ASTと言語ロジックを分離するためにビジターパターンに従います。
BinaryExpression
を例にとってみましょう。これには3つのプロパティがあります。式の2つの値と演算子です。この演算子は、プラス、マイナス、または言語がサポートするその他の演算子である可能性があります。最初に2つの値を解決し、どの演算子が使用されているかを確認し、それを2つの値に適用します。どの値がどちら側にあるかによって、この手順は完全に異なる結果を生み出す可能性があります。3 + [1, 2]
は [1, 2] + 3
(NAN
および [1, 2, 3]
)と同じではありません。
IdentifierLiteral
は現在のスコープから値をロードし、CallExpression
は定義済みの関数などを呼び出します。
なぜCrystalなのか?
このプロジェクトでCrystalを使用した主な理由は、速度とシンプルさでした。
Crystalの構文と標準ライブラリはどちらもRubyに触発されています。これは、Crystalプロジェクトで、Rubyの世界からの多くの知識と確立された原則と実践を再利用できることを意味します。APIの詳細の多くは非常に似ています。たとえば、Crystalでファイルを開く方法に関する情報が見つからない場合は、Googleで「Ruby open file」と検索することもでき、StackOverflowでの最初の回答が100%有効なCrystalコードであることがわかります。もちろん、これはより複雑な場合には当てはまりませんが、常にインスピレーションの場所として使用できます。
Crystalのもう1つの優れた点は、多くの低レベルのものを扱う必要がないことです。Crystalの標準ライブラリは、メモリ管理でさえ、低レベルと見なされるもののほとんどを処理します。本当に低レベルのことをする必要がある場合は、生のポインタとCへのバインディングにアクセスできます。これは、Crystalで正規表現リテラルが実装される方法でもあります。内部的には、PCREライブラリにバインドし、その上に使いやすい抽象化を配置します。車輪を再発明する代わりに、Crystalは既存のCライブラリにバインドするだけです。Crystalは、C標準ライブラリ、OpenSSL、LibGMP、LibXML2、LibYAML、およびその他多数にもバインドします。
Crystalに切り替えるもう1つの大きな理由は速度でした。Crystalは驚くほど高速です。Ruby 2.3を使用した古いCharly実装では、1つのファイルを解析するだけで300ミリ秒以上かかりました。それに加えて、テストスイートの実行に約1.8秒かかりました。Crystalで記述され、--release
オプションでコンパイルされたプログラムは、それらすべてを実行するのに約1〜2ミリ秒しかかかりませんでした!非常に印象的です。
CrystalはLLVMを使用しているため、プログラムはすべての最適化パスを通過します。これらには、定数フォールディング、デッドコード除去、関数インライン化、さらにはコンパイル時の完全なコードブランチの評価が含まれますが、これらに限定されません。Rubyではそれは得られません;)
インタープリターのほとんどをCrystalで書き直すのに約1週間かかりました。標準ライブラリへの小さなバグ修正と変更のみにそれよりも時間がかかりました。
Crystalでの開発のもう1つの優れた点は、コンパイラ自体もCrystalで記述されていることです。これは、Crystalがセルフホストであることを意味します。Crystalのコンパイラからコードをコピーして、自分の使用のために適応させたことが複数回ありました。たとえば、Crystalのパーサーとレクサーは、これらの仕組みを理解する上で非常に役立ちました(パーサーとレクサーを以前に書いたことはありませんでした)。
マクロシステム
マクロシステムは、多くの場所で非常に役立ちました。これは主にボイラープレートを回避し、DRYパターンに従うために使用されました。
マクロシステムをどのように使用できるかの実際の例については、次のファイルをご覧ください
たとえば、Crystalの標準ライブラリは、マクロを使用してプロパティメソッドを提供します。クラスに新しいインスタンス変数を導入するときに、ボイラープレートを回避するために使用できます。
結論
現在の状態では、Charlyは私自身の学習プロジェクトにすぎません。現時点では、インタープリターを自分で作成する方法に関する学習リソースとしての他に、Charlyを何か深刻なことに使用することはお勧めしません。CharlyはGitHubで開発されているため、自由に問題をオープンしたり、新機能を提案したり、独自のプルリクエストを送信したりしてください。この記事のコメントでのフィードバックも非常に感謝しています。
私は2016年8月頃からCrystalを使用し始めましたが、絶対に気に入っています。これは、これまでコードを記述した中で最も表現力があり、やりがいのある言語の1つです。以前にCrystalを使用したことがない場合は、今すぐ試してみてください。
著者について
私の名前はLeonard Schützです。私はスイス出身の16歳の学生です。私は現在、シーメンスのヘルスケア分野で見習いをしており、主にPHP、EWS、その他のWebテクノロジーを使用しています。余暇には、Charlyプログラミング言語が現在のプロジェクトであるサイドプロジェクトに取り組むのが好きです。
気軽にTwitter、GitHubをフォローするか、私のウェブサイトleonardschuetz.chをご覧ください
読んでくれてありがとう!
リンクとソース
- Leonard Schütz: leonardschuetz.ch
- Charlyプログラミング言語: charly-lang/charly
- GraphViz (ASTの視覚化に使用): www.graphviz.org
- Stackoverflowでの「Ruby open file」: how-to-read-an-open-file-in-ruby
- Rubyインタープリターに使用された古いテストスイート: test/main.ch