From 4681e861efcdf9b0b73e3803e70712bc55162863 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 8 Aug 2026 10:38:18 +0900 Subject: feat(slides): improve slide controller --- .../trick-2025-most-ruby-on-ruby-award/index.html | 153 +++++++++++---------- 1 file changed, 80 insertions(+), 73 deletions(-) (limited to 'services/nuldoc/public/blog/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award') diff --git a/services/nuldoc/public/blog/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award/index.html b/services/nuldoc/public/blog/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award/index.html index 1e161d0f..e1948dc4 100644 --- a/services/nuldoc/public/blog/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award/index.html +++ b/services/nuldoc/public/blog/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award/index.html @@ -15,7 +15,7 @@ RubyKaigi 2025 の TRICK で入賞した|REPL: Rest-Eat-Program Loop - +
@@ -161,7 +161,8 @@ 表示している。つまり、Ruby プログラムにルビを振った作品である。例えば、先頭の2行目の require は次のような HTML で構成されている。

-
<ruby class="IDENTIFIER">require<rp class="">(</rp><rt class="">リクワイア</rt><rp class="">)</rp></ruby>
+
<ruby class="IDENTIFIER">require<rp class="">(</rp><rt class="">リクワイア</rt><rp class="">)</rp></ruby>
+

順に使ったテクニックを解説していく。 @@ -172,11 +173,12 @@ 改めて quine について説明する。Quine とは、自身のソースコードを出力するようなプログラムである。Ruby では様々な方法で quine を書くことができるが、この作品で使っている基本形は以下のようなものである。

-
eval $s=<<'EOS' -
print "eval $s=<<'EOS'\n" -
print $s -
print "EOS\n" -
EOS
+
eval $s=<<'EOS'
+print "eval $s=<<'EOS'\n"
+print $s
+print "EOS\n"
+EOS
+

変数 $s に 2 行目、3 行目、4 行目が入っており、それに加えて 1 行目と 5 行目を出力すれば元のソースコードが得られる。実際には $s を加工してシンタックスハイライトや振り仮名を振ることになる。 @@ -191,23 +193,24 @@ トークナイズには Ruby 3.4 からデフォルトのパーサになった Prism を利用している。Prism.lex() を使うとトークナイズができるので、トークンに付いているソースコード位置の情報を使いつつ元のソースコードを復元する。

-
y = 1 # 現在の行 -
x = 0 # 現在の列 -
Prism.lex($s).value[..-2].each {|t, *| -
l = t.location -
r = l.start_line # トークンの開始行 -
if y < r # 改行が必要なら -
p "\n" * (r - y) # 改行を挿入して -
x = 0 # 列の先頭へ戻る -
end -
c = l.start_column # トークンの開始列 -
if x < c # 空白が必要なら -
p " " * (c - x) # 空白を挿入 -
end -
p ruby(t) # トークン本体を出力 -
y = l.end_line # 現在行を更新 -
x = l.end_column # 現在列を更新 -
}
+
y = 1                 # 現在の行
+x = 0                 # 現在の列
+Prism.lex($s).value[..-2].each {|t, *|
+  l = t.location
+  r = l.start_line    # トークンの開始行
+  if y < r            # 改行が必要なら
+    p "\n" * (r - y)  #   改行を挿入して
+    x = 0             #   列の先頭へ戻る
+  end
+  c = l.start_column  # トークンの開始列
+  if x < c            # 空白が必要なら
+    p " " * (c - x)   #   空白を挿入
+  end
+  p ruby(t)           # トークン本体を出力
+  y = l.end_line      # 現在行を更新
+  x = l.end_column    # 現在列を更新
+}
+

補足: 変数名がやたら短いのは、このあとの振り仮名データの量を削減するため。 @@ -216,20 +219,21 @@ トークン種別に応じた色付けは CSS でおこなっている。出力する HTML のクラス名に Prism::Token#type を指定しておいて、index.html でそれぞれのクラスにスタイルを当てた。

