PythonのStringIOをIoで実装してみた。

id:rokujyouhitomaです。

前回はPythonのStringIOをJavaScriptで実装してみた。ので、今回はIo(Iolanguage)で実装してみましょう。

興味有る方はぜひともPythonのStringIOの実装、前回実装したJavaScriptでのStringIOの実装と今回の実装を見比べて下さい。

コード

書いたコードは例のごとくgithubにpushしました。

https://github.com/rokujyouhitoma/io-training/blob/develop/StringIO.io

EINVAL := 22;


_complain_ifclosed := method(closed,
  if(closed == true, Exception raise("I/O operation on closed file"), nil)
)

/**
 * StringIO is based on Python StringIO build-in modules.
 * @extends {Object}
 */
StringIO := Object clone do(

  /**
   * @param {?string} buf .
   * @return {StringIO}
   */
  clone := method(buf,
     self protos clone
     self init
     self buf := if(buf isNil, "", buf)
     self len := if(buf isNil, 0, buf size)
     self
  )

  /**
   * @return {StringIO}
   * @constructor
   */
  init := method(
    self buf := ""
    self len := 0
    self buflist := list()
    self pos := 0
    self closed := false clone
    self softspace := 0
    self
  )

  __iter__ := method(self)

  /**
   * @param {?number=} size .
   */
  next := method(
    _complain_ifclosed(self closed)
    r := self readline
    #TODO: if(r isEmpty, StopIteration raise(""))
    if(r isEmpty, Exception raise(""))
    r
  )

  /**
   * Free the memory buffer.
   */
  close := method(
    if(self closed not) then(
      self closed = true clone
      self removeSlot("buf")
      self removeSlot("pos")
    )
  )

  /**
   * Returns false because StringIO objects are not connected to a tty-like
   * device
   */
  isatty := method(
    _complain_ifclosed(self closed)
    return false
  )

  /**
   * Set the file's current position.
   * 
   * The mode argument is optional and defaults to 0 (absolute file
   * positioning); other values are 1 (seek relative to the current
   * position) and 2 (seek relative to the file's end).
   * There is no return value.
   */
  seek := method(pos, mode,
    if(mode isNil not, mode = 0)
    _complain_ifclosed(self closed)
    if(self buflist size > 0) then(
      self buf = self buf .. self buflist join
      self buflist = list()
    )
    if(mode == 1) then(
      pos = pos + self pos
    ) elseif(mode == 2) then(
      pos = pos + self len
    )
    self pos = list(0, pos) max
    nil
  )

  /**
   * Return the file's current position.
   */
  tell := method(pos, mode,
    if(mode isNil not, mode = 0)
    _complain_ifclosed(self closed)
    self pos
  )

  /**
   *
   */
  read := method(n,
    if(n isNil not, n = -1)
    _complain_ifclosed(self closed)
    if(self buflist size > 0) then(
      self buf = self buf .. self buflist join
      self buflist = list()
    )
    newpos := nil
    if(n isNil or n < 0) then(
      newpos = self len
    ) else(
      newpos = list(self pos + n, self len) min
    )
    r := self buf exSlice(self pos, newpos)
  )

  /**
   * @param {?number=} length .
   * @return {string} .
   */
  readline := method(length,
    _complain_ifclosed(self closed)
    if(self buflist size > 0) then(
      self buf = self buf .. self buflist join
      self buflist = list()
    )
    i := self buf findSeq("\n", self pos)
    if(i isNil, i = 0)
    newpos := nil
    if(i < 0) then(
      newpos = self len
    ) else (
      newpos = i + 1
    )
    if(length isNil not and length > 0) then(
      if(self pos + length < newpos) then(
        newpos = self pos + length
      )
    )
    r := self buf exSlice(self pos, newpos)
    self pos = newpos
    r
  )

  /**
   * @param {number} sizehint .
   */
  readlines := method(sizehint,
    if(sizehint isNil not, sizehint = 0)
    total := 0
    lines := list()
    line := self readline
    while(line isEmpty not,
      lines append(line)
      total = total + line size 
      if(0 < sizehint and sizehint <= total) then(
        break
      )
      line = self readline
    )
    lines
  )

  /**
   * @param {?number=} size .
   */
  truncate := method(size,
    _complain_ifclosed(self closed)
    if(size isNil) then(
      size = self pos
    ) elseif(size < 0) then(
      #TODO: IOError raise(EINVAL, 'Negative size not allowed')
      Exception raise("Negative size not allowed")
    ) elseif(size < self pos) then(
      self pos = size
    )
    self buf = self getvalue exSlice(0, size)
    self len = size
    nil
  )

  /**
   * @param {string} s .
   */
  write := method(s,
    _complain_ifclosed(self closed)
    if(s isEmpty, return nil)
    spos := self pos
    slen := self len
    if(spos == slen,
      self buflist append(s)
      self len = spos + s size
      self pos = spos + s size
      return nil
    )
    if(spos > slen,
      str := ""
      len := spos - slen
      for(i, 0, len -1, str = str .. "\0")
      self buflist append(str)
      slen = spos
    )
    newpos := spos + s size
    if(spos < slen,
      if(self buflist size > 0,
        self buf = self buf .. self buflist join)
        self buflist = list(self buf exSlice(0, spos), s, self buf exSlice(newpos, s size))
        self buf = ""
        if(newpos > slen, slen = newpos),
      self buflist append(s)
      slen = newpos
    )
    self len = slen
    self pos = newpos
    nil
  )

  /**
   * @param {Array} iterable .
   */
  writelines := method(iterable,
    iterable foreach(key, line, self write(line))
    nil
  )

  /**
   *
   */
  flush := method(
    _complain_ifclosed(self closed)
    nil
  )

  /**
   * @return {string} .
   */
  getvalue := method(
    if(self buflist isTrue,
      self buf := self buf .. self buflist join; self buflist := list()
    )
    self buf
  )
)

テストコード

残念ながらテストコードは揃ってないです>

後ほど揃えます。。


まとめ

IoはJavaScriptと同水準の関数(Ioではメッセージ)、構文(for, while, if, 各種オペレータ)が揃ってるように思えます。
Io Languageを書くのには特に困りそうもありませんでした。

めでたしめでたし。


後記

Ioに興味がある方は、PyPyプロジェクトがRPythonで実装したIo処理系もあります。読んでみると楽しいかもしれません。

召喚

ping id:torazukaさん