Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14632] ファイルの概要

このコミットは、Go PlaygroundのフロントエンドJavaScriptファイルであるdoc/play/playground.jsを、go-playgroundリポジトリの最新版と同期させることを目的としています。これにより、Go Playgroundの出力表示機能が大幅に強化され、イベントベースの再生、画像出力のサポート、およびエラーハンドリングの改善が図られています。また、「Program exited.」メッセージのスタイルを追加するためのCSS変更も含まれています。

コミット

commit c8ce844d5a8680cc4f22639f7c2eda4ee57cfcdd
Author: Andrew Gerrand <adg@golang.org>
Date:   Thu Dec 13 14:32:03 2012 +1100

    doc/play: sync playground.js with go-playground repo
    
    Also add style for "Program exited." message.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/6934047

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c8ce844d5a8680cc4f22639f7c2eda4ee57cfcdd

元コミット内容

doc/play/playground.jsgo-playgroundリポジトリと同期させます。 また、「Program exited.」メッセージのスタイルを追加します。

変更の背景

Go Playgroundは、Go言語のコードをブラウザ上で記述、実行、共有できる公式のオンラインツールです。このツールは、Go言語の学習、デモンストレーション、および簡単なプロトタイピングに広く利用されています。

このコミットが行われた2012年当時、Go Playgroundの機能は進化の途上にありました。特に、コードの実行結果の表示方法には改善の余地がありました。従来のシンプルなテキスト出力では、時間差で出力されるログや、グラフィカルな出力(画像など)を表現することが困難でした。

この変更の主な背景には、以下の点が挙げられます。

  1. 出力の表現力向上: Go Playgroundで実行されるプログラムの中には、時間経過とともに出力が変化するものや、画像生成を行うものがあります。これらの動的な出力をより忠実に再現するために、イベントベースの出力再生機能と画像表示機能が必要とされました。
  2. go-playgroundリポジトリとの同期: go-playgroundは、Go Playgroundのバックエンドおよびフロントエンドのコアロジックを管理する独立したリポジトリであると考えられます。このリポジトリで開発された新機能や改善を、Go言語の公式リポジトリ内のGo Playground実装に反映させる必要がありました。これにより、機能の一貫性と最新性の維持が図られます。
  3. ユーザーエクスペリエンスの向上: プログラムの終了を示す「Program exited.」のようなメッセージは、ユーザーにとって重要な情報です。これを視覚的に区別しやすくすることで、ユーザーはプログラムのライフサイクルをより明確に把握できるようになります。

これらの背景から、playground.jsの抜本的な改修と、それに伴うスタイルの調整が実施されました。

前提知識の解説

このコミットの変更内容を理解するためには、以下の技術的知識が役立ちます。

  • Go Playground: Go Playgroundは、Go言語の公式ウェブサイト(golang.org)に統合されているオンラインツールです。ユーザーはブラウザ上でGoコードを記述し、サーバーサイドでコンパイル・実行された結果をリアルタイムで受け取ることができます。これは、Go言語の学習者がコードを試したり、開発者がコードスニペットを共有したりするのに非常に便利です。バックエンドでは、サンドボックス化された環境でGoプログラムが実行され、その標準出力や標準エラー出力がフロントエンドに返されます。

  • playground.js: Go Playgroundのフロントエンド(クライアントサイド)のロジックを担うJavaScriptファイルです。ユーザーインターフェースの操作(コードの入力、実行ボタンのクリック)、AJAXリクエストによるバックエンドとの通信、そしてバックエンドから返された実行結果の表示などを担当しています。

  • go-playgroundリポジトリ: コミットメッセージに登場するgo-playgroundリポジトリは、Go Playgroundのサーバーサイドコンポーネントや、より汎用的なクライアントサイドロジックを開発するための独立したプロジェクトであると推測されます。Go言語の公式リポジトリ内のdoc/playディレクトリにあるplayground.jsは、このgo-playgroundリポジトリのコードベースから定期的に同期されることで、最新の機能やバグ修正が取り込まれる運用がなされていたと考えられます。

  • AJAX (Asynchronous JavaScript and XML): ウェブページの一部を非同期で更新するための技術です。Go Playgroundでは、ユーザーが「Run」ボタンをクリックした際に、ページ全体をリロードすることなく、JavaScriptがバックエンドの/compileエンドポイントにコードを送信し、実行結果を非同期で受け取って表示するために利用されています。

  • Base64エンコーディング: バイナリデータをASCII文字列に変換するエンコーディング方式です。画像などのバイナリデータをHTTP経由でテキストとして送信したり、HTMLやCSSに直接埋め込んだりする際によく用いられます。Go Playgroundでは、プログラムが生成した画像をBase64エンコードしてフロントエンドに送信し、JavaScriptでデコードして表示する仕組みが導入されました。

  • ^L (Form Feed): ASCII制御文字の一つで、コードは12 (0x0C) です。プリンターや古い端末では、この文字を受信すると次のページの先頭(または画面のクリア)に移動する動作をします。このコミットでは、Go Playgroundの出力において、この文字が画面クリアの指示として解釈され、それまでの出力を消去して新しい出力の表示を開始するために利用されています。これは、インタラクティブなCLIアプリケーションの出力を模倣する際に有用です。

  • setTimeout: JavaScriptのグローバル関数で、指定したミリ秒数が経過した後に一度だけ関数を実行します。このコミットでは、プログラムの出力イベントに時間差(e.Delay)がある場合に、その遅延をシミュレートして、あたかもリアルタイムで出力されているかのように見せるために使用されています。

