オフラインリアルタイムどう書く E29 の実装例( ruby, rust )

問題の名前 : アンエスケープ
問題 : http://nabetani.sakura.ne.jp/hena/orde29unes/
実装リンク集 : https://qiita.com/Nabetani/items/f2db9b916c0a301b744f

次回のイベントは  2/2
see https://yhpg.doorkeeper.jp/events/84247 。

で。

最初に書いた、ruby による実装。

```ruby:ruby2.5
require "json"
require "strscan"

def do_quote( q, r, ch, mode )
  case ch
  when q
    :normal
  else
    r.last << ch
    mode
  end
end

def do_slash( r, ch )
  case ch
  when "/"
    r.last << ch
    :normal
  when "'"
    r.push ""
    :sq
  when '"'
    r.push ""
    :dq
  else
    r.push ""
    r.last << ch
    :normal
  end
end

def do_normal( r, ch )
  case ch
  when "/"
    :slash
  when "'"
    :sq
  when '"'
    :dq
  else
    r.last << ch
    :normal
  end
end

def unescape(s)
  ss = StringScanner.new(s)
  r=[""]
  mode = :normal
  while ! ss.eos?
    ch=ss.getch
    mode = case mode
    when :sq
      do_quote("'", r, ch, mode)
    when :dq
      do_quote('"', r, ch, mode)
    when :slash
      do_slash( r, ch )
    when :normal
      do_normal( r, ch )
    end
  end
  return nil unless mode==:normal
  r
end

def solve(src)
  r = unescape(src)
  return "-" if !r || r.empty? || r.any?{ |x| x.empty? }
  r.join(",")
end

if $0==__FILE__
  data = JSON.parse(DATA.read, symbolize_names: true)
  data[:test_data].map{ | number:, src:, expected: |
    actual = solve( src )
    okay = actual == expected
    puts [ number, (okay ? "ok" : "**NG**"), actual, expected ].join(" ")
    okay
  }.all?.yield_self{ |x| puts( x ? "okay" : "SOMETHING WRONG" ) }
end

__END__
{"event_id":"E29","event_url":"https://yhpg.doorkeeper.jp/events/82699","test_data":[
  {"number":0,"src":"foo/bar/baz","expected":"foo,bar,baz"},
  {"number":1,"src":"/foo/bar/baz'/","expected":"-"},
  {"number":2,"src":"\"","expected":"-"},
  {"number":3,"src":"'","expected":"-"},
  {"number":63,"src":"Foo/Bar/\"Hoge'/'Fuga\"","expected":"Foo,Bar,Hoge'/'Fuga"}]}
```

最初に書いたのは、状態遷移させつつ一文字ずつ処理するというわりと順当な感じの実装。
先読みとか一文字戻すとか、そういう難しいことはしていない。

問題を作っていて思ったのは、「`//`」が面白いなというところ。
しかし。ルールが簡単すぎて [正規表現で解けてしまう]( https://twitter.com/angel_p_57/status/1071407484076666880 ) というミス(?)を犯してしまった。まあ仕方ない。

それはさておき。
上記の ruby 版を rust に移植したのが下記：

```rust:e29.rs
pub mod unescape {
    enum Mode {
        Normal,
        Sq,
        Dq,
        Slash,
    }

    fn push(v: &mut Vec<String>, ch: char) {
        let lastpos = v.len() - 1;
        v[lastpos].push(ch);
    }

    fn do_normal(r: &mut Vec<String>, ch: char) -> Mode {
        match ch {
            '/' => Mode::Slash,
            '\'' => Mode::Sq,
            '"' => Mode::Dq,
            _ => {
                push(r, ch);
                Mode::Normal
            }
        }
    }

    fn do_quote(r: &mut Vec<String>, ch: char, q: char, mode: Mode) -> Mode {
        if ch == q {
            Mode::Normal
        } else {
            push(r, ch);
            mode
        }
    }

    fn do_slash(r: &mut Vec<String>, ch: char) -> Mode {
        match ch {
            '/' => {
                push(r, ch);
                Mode::Normal
            }
            '\'' => {
                r.push("".to_string());
                Mode::Sq
            }
            '"' => {
                r.push("".to_string());
                Mode::Dq
            }
            _ => {
                r.push(ch.to_string());
                Mode::Normal
            }
        }
    }

    fn solve_impl(src: String) -> Option<Vec<String>> {
        let mut r = vec!["".to_string()];
        let mut pos = 0usize;
        let mut mode = Mode::Normal;
        while pos < src.len() {
            let ch = src.chars().nth(pos).unwrap();
            mode = match mode {
                Mode::Normal => do_normal(&mut r, ch),
                Mode::Sq => do_quote(&mut r, ch, '\'', Mode::Sq),
                Mode::Dq => do_quote(&mut r, ch, '"', Mode::Dq),
                Mode::Slash => do_slash(&mut r, ch),
            };
            pos += 1;
        }
        if r.iter().any(|x| x.len() == 0) {
            return None;
        }
        match mode {
            Mode::Normal => Some(r),
            _ => None,
        }
    }

    pub fn solve(src: String) -> String {
        let r = solve_impl(src);
        match r {
            Some(x) => x.join(","),
            _ => "-".to_string(),
        }
    }
}
```

```rust:lib.rs
pub mod e29;

#[cfg(test)]
mod tests {
    use e29::unescape;
    macro_rules! test {
        ($n:expr, $src:expr, $expected:expr) => {
            eprintln!("{} {} {}", $n, $src, $expected);
            let actual = unescape::solve($src.to_string());
            assert_eq!(actual, $expected);
        };
    }

    #[test]
    fn it_works() {
        test!(0, "foo/bar/baz", "foo,bar,baz");
        test!(1, "/foo/bar/baz'/", "-");
        test!(2, "\"", "-");
        // 中略
        test!(63, "Foo/Bar/\"Hoge'/'Fuga\"", "Foo,Bar,Hoge'/'Fuga");
    }
}
```

ruby と同じぐらいの長さで書けていて、いい感じだと思う。
`while pos < src.len(){ /* 略 */ }`のループはもう少しいい書き方ができるかもしれない。

当日はダブルクオート一文字を `'\"'` と無駄にエスケープしていたんだけど、Qiita のシンタックスハイライトが壊れるので `'"'` に修正した。lib.rs の方は治せなかったので壊れたままだけど。