HTML をトークナイズする [Lua]
ちょっと必要があって Lua で書いたコード。標準入力から HTML を受け取ってタグとテキストの部分に分けたトークンのストリームにする。前半の string buffer と makeiter は基本的に Programming in Lua に出てくるのをアレンジしたものです。
-- a (singleton) string buffer implementation
buf = {
_buf = {},
clear = function (self) self._buf = {} return self end,
content = function (self) return table.concat(self._buf) end,
append = function (self, s) self._buf[#(self._buf) + 1] = s; return self end,
set = function (self, s) self._buf = {s}; return self end,
}
-- iterator factory
function makeiter (f)
local co = coroutine.create(f)
return function ()
local code, res = coroutine.resume(co)
return res
end
end
function Tag (s) return {type = "Tag", value = s} end
function Text (s) return {type = "Text", value = s} end
-- text mode
function text ()
local c = io.read(1)
if c == "<" then
if buf:content() ~= "" then coroutine.yield(Text(buf:content())) end
buf:set(c)
return tag()
elseif c then
buf:append(c)
return text()
else
if buf:content() ~= "" then coroutine.yield(Text(buf:content())) end
end
end
-- tag mode
function tag ()
local c = io.read(1)
if c == ">" then
coroutine.yield(Tag(buf:append(c):content()))
buf:clear()
return text()
elseif c then
buf:append(c)
return tag()
else
if buf:content() ~= "" then coroutine.yield(Tag(buf:content())) end
end
end
--[[
for i in makeiter(text) do
print(i.type .. ": " .. string.gsub(i.value, "\n", " "))
end
--]]
どうということもないコードですが、末尾呼び出しが出てきたりコルーチンを使っていたりするので、両方を同じように書けるスクリプト言語は意外に限定されるかもしれません。ちなみに Lua で末尾呼び出しになっているかどうかを調べるには luac -l でバイトコードのアセンブリ出力を見ると TAILCALL というそのものずばりのオペコードが出てくるので大変分かりやすいです。
ここでは基本的には延々と末尾呼び出しをして(つまりループをして)トークンになったところで yield しているので(さらに makeiter でイテレータ化しているので)ブロックコメント内のような使い方が出来ます。使うと例えばこんな結果を出力します。
Tag: <html> Text: Tag: <body> Text: Tag: <p> Text: blah blah Tag: </p> ...
これだと開始タグと終了タグの区別が付かないので不便ですが、そういう場合でなおかつ元のコードに手をつけたくない場合でも以下のようなラッパーを書けば同じように使うことが出来ます。
function startend ()
for i in makeiter(text) do
if i.type == "Tag" then
coroutine.yield(
string.find(i.value, "^</")
and {type = "End", value = i.value}
or {type = "Start", value = i.value}
)
else
coroutine.yield(i)
end
end
end
--[[
for i in makeiter(startend) do
print(i.type .. ": " .. string.gsub(i.value, "\n", " "))
end
--]]
コルーチンって便利という話でした。
コメント 0