Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions app/pagesList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,25 @@ export const pagesList = [
{ id: 9, title: "ジェネレータとデコレータ" },
],
},
{
id: "ruby",
lang: "Ruby",
description: "hoge",
pages: [
{ id: 1, title: "rubyの世界へようこそ" },
{ id: 2, title: "基本構文とデータ型" },
{ id: 3, title: "制御構造とメソッド定義" },
{ id: 4, title: "すべてがオブジェクト" },
{ id: 5, title: "コレクション (Array, Hash, Range)" },
{ id: 6, title: "ブロックとイテレータ" },
{ id: 7, title: "クラスとオブジェクト" },
{ id: 8, title: "モジュールとMix-in" },
{ id: 9, title: "Proc, Lambda, クロージャ" },
{ id: 10, title: "標準ライブラリの活用" },
{ id: 11, title: "テスト文化入門" },
{ id: 12, title: "メタプログラミング入門" },
],
},
{
id: "cpp",
lang: "C++",
Expand Down
9 changes: 8 additions & 1 deletion app/terminal/exec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ export function ExecFile(props: ExecProps) {
terminalInstanceRef.current!.write(systemMessageColor("実行中です..."));
const outputs = await runFiles(props.filenames);
clearTerminal(terminalInstanceRef.current!);
writeOutput(terminalInstanceRef.current!, outputs, false);
writeOutput(
terminalInstanceRef.current!,
outputs,
false,
undefined,
props.language
);
// TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる
setExecResult(props.filenames.join(","), outputs);
setExecutionState("idle");
Expand All @@ -60,6 +66,7 @@ export function ExecFile(props: ExecProps) {
runFiles,
setExecResult,
terminalInstanceRef,
props.language,
]);

return (
Expand Down
2 changes: 1 addition & 1 deletion app/terminal/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const sampleConfig: Record<RuntimeLang, SampleConfig> = {
},
ruby: {
repl: true,
replInitContent: '>> puts "Hello, World!"\nHello, World!',
replInitContent: 'irb(main):001:0> puts "Hello, World!"\nHello, World!',
editor: {
"main.rb": 'puts "Hello, World!"',
},
Expand Down
31 changes: 19 additions & 12 deletions app/terminal/repl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ export type SyntaxStatus = "complete" | "incomplete" | "invalid"; // 構文チ
export function writeOutput(
term: Terminal,
outputs: ReplOutput[],
endNewLine: boolean
endNewLine: boolean,
returnPrefix: string | undefined,
language: RuntimeLang
) {
for (let i = 0; i < outputs.length; i++) {
const output = outputs[i];
Expand All @@ -47,6 +49,12 @@ export function writeOutput(
case "system":
term.write(systemMessageColor(message));
break;
case "return":
if (returnPrefix) {
term.write(returnPrefix);
}
term.write(highlightCodeToAnsi(message, language));
break;
default:
term.write(message);
break;
Expand Down Expand Up @@ -77,7 +85,7 @@ export function ReplTerminal({
checkSyntax,
splitReplExamples,
} = useRuntime(language);
const { tabSize, prompt, promptMore } = langConstants(language);
const { tabSize, prompt, promptMore, returnPrefix } = langConstants(language);
if (!prompt) {
console.warn(`prompt not defined for language: ${language}`);
}
Expand Down Expand Up @@ -160,12 +168,18 @@ export function ReplTerminal({
const handleOutput = useCallback(
(outputs: ReplOutput[]) => {
if (terminalInstanceRef.current) {
writeOutput(terminalInstanceRef.current, outputs, true);
writeOutput(
terminalInstanceRef.current,
outputs,
true,
returnPrefix,
language
);
// 出力が終わったらプロンプトを表示
updateBuffer(() => [""]);
}
},
[updateBuffer, terminalInstanceRef]
[updateBuffer, terminalInstanceRef, returnPrefix, language]
);

const keyHandler = useCallback(
Expand All @@ -184,17 +198,10 @@ export function ReplTerminal({
}
} else if (code === 13) {
// Enter
const hasContent =
inputBuffer.current[inputBuffer.current.length - 1].trim()
.length > 0;
const status = checkSyntax
? await checkSyntax(inputBuffer.current.join("\n"))
: "complete";
if (
(inputBuffer.current.length === 1 && status === "incomplete") ||
(inputBuffer.current.length >= 2 && hasContent) ||
!isLastChar
) {
if (status === "incomplete" || !isLastChar) {
// 次の行に続く
updateBuffer(() => [...inputBuffer.current, ""]);
} else {
Expand Down
7 changes: 5 additions & 2 deletions app/terminal/runtime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface LangConstants {
tabSize: number;
prompt?: string;
promptMore?: string;
returnPrefix?: string;
}
export type RuntimeLang = "python" | "ruby" | "cpp" | "javascript";

Expand Down Expand Up @@ -99,8 +100,10 @@ export function langConstants(lang: RuntimeLang | AceLang): LangConstants {
case "ruby":
return {
tabSize: 2,
prompt: ">> ",
promptMore: "?> ",
// TODO: 実際のirbのプロンプトは静的でなく、(main)や番号などの動的な表示がある
prompt: "irb> ",
promptMore: "irb* ",
returnPrefix: "=> ",
};
case "javascript":
return {
Expand Down
25 changes: 19 additions & 6 deletions app/terminal/worker/ruby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,26 @@ export function useRuby() {
function splitReplExamples(content: string): ReplCommand[] {
const initCommands: { command: string; output: ReplOutput[] }[] = [];
for (const line of content.split("\n")) {
if (line.startsWith(">> ")) {
// Ruby IRB uses >> as the prompt
initCommands.push({ command: line.slice(3), output: [] });
} else if (line.startsWith("?> ")) {
// Ruby IRB uses ?> for continuation
if (line.startsWith("irb")) {
if (
initCommands.length > 0 &&
initCommands[initCommands.length - 1].output.length === 0
) {
// 前のコマンド行と連続している場合つなげる
initCommands[initCommands.length - 1].command +=
"\n" + line.slice(line.indexOf(" ") + 1);
} else {
initCommands.push({
command: line.slice(line.indexOf(" ") + 1),
output: [],
});
}
} else if (line.startsWith("=> ")) {
if (initCommands.length > 0) {
initCommands[initCommands.length - 1].command += "\n" + line.slice(3);
initCommands[initCommands.length - 1].output.push({
type: "return",
message: line.slice(3),
});
}
} else {
// Lines without prompt are output from the previous command
Expand Down
17 changes: 16 additions & 1 deletion public/docs/python-1.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# 第1章: Pythonへようこそ:環境構築と基本
# 第1章: Pythonへようこそ:環境構築と基本思想

プログラミング経験者であっても、言語ごとのツールや流儀を最初に理解することは重要です。この章では、Pythonの開発環境を整え、基本的なツールの使い方を学びます。

## Pythonの思想と特徴: 「読みやすさ」は最優先

他の言語(Java, C++, PHPなど)と比較したとき、Pythonが最も重視するのは**コードの可読性(Readability)**です。

* **シンプルな文法:** C言語やJavaのような `{}`(波括弧)によるブロックや、行末の `;`(セミコロン)を必要としません。
* **強制的なインデント:** Pythonは、**インデント(字下げ)**そのものでコードブロックを表現します。これは構文的なルールであり、オプションではありません。これにより、誰が書いても(ある程度)同じような見た目のコードになり、可読性が劇的に向上します。
* **動的型付け (Dynamic Typing):** JavaやC++のように `int num = 10;` と変数の型を明示的に宣言する必要がありません。`num = 10` と書けば、Pythonが実行時に自動的に型を推論します。(これはJavaScriptやPHPと似ていますが、Pythonは型付けがより厳格(Strong Typing)で、例えば文字列と数値を暗黙的に連結しようとするとエラーになります)
* **豊富な標準ライブラリ**: 「Batteries Included(バッテリー同梱)」という思想のもと、OS操作、ネットワーク、データ処理、JSON、正規表現など、多くの機能が最初から標準ライブラリとして提供されています。

**💡 The Zen of Python (Pythonの禅)** Pythonの設計思想は、`import this` というコマンドでいつでも確認できます。

* Beautiful is better than ugly. (醜いより美しいほうがいい)
* Explicit is better than implicit. (暗黙的より明示的なほうがいい)
* Simple is better than complex. (複雑であるよりシンプルなほうがいい)

## Pythonのインストール方法

手元の環境で本格的に開発を進めるために、Pythonのインストール方法を紹介します。
Expand Down
147 changes: 147 additions & 0 deletions public/docs/ruby-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# 第1章: Rubyの世界へようこそ - 環境構築と第一歩

Rubyへようこそ! 他の言語でのプログラミング経験をお持ちのあなたなら、Rubyの持つ柔軟性と「開発者の楽しさ」をすぐに感じ取れるはずです。この章では、Rubyの基本的な哲学に触れ、あなたのマシンに開発環境をセットアップし、最初のコードを実行します。

## Rubyの哲学と特徴

Rubyは、まつもとゆきひろ(Matz)氏によって開発された、**シンプルさ**と**生産性**を重視した動的オブジェクト指向言語です。

* **すべてがオブジェクト (Everything is an Object)**
JavaやPythonでは`int`や`float`などのプリミティブ型がオブジェクトとは別に存在しますが、Rubyでは**すべてがメソッドを持つオブジェクト**です。`5`のような数値や`"hello"`のような文字列はもちろん、`nil`(nullに相当)や`true`/`false`さえもオブジェクトです。

```ruby-repl:1
irb(main):001> 5.class
=> Integer
irb(main):002> "hello".upcase
=> "HELLO"
irb(main):003> nil.nil?
=> true
```

* **開発者を楽しませる (MINASWAN)**
Rubyの設計思想の核は、プログラマがストレスなく、楽しくコーディングできることを最適化する点にあります。これはしばしば「**MINASWAN** (Matz Is Nice And So We Are Nice)」というコミュニティの標語にも表れています。言語仕様が厳格さよりも「驚き最小の原則」や表現力を優先することがあります。

* **柔軟性と表現力**
Rubyは非常に柔軟で、既存のクラスを後から変更する(オープンクラス)ことや、コードによってコードを操作するメタプログラミングが容易です。これにより、Ruby on Railsのような強力なフレームワークや、RSpecのようなDSL(ドメイン固有言語)が生まれています。


## 他言語との簡単な比較

あなたの経験言語とRubyを少し比べてみましょう。

* **Pythonistaへ**:

* インデントは構文的な意味を持ちません(単なる可読性のため)。
* ブロック(コードのまとまり)は`:`とインデントではなく、`do...end`キーワード、または`{...}`(ブレース)で定義します。
* メソッド呼び出しの丸括弧`()`は、曖昧さがなければ省略可能です。`print "hello"`のように書けます。

* **Java/C\# Developerへ**:

* Rubyは静的型付けではなく**動的型付け**です。変数の型宣言(`int i = 10`)は不要で、`i = 10`と書くだけです。
* コンパイルステップは不要です。スクリプトとして直接実行されます。
* `public`, `private`, `protected`のアクセス修飾子はありますが、Javaなどとは少し動作が異なります(特に`private`)。

* **JavaScript Developerへ**:

* Rubyもクラスベースのオブジェクト指向です(ただし、内部的にはJSのプロトタイプチェーンに似た特異クラスの仕組みも持ちます)。
* `this`の代わりに`self`を使いますが、`self`のコンテキストはJSの`this`ほど複雑に変化せず、より直感的です。
* `true`, `false`, `nil` 以外はすべて「Truthy(真)」として扱われます(`0`や空文字列`""`も真です)。

## 環境構築:バージョン管理ツールの導入

システム(OS)に最初から入っているRubyを直接使うのではなく、**バージョン管理ツール**(`rbenv`や`RVM`)を導入することを強く推奨します。

プロジェクトごとに異なるRubyバージョン(例: 2.7.x と 3.3.x)を簡単に切り替えることができ、システムをクリーンに保てます。

ここでは`rbenv`を使った一般的な流れを紹介します。(詳細なインストール手順はOSによって異なりますので、`rbenv`のGitHubリポジトリなどを参照してください。)

1. **rbenv と ruby-build のインストール**:
Homebrew (macOS) や apt/yum (Linux) など、お使いのパッケージマネージャでインストールします。

2. **Rubyのインストール**:

```bash
# インストール可能なバージョン一覧を確認
$ rbenv install -l

# 最新の安定版(例: 3.3.0)をインストール
$ rbenv install 3.3.0
```

3. **使用するバージョンの設定**:

```bash
# このPCでのデフォルトバージョンとして設定
$ rbenv global 3.3.0

# バージョンが切り替わったか確認
$ ruby -v
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]
```

## 対話型シェル(irb)の活用

Rubyのインストールが完了したら、`irb` (Interactive Ruby) を起動してみましょう。これはRubyのREPL (Read-Eval-Print Loop) で、コード片を試したり、ドキュメント代わりに使ったりするのに非常に便利です。

ターミナルで`irb`と入力することで起動できます。

このウェブサイトではドキュメント内にRubyの実行環境を埋め込んでいます。
以下のように青枠で囲われたコード例には自由にRubyコードを書いて試すことができます。

```ruby-repl:2
irb(main):001> 10 * (5 + 3)
=> 80
irb(main):002> "Ruby".length
=> 4
irb(main):003> 3.times { puts "Welcome!" }
Welcome!
Welcome!
Welcome!
=> 3
```

`=>` の右側に表示されているのが、式の**評価結果(返り値)**です。

`3.times`の例に注目してください。`puts "Welcome!"`が3回実行(出力)されていますが、`=> 3`と表示されています。これは、`3.times`というメソッド自体の返り値が`3`(レシーバである数値)であることを示しています。Rubyではすべての式が値を返すことを覚えておいてください。

## "Hello, World\!" とスクリプトの実行

最後に、定番の "Hello, World\!" を2通りの方法で実行してみましょう。

### irbでの実行

`puts`("put string")は、引数を標準出力(ターミナル)に出力し、最後に改行を追加するメソッドです。

```ruby-repl:3
irb(main):001> puts "Hello, World!"
Hello, World!
=> nil
```

(`puts`メソッド自体の返り値は、常に`nil`です。)

### スクリプトファイルでの実行

エディタで`hello.rb`という名前のファイルを作成します。

```ruby:hello.rb
#!/usr/bin/env ruby
# 1行目はShebang(シーバン)と言い、Unix系OSでスクリプトとして直接実行する際に使われます。

# 変数に文字列を代入
message = "Hello from script file!"

# 変数の内容を出力
puts message
```

このファイルを実行するには、ターミナルで`ruby`コマンドの引数にファイル名を渡します。

このウェブサイト上では以下のように実行ボタンをクリックするとスクリプトの実行結果が表示されます。上の hello.rb のコードを変更して再度実行すると結果も変わるはずです。試してみてください。

```ruby-exec:hello.rb
Hello from script file!
```

おめでとうございます! これでRubyの世界への第一歩を踏み出しました。
次の章では、Rubyの基本的な構文、データ型、そして他の言語にはない特徴的な「シンボル」について詳しく学んでいきます。
Loading