cxx-ruby は C++23 で書かれた実験的な Ruby インタプリタです。
このプロジェクトは MRI Ruby の互換実装ではありません。Ruby 風の構文、オブジェクト、ブロック、クラス、例外、標準的な組み込みメソッドの一部を実装した、小さなツリーウォーク型インタプリタです。教育用途、実装実験、言語処理系の拡張題材として使うことを想定しています。
実用的な Ruby アプリケーション、RubyGems、Rails、Bundler、標準ライブラリ互換などを動かすための処理系ではありません。
- C++23 と CMake でビルドできます。
- Ruby ソースファイルを実行できます。
-eで短い Ruby 断片を実行できます。- 引数なしで起動すると簡易 REPL として動作します。
- 字句解析器、再帰下降パーサ、AST、動的な値表現、ツリーウォーク評価器を持ちます。
nil、真偽値、整数、浮動小数点数、シンボル、文字列、配列、Hash、Range、Proc、Class、ユーザー定義オブジェクトを扱えます。- 変数、代入、算術、比較、制御構文、メソッド定義、ブロック、
yield、クラス定義、継承、例外処理の基本を実装しています。 String、Array、Hash、Range、Enumerable風のメソッドを一部実装しています。examples/tour.rbで主要機能をまとめて確認できます。
- CMake 3.20 以上
- C++23 に対応した C++ コンパイラ
外部ライブラリへの依存はありません。
cmake -S . -B build
cmake --build build実行ファイルは次に作成されます。
./build/cxx-rubyRuby ファイルを実行します。
./build/cxx-ruby examples/tour.rb短いコードを実行します。
./build/cxx-ruby -e 'puts [1, 2, 3].map { |n| n * 2 }.inspect'引数なしで起動すると簡易 REPL になります。
./build/cxx-rubyREPL は IRB 互換ではありません。1 行ずつ簡単に動作確認するためのものです。
class Animal
attr_accessor :name
def initialize(name)
@name = name
end
def speak
"..."
end
def to_s
"#{self.class.name}(#{@name}): #{speak}"
end
end
class Dog < Animal
def speak
"Woof"
end
end
puts Dog.new("Rex")パーサと評価器は、現在おおよそ次の構文を扱えます。
- 整数、浮動小数点数、真偽値、
nil、シンボル、文字列、配列、Hash、Range リテラル - ダブルクォート文字列の
#{...}補間 - ローカル変数、インスタンス変数、グローバル変数、クラス変数、定数
- 通常代入、演算代入、多重代入、添字代入
- 算術演算、比較演算、ビット演算の一部
&&、||、and、or、not、!if、elsif、else、unless- 後置
if/unless/while/until - 三項演算子
while、until、forcase/whenreturn、break、next{ ... }とdo ... endのブロックyieldproc、lambda、->の簡易サポート- 明示的レシーバー呼び出し、暗黙の
self呼び出し - 括弧あり・括弧なしのメソッド呼び出しの一部
- 簡易 splat、ブロック引数、ブロック渡し
- safe navigation 構文
&. def、endless method definitiondef self.foo形式の簡易 singleton methodclass、module、継承指定superbegin/rescue/ensure- メソッド本体末尾の
rescue - 基本的な定数参照とクラス作成
組み込みライブラリは C++ で手書きされています。MRI Ruby の標準ライブラリではなく、この処理系専用の最小実装です。
Kernel 風のメソッド例:
putsprintpppraisefailrandsrandsleepexitexit!abortblock_given?loopformatsprintfprintfIntegerFloatStringArraygetsrequirerequire_relativeattr_readerattr_writerattr_accessor
Enumerable 風のメソッド例:
eacheach_with_indexeach_with_objectmapcollectflat_mapcollect_concatselectfilterrejectfinddetectfind_indexfind_allcountreduceinjectsumminmaxmin_bymax_bysortsort_bygroup_bypartitionany?all?none?tallyto_aentriesfirstinclude?member?each_sliceeach_consziptakedroptake_whiledrop_whileto_hreverse_each
そのほか、数値、文字列、配列、Hash、Range、Symbol、Proc、Class、nil、真偽値向けに多数のメソッドを実装しています。正確な一覧は src/builtins.cpp、src/builtins_methods.cpp、src/builtins_dispatch.cpp を参照してください。
主なファイルは次の通りです。
src/lexer.cpp/src/lexer.hpp: Ruby 風ソースコードをトークン列に変換します。src/parser.cpp/src/parser.hpp: トークン列から AST を構築します。src/ast.hpp: AST ノード定義です。src/value.hpp/src/value.cpp: 実行時の値、クラス、オブジェクト、配列、Hash、Proc などを定義します。src/interpreter.cpp/src/interpreter.hpp: AST を評価するツリーウォーク実行器です。src/builtins.cpp: Kernel 風メソッド、例外、表示、変換などを実装します。src/builtins_methods.cpp: Enumerable、Numeric、String などのメソッドを実装します。src/builtins_dispatch.cpp: Array、Hash、Range、Proc、Class、Symbol などの dispatch を実装します。src/main.cpp: CLI、-e、REPL の入口です。
実行時の値は std::variant で表現されています。文字列、配列、Hash、Range、Proc、Class、ユーザー定義オブジェクトなど、可変または同一性を持つ値は std::shared_ptr で保持します。
return、break、next、Ruby 例外は、評価器内部では C++ 例外として実装されています。
この章がこの README で最も重要な部分です。cxx-ruby は Ruby 風の処理系ですが、MRI Ruby とは多くの点で違います。
- MRI Ruby 互換実装ではありません。
- Ruby 仕様への準拠を目標にしていません。
- ある MRI バージョンとの完全互換を目指していません。
RUBY_VERSIONは合成された値であり、MRI の実バージョン互換を示しません。- RubyGems は動きません。
- Bundler は動きません。
- Rails は動きません。
- Rake は動きません。
- IRB は含まれていません。
- Ruby 標準ライブラリは含まれていません。
- 実際の Ruby プロジェクトをそのまま実行する用途には向いていません。
examples/tour.rbが通ることは、一般的な Ruby 互換性を意味しません。
- パーサは独自の再帰下降パーサです。
- MRI の parse.y、Prism、Ripper とは別物です。
- Ruby の文法全体は実装していません。
- 多くの合法な Ruby コードは parse error になります。
- MRI と同じエラーメッセージは出ません。
- MRI と同じ warning は出ません。
- magic comment は解釈しません。
# frozen_string_literal: trueは無視されます。- encoding comment は無視されます。
__FILE__、__LINE__、__ENCODING__は実装していません。- 正規表現リテラルは実装していません。
%w、%i、%q、%Q、%r、%x、%sなどの percent literal は実装していません。- heredoc は実装していません。
- バッククォートによるコマンド実行は実装していません。
- 文字列エスケープは MRI ほど完全ではありません。
- シングルクォート文字列とダブルクォート文字列の差異は MRI ほど正確ではありません。
- Unicode 識別子は想定していません。
- Ruby の全ての数値リテラル形式は実装していません。
- 2 進数、8 進数、16 進数、underscore、Rational、Complex などは不完全です。
- pattern matching の
case ... inは実装していません。 - pin operator は実装していません。
- endless range、beginless range は実装していません。
BEGIN/ENDブロックは実装していません。aliasは言語機能として実装していません。undefは実装していません。defined?は parser header に名残がありますが、実用的な言語機能としては未実装です。retryは実装していません。redoは実装していません。unless、後置構文、コマンド呼び出し構文は一部だけ対応しています。- MRI の細かい演算子優先順位や曖昧構文の解釈とは異なる場合があります。
- キーワード引数構文は簡易的に Hash 化するだけで、MRI 互換ではありません。
- MRI の内部オブジェクトモデルとは全く違います。
VALUE、RBasic、RClass など MRI の内部構造は使っていません。- singleton class / eigenclass は一般的には実装していません。
- オブジェクト単位の singleton method は基本的にありません。
def self.fooなど一部のクラス側メソッドだけ簡易的に扱います。method_missingは実装していません。respond_to_missing?は実装していません。send、public_send、method、instance_methodなどのリフレクションは未実装または不完全です。ObjectSpaceはありません。object_idの MRI 互換性はありません。- オブジェクトの identity、allocation、finalizer は MRI と違います。
- GC はありません。
- C++ の
shared_ptrで寿命を管理します。 - 循環参照は回収されない可能性があります。
freezeは凍結状態を強制しません。frozen?は MRI と同じ意味を持ちません。- taint、trust、fstring、hidden class、shape など MRI 固有の概念はありません。
BasicObject、Object、Class、Moduleはありますが、メソッド数も意味も非常に限定的です。
- クラス定義と単純な継承はできます。
- モジュールは class-like なオブジェクトとして作られますが、MRI の Module 互換ではありません。
includeはプレースホルダーに近く、実際の mixin として機能しません。extendも MRI のような singleton class への mixin ではありません。prependは実装していません。- refinements は実装していません。
usingは実装していません。- 可視性制御は実質的にありません。
private、public、protectedは MRI のように dispatch を制限しません。- 定数探索は簡易実装です。
- lexical constant lookup は MRI と一致しません。
- namespace と
::は一部だけ対応しています。 - autoload はありません。
- class variable の探索・警告・継承挙動は MRI と違います。
superの引数省略時 forwarding は MRI と一致しません。
- 引数束縛は簡易実装です。
- 必須引数、optional 引数、splat、block 引数は一部だけ対応しています。
- keyword 引数は MRI 互換ではありません。
- positional 引数と keyword 引数の分離は再現していません。
- Ruby 3 系の keyword argument semantics は実装していません。
- arity error は MRI ほど厳密ではありません。
- 引数不足でも
nilが入る場合があります。 - ブロック引数の destructuring は簡易的です。
- ブロックローカル変数は実装していません。
Procとlambdaの非局所 return / break の違いは完全ではありません。Proc#arityは簡易計算です。Proc#curryは実質的にプレースホルダーです。- Enumerator を返すべき場面で Enumerator を返さないことが多いです。
&:symbolは一部動作しますが、MRI の完全なSymbol#to_procではありません。
yieldは単純なブロック呼び出しで動きます。block_given?は現在の評価フレームに基づく簡易判定です。break、next、returnは C++ 例外で実装されています。- ネストした proc、lambda、method、ensure をまたぐ複雑な非局所制御フローは MRI と一致しない場合があります。
- Fiber はありません。
- Continuation はありません。
- Enumerator::Yielder はありません。
- lazy enumerator はありません。
- 例外クラス階層は小さな手書き実装です。
Exception、StandardError、RuntimeError、ArgumentErrorなど一部だけあります。- backtrace は記録しません。
Exception#backtraceは実用的に使えません。Exception#causeは実装していません。full_messageは実装していません。- rescue の型照合は簡易的です。
- rescue 節の
elseは MRI と同じようには評価されません。 ensureは基本的なケースに対応していますが、全ての制御フローで MRI と一致する保証はありません。raiseの引数バリエーションは一部だけ対応しています。- system exception、signal、interrupt は扱いません。
- Ruby 標準ライブラリは付属しません。
requireはファイルを読みません。require_relativeもファイルを読みません。- 現状の
require/require_relativeはfalseを返すスタブです。 $LOAD_PATHは実装していません。$LOADED_FEATURESは実装していません。- RubyGems の activation はありません。
- native extension は読み込めません。
Fileはありません。Dirはありません。- 実用的な
IOクラス階層はありません。 getsは標準入力から 1 行読む程度の実装です。Time、Date、JSON、CSV、YAML、Set、Pathname、BigDecimalなどはありません。Socket、OpenSSL、Net::HTTPなどはありません。Ripper、Prism、RubyVMはありません。Thread、Fiber、Monitor、Mutexはありません。ARGVは空配列として用意されていますが、CLI 引数とは連動していません。
Integerはstd::int64_tです。- MRI のような任意精度整数ではありません。
- オーバーフロー時の挙動は MRI と違います。
Floatは C++ のdoubleです。- NaN、Infinity、丸め、表示は MRI と一致しない場合があります。
- Rational はありません。
- Complex はありません。
Numeric階層は名前だけ近く、メソッド群は限定的です。Integer#times、upto、downto、stepなどは基本ケース向けです。- 例外や境界条件は MRI と一致しない場合があります。
Stringは基本的に byte string として扱います。Encodingはありません。- UTF-8 文字単位の処理は保証しません。
- Unicode case folding はありません。
upcase/downcaseは ASCII 的な処理です。lengthはバイト長に近い挙動です。- 正規表現がないため、
sub/gsubは単純な文字列置換です。 splitは MRI の regexp、limit、空白分割の細かい仕様を再現しません。String#%/sprintfは一部のフォーマットだけです。- 破壊的メソッドと非破壊的メソッドの戻り値は MRI と異なる場合があります。
- frozen string literal はありません。
- 多くの基本メソッドはありますが、完全ではありません。
- Enumerator を返すべきケースで Enumerator を返さないことがあります。
Array#packは空文字列を返すだけのプレースホルダーです。Array#sample/shuffleは簡易乱数です。- 比較、再帰構造、ハッシュ化、破壊的操作の細かい挙動は MRI と一致しません。
Array#fillなどの引数バリエーションは限定的です。Array#sortは実装の比較関数に依存し、MRI の全挙動とは異なります。
- Hash は順序を保持します。
- 内部的には vector の線形探索です。
- 大きな Hash では遅いです。
- default value / default proc は実質的にありません。
Hash#defaultはnilを返すだけです。rehashはありません。- key mutation まわりの挙動は MRI と違います。
Hash#fetchなど一部はありますが、例外やメッセージは MRI と一致しません。- block arity による key/value の渡し方は簡易的です。
- 主に整数 Range の反復を想定しています。
- 文字列 Range、独自オブジェクト Range は MRI ほど動きません。
- beginless range はありません。
- endless range はありません。
cover?、include?、===は単純な比較です。stepは基本ケースだけです。- Range と Enumerator の連携はありません。
- 実装は配列化して処理するものが多いです。
- 遅延評価はありません。
- Enumerator オブジェクトはありません。
eachにブロックがない場合の MRI 互換挙動はありません。- size hint はありません。
- chaining は MRI の Enumerator ほどできません。
- Hash の key/value 展開は MRI と完全一致しません。
Enumerableモジュールとして include される仕組みではなく、型ごとの組み込み dispatch です。
- ファイル I/O はありません。
- ディレクトリ操作はありません。
- ソケットはありません。
systemはありません。- backtick はありません。
exec、fork、spawnはありません。- pipe はありません。
- process status はありません。
- 環境変数 API はありません。
- signal handling はありません。
sleepは実際に待たず、すぐ返るスタブです。exitとabortはホストプロセスを直接終了します。
- Thread はありません。
- Mutex はありません。
- Queue はありません。
- ConditionVariable はありません。
- Fiber はありません。
- Scheduler はありません。
- Ractor はありません。
- GVL のような概念はありません。
- ツリーウォーク実行です。
- bytecode compiler はありません。
- JIT はありません。
- inline cache はありません。
- method cache はありません。
- Hash は線形探索です。
- 多くのメソッドは配列や文字列を eager にコピーします。
- 再帰的な Ruby コードは C++ の call stack を消費します。
- 最適化より読みやすさと実験しやすさを優先しています。
- サンドボックスではありません。
- ユーザーコードは実装済みの
exitなどを呼べます。 - 無限ループを止める仕組みはありません。
- メモリ使用量制限はありません。
- CPU 時間制限はありません。
- 深い再帰でプロセスが落ちる可能性があります。
- 信頼できないコードを安全に実行する用途には使えません。
- IRB ではありません。
- 複数行入力はほぼ考慮していません。
- 履歴はありません。
- 補完はありません。
- pretty inspect の補助はありません。
- 例外からの復帰や構文エラー表示は最小限です。
- RubySpec は通していません。
- MRI のテストスイートは通していません。
- 自動テストターゲットはまだありません。
- 現在の主な検証は
examples/tour.rbです。 - 「この README にある機能」は現時点の実装に基づく説明であり、MRI 互換保証ではありません。
よく使う確認コマンドです。
cmake --build build
./build/cxx-ruby examples/tour.rb
./build/cxx-ruby -e 'puts 1 + 2 * 3'機能追加時の主な編集場所です。
- トークンを増やす場合:
src/lexer.cpp、src/lexer.hpp - AST ノードを増やす場合:
src/ast.hpp - 構文を増やす場合:
src/parser.cpp、src/parser.hpp - 評価規則を増やす場合:
src/interpreter.cpp、src/interpreter.hpp - 組み込みメソッドを増やす場合:
src/builtins.cpp、src/builtins_methods.cpp、src/builtins_dispatch.cpp
この README 作成時点では、次のコマンドで確認しています。
cmake --build build
./build/cxx-ruby examples/tour.rb
./build/cxx-ruby -e 'puts [1,2,3].map { |x| x * 2 }.inspect'examples/tour.rb は、算術、文字列、配列、Hash、制御構文、メソッド、ブロック、クラス、継承、attr_accessor、<=> を使った比較、例外処理、メソッドレベル rescue、case を確認します。