技術的詳細

このコミットの技術的詳細は、主にplayground.jsにおける出力処理のアーキテクチャ変更と、それに伴うバックエンドAPIとの連携方法の進化に集約されます。

  1. イベントベースの出力処理 (playback関数): 従来のGo Playgroundは、プログラムの実行結果を単一のテキストブロックとして受け取り、そのまま表示していました。しかし、このコミットでは、バックエンドから返される出力が単なる文字列ではなく、Eventsという構造化されたデータの配列として扱われるようになりました。各Eventオブジェクトは、Message(出力内容)とDelay(次の出力までの遅延時間、ナノ秒単位)というプロパティを持ちます。 新しく導入されたplayback関数は、このEvents配列を順に処理し、e.Delayに基づいてsetTimeoutを呼び出すことで、時間差のある出力をシミュレートします。これにより、Goプログラムがtime.Sleepなどを用いて意図的に遅延させた出力を、フロントエンドでも忠実に再現できるようになりました。 また、Message内に含まれる^L(フォームフィード)文字を検出すると、それまでの出力をクリアし、新しいメッセージを表示する機能も追加されました。これは、ターミナルアプリケーションが画面をクリアする動作を模倣するものです。

  2. 画像出力のサポート: Go Playgroundでグラフィカルな出力を可能にするため、特定の形式の出力が画像として解釈されるようになりました。具体的には、出力メッセージがIMAGE:で始まる場合、その後に続く文字列をBase64エンコードされたPNG画像データとして扱い、<img>タグのsrc属性にdata:image/png;base64,...形式で埋め込むことで、ブラウザ上に直接画像を表示します。これにより、Goの画像処理ライブラリなどを用いて生成された画像を、Go Playground上で視覚的に確認できるようになりました。

  3. APIバージョニングとエラーハンドリングの改善: /compileエンドポイントへのAJAXリクエストにversion: 2というパラメータが追加されました。これは、バックエンドAPIが新しい出力形式(イベントベースの出力や画像出力)をサポートするために、APIのバージョン管理を導入したことを示唆しています。 エラーハンドリングも改善され、従来のdata.compile_errorsdata.outputといったプロパティから、より汎用的なdata.Errorsdata.Eventsといったプロパティにアクセスするように変更されました。これにより、コンパイルエラーと実行時エラーが統一的にErrorsとして扱われ、通常の出力はEventsとして扱われるようになりました。エラーメッセージ中の行番号情報に基づいて、コードエディタの該当行にエラーを示すスタイル(lineerrorクラス)を適用する機能も引き続きサポートされています。

  4. 「Program exited.」メッセージのスタイル追加: プログラムの実行が完了した際に表示される「Program exited.」というメッセージに対して、doc/style.cssに新しいCSSルールが追加されました。.play .output .exitセレクタに対してcolor: #999;が設定され、このメッセージが薄い灰色で表示されるようになりました。これにより、通常のプログラム出力と区別され、視覚的に終了ステータスが分かりやすくなりました。