-
<style> -
/* ... */ -
-
.COMMENT { -
color: #777; -
font-style: italic; -
} -
-
.CONSTANT, .GLOBAL_VARIABLE, .INSTANCE_VARIABLE, .IDENTIFIER { -
color: #088; -
} -
-
/* ... */ -
</style>
+
    <style>
+      /* ... */
+
+      .COMMENT {
+        color: #777;
+        font-style: italic;
+      }
+
+      .CONSTANT, .GLOBAL_VARIABLE, .INSTANCE_VARIABLE, .IDENTIFIER {
+        color: #088;
+      }
+
+      /* ... */
+    </style>
+

トークン種別の列挙にはそれなりに文字数を使ってしまうのだが、今回の TRICK のレギュレーションでは index.html にサイズ制限がなかったので好きに色を付けることができた。 @@ -241,27 +245,28 @@ それぞれの英単語や記号に対応した振り仮名のデータは、プログラム中に埋め込まれている。

-
def rt(t) -
r = { -
:"&&" => "1136", -
:"=" => "04199275", -
:"||" => "623147", -
:$s => "41750825", -
:* => "111775", -
# ... -
type: "310455", -
utf_8: "70923803920853080440", -
value: "48746992", -
x: "08351525", -
y: "7904", -
} -
kana( -
r[:"#{t.type}"] || -
r[s = :"#{t.value.downcase}"] || -
s.end_with?(":") && r[:"#{s[..-2]}"] || -
nil -
) -
end
+
def rt(t)
+  r = {
+    :"&&" => "1136",
+    :"=" => "04199275",
+    :"||" => "623147",
+    :$s => "41750825",
+    :* => "111775",
+    # ...
+    type: "310455",
+    utf_8: "70923803920853080440",
+    value: "48746992",
+    x: "08351525",
+    y: "7904",
+  }
+  kana(
+    r[:"#{t.type}"] ||
+    r[s = :"#{t.value.downcase}"] ||
+    s.end_with?(":") && r[:"#{s[..-2]}"] ||
+    nil
+  )
+end
+

トークンの種類 (t.type) またはトークンの文字列表現そのもの (t.value.downcase) を使ってテーブルを引いて振り仮名へ変換している。このテーブルのキー部分そのものにも振り仮名を振るために、トークンが : で終わっていれば : を取り除いて振り仮名を得ている (例: "value:""value""48746992")。 @@ -270,25 +275,27 @@ このテーブルはサイズ制限を突破するために圧縮されており、kana() 関数で展開される。

-
def kana(s) -
s -
&.scan(/.{2}/) -
&.map{|c| (0x30A0 + c.to_i).chr(Encoding::UTF_8)} -
&.*("") -
end
+
def kana(s)
+  s
+    &.scan(/.{2}/)
+    &.map{|c| (0x30A0 + c.to_i).chr(Encoding::UTF_8)}
+    &.*("")
+end
+

例えば value に対応する振り仮名データ "48746992" であれば、次のような変換を経て振り仮名へと展開される。

-
s -
# => "48746992" -
&.scan(/.{2}/) -
# => ["48", "74", "69", "92"] -
&.map{|c| (0x30A0 + c.to_i).chr(Encoding::UTF_8)} -
# => ["バ", "リ", "ュ", "ー"] -
&.*("") -
# => "バリュー"
+
  s
+    # => "48746992"
+    &.scan(/.{2}/)
+    # => ["48", "74", "69", "92"]
+    &.map{|c| (0x30A0 + c.to_i).chr(Encoding::UTF_8)}
+    # => ["バ", "リ", "ュ", "ー"]
+    &.*("")
+    # => "バリュー"
+

これは後で気付いたのだが、Ruby は多倍長整数が扱えるので "48746992" のようなデータは単に 48746992 と書けばよかった。kana() 関数が多少長くはなるが、振り仮名データの数 x 2 バイト分サイズが減るのでこちらの方が短くなる。サイズ制限の都合で振り仮名を振るのを諦めた記号もあったのでもったいない。 -- cgit v1.3.1