SSブログ

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
--]]

コルーチンって便利という話でした。


nice!(0)  コメント(0)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。