これらの変更は、Go Playgroundのインタラクティブ性と表現力を大幅に向上させ、より多様なGoプログラムのデモンストレーションや実行を可能にしました。

コアとなるコードの変更箇所

doc/play/playground.js

--- a/doc/play/playground.js
+++ b/doc/play/playground.js
@@ -83,18 +83,73 @@ function playground(opts) {
 			'<div class="loading">Waiting for remote server...</div>'
 		);
 	}
-	function setOutput(text, error) {
+	var playbackTimeout;
+	function playback(pre, events) {
+		function show(msg) {
+			// ^L clears the screen.
+			var msgs = msg.split("\x0c");
+			if (msgs.length == 1) {
+				pre.text(pre.text() + msg);
+				return;
+			}
+			pre.text(msgs.pop());
+		}
+		function next() {
+			if (events.length == 0) {
+				var exit = $('<span class="exit"/>');
+				exit.text("\nProgram exited.");
+				exit.appendTo(pre);
+				return;
+			}
+			var e = events.shift();
+			if (e.Delay == 0) {
+				show(e.Message);
+				next();
+			} else {
+				playbackTimeout = setTimeout(function() {
+					show(e.Message);
+					next();
+				}, e.Delay / 1000000);
+			}
+		}
+		next();
+	}
+	function stopPlayback() {
+		clearTimeout(playbackTimeout);
+	}
+	function setOutput(events, error) {
+		stopPlayback();
 		output.empty();
 		$(".lineerror").removeClass("lineerror");
-
+
+		// Display errors.
 		if (error) {
 			output.addClass("error");
 			var regex = /prog.go:([0-9]+)/g;
 			var r;
-			while (r = regex.exec(text)) {
+			while (r = regex.exec(error)) {
 				$(".lines div").eq(r[1]-1).addClass("lineerror");
 			}
+			$("<pre/>").text(error).appendTo(output);
+			return;
+		}
+
+		// Display image output.
+		if (events.length > 0 && events[0].Message.indexOf("IMAGE:") == 0) {
+			var out = "";
+			for (var i = 0; i < events.length; i++) {
+				out += events[i].Message;
+			}
+			var url = "data:image/png;base64," + out.substr(6);
+			$("<img/>").attr("src", url).appendTo(output);
+			return;
+		}
+
+		// Play back events.
+		if (events !== null) {
+			var pre = $("<pre/>").appendTo(output);
+			playback(pre, events);
 		}
-		$("<pre/>").text(text).appendTo(output);
 	}
 
 	var pushedEmpty = (window.location.pathname == "/");
@@ -104,7 +159,10 @@ function playground(opts) {
 		loading();
 		seq++;
 		var cur = seq;
-		var data = {"body": body()};
+		var data = {
+			"version": 2,
+			"body": body()
+		};
 		$.ajax("/compile", {
 			data: data,
 			type: "POST",
@@ -116,20 +174,11 @@ function playground(opts) {
 				if (!data) {
 					return;
 				}
-				if (data.compile_errors != "") {
-					setOutput(data.compile_errors, true);
-					return;
-				}
-				var out = ""+data.output;
-				if (out.indexOf("IMAGE:") == 0) {
-					var img = $("<img/>");
-					var url = "data:image/png;base64,";
-					url += out.substr(6)
-					img.attr("src", url);
-					output.empty().append(img);
+				if (data.Errors) {
+					setOutput(null, data.Errors);
 					return;
 				}
-				setOutput(out, false);
+				setOutput(data.Events, false);
 			},
 			error: function() {
 				output.addClass("error").text(
@@ -148,11 +197,11 @@ function playground(opts) {
 			dataType: "json",
 			success: function(data) {
 				if (data.Error) {
-					setOutput(data.Error, true);
+					setOutput(null, data.Error);
 					return;
 				}
 				setBody(data.Body);
-				setOutput("", false);
+				setOutput(null);
 			}
 		});
 	});

doc/style.css

--- a/doc/style.css
+++ b/doc/style.css
@@ -486,6 +486,9 @@ div.play .buttons a {
 	padding: 10px;
 	cursor: pointer;
 }
+div.play .output .exit {
+	color: #999;
+}
 
 /* drop-down playground */
 #playgroundButton,

コアとなるコードの解説

doc/play/playground.js

  1. playback 関数と playbackTimeout:

    • playbackTimeoutは、setTimeoutのIDを保持し、再生を停止する際にclearTimeoutで利用されます。
    • playback(pre, events)関数は、Goプログラムの実行結果をイベント駆動で再生する中核をなす関数です。
    • events配列は、バックエンドから送られてくる出力イベントのリストです。各イベントee.Message(出力文字列)とe.Delay(次のイベントまでの遅延時間、ナノ秒)を持ちます。
    • show(msg)ヘルパー関数は、実際の出力表示を担当します。特に、\x0c(フォームフィード、^L)文字を検出すると、それまでの出力をクリアし、新しいメッセージのみを表示します。これは、ターミナル画面のクリアを模倣した動作です。
    • next()関数は、events配列から次のイベントを取り出し、e.Delayが0であれば即座にshowを呼び出し、そうでなければsetTimeoutを使って指定された遅延後にshownextを再帰的に呼び出します。
    • events配列が空になると、Program exited.というメッセージが追加されます。
  2. stopPlayback 関数:

    • playbackTimeoutに設定されたsetTimeoutをクリアし、現在進行中の出力再生を停止します。これは、新しいコンパイルが開始された際などに、古い出力を中断するために使用されます。
  3. setOutput 関数の変更:

    • 引数がtext, errorからevents, errorに変更されました。これにより、単一のテキストではなく、イベントの配列を処理するようになりました。
    • 関数が呼び出されると、まずstopPlayback()が実行され、既存の出力再生が停止されます。
    • エラー表示: error引数が存在する場合、従来通りエラーメッセージを表示し、行番号に基づいてコードエディタの該当行にlineerrorクラスを適用します。
    • 画像出力: events配列の最初のメッセージがIMAGE:で始まる場合、すべてのイベントメッセージを連結し、IMAGE:プレフィックスを除去した残りの部分をBase64エンコードされた画像データとして解釈します。このデータを<img>タグのsrc属性に設定し、画像として表示します。
    • イベント再生: エラーでも画像でもない場合、eventsnullでなければ、<pre>タグを作成し、その中にplayback関数を呼び出してイベントの再生を開始します。
  4. /compile AJAXリクエストの変更:

    • 送信するデータに"version": 2が追加されました。これは、バックエンドAPIが新しい出力形式をサポートしていることを示すためのバージョン指定です。
    • 成功時のコールバックで、従来のdata.compile_errorsdata.outputではなく、data.Errorsdata.Eventsという新しいプロパティをチェックするように変更されました。これにより、エラーと通常の出力がより構造化された形で扱われるようになりました。
  5. /share AJAXリクエストの変更:

    • 成功時のコールバックで、エラー処理がdata.ErrorsetOutput(null, data.Error)に渡すように変更されました。
    • 成功してコードが設定された後、出力エリアをクリアするためにsetOutput(null)が呼び出されるようになりました。これは、共有されたコードを読み込んだ際に、以前の実行結果をクリアする意図があると考えられます。

doc/style.css

  1. .play .output .exit スタイルの追加:
    • Go Playgroundの出力エリア(.play .output)内で、exitクラスを持つ要素(playback関数によって追加される「Program exited.」メッセージの<span>タグ)に対して、color: #999;(薄い灰色)が適用されるようになりました。これにより、プログラムの終了メッセージが他の出力と区別され、視覚的に分かりやすくなりました。

これらの変更により、Go Playgroundはよりリッチでインタラクティブな出力表示が可能になり、ユーザーエクスペリエンスが向上しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • JavaScriptのsetTimeoutおよびclearTimeoutに関するMDN Web Docs
  • Base64エンコーディングに関する一般的な情報
  • ASCII制御文字(特にフォームフィード^L)に関する情報
  • jQueryのAJAXおよびDOM操作に関するドキュメント