[Rustのデザインパターン 学習] Interpreter [振る舞い]

2023/02/26に公開

https://rust-unofficial.github.io/patterns/patterns/behavioural/interpreter.html

はじめに

  • 2月は隔週投稿となり7記事目です。

  • プログラミングRust 第2版

    • 上記書籍をp299~585まで読み進めることができました。
      範囲
      • クロージャ
      • イテレータ
      • コレクション
      • 文字列とテキスト
      • 入出力
      • 並列性
      • 非同期プログラミング
      • マクロ
      • unsafeなコード

      太字の章を読み理解できた内容が、今回作成したコードの各所で活用されています

内容

mermaid記法で書いたガントチャートについて、記載した期日を過ぎていればdoneに書き換えるコードを作成しました

rustのコード
main.rs
use regex::Regex;
use chrono::*;

pub struct Consts {}

/// Mermaid記法に関連する文字列定数
/// MEMO:型関連定数の定義 
impl <'a>Consts {
	//FIXME:zennに公開する際、コードブロックが含まれると記事内でシンタックスハイライトが崩れる
    const SAMPLE_MD: &'a str = "";    
    const MERMAID_BLOCK_BEGIN:&'a str = "```mermaid";
    const CHART_TYPE:&'a str = "gantt";    
}

pub enum EDiagram {
    Flowchart,
    Gantt,
    //TODO:
    //And more...
}

pub struct Interpreter<'a> {
    it: std::str::Chars<'a>,
}

/// Mermaid記法の各ダイアグラムに対応する実装を行う
/// HACK:コメントに書くのではなく型パラメーターつけたほうが自明
impl<'a> Interpreter <'a> {
    pub fn new(infix: &'a str) -> Self {
        Self { it: infix.chars() }
    }

    fn next_char(&mut self) -> Option<char> {
        self.it.next()
    }

    /// Mainシーケンス
    pub fn interpret<T>(&mut self, out: &mut String, diagram_type:EDiagram) {    
        
        // mermaidコードブロックがあるか?
        while !self.check_chars(out, Consts::MERMAID_BLOCK_BEGIN){};       
        
        match diagram_type {
            EDiagram::Gantt => {
                while !self.check_chars(out, Consts::CHART_TYPE){};        
                self.parse_gantt_task(out);
            },
            _ => todo!()
        }
    }

    /// ganttの各タスクを読み取り
    fn parse_gantt_task(&mut self, out:&mut String){
        while let Some(op) = self.next_char() {
            out.push(op);

            if op == ':' {
                let mut task = String::new();
                while let Some(c) = self.next_char(){
                    match c {
                        '\n' => {
                            let task_update = Self::modify_task_status(&task);
                            out.push_str(&task_update);
                            out.push(c);
                            break;
                        },
                        _ => task.push(c),
                    }
                }
            }
        }
    }

    /// タスクの期日を過ぎていれば`done`に変更
    fn modify_task_status(task:&String) -> String{
        let task_split = task.split(",");
        let mut task_update = String::new();
        

        //TODO:dateFormatから正規表現パターンを取得
        let re = Regex::new(r"\d{4}-\d{2}-\d{2}").unwrap();
        
        let today = Utc::now().date_naive();

        for task in task_split{
            task_update.push_str(&format!("{},",task));
            if re.is_match(task) {
                let ymd:Vec<i32> = task.split('-').collect::<Vec<&str>>().iter()
                    .map(|t| t.parse::<i32>().unwrap()).collect();
                let date = NaiveDate::from_ymd_opt(ymd[0], ymd[1].try_into().unwrap(), ymd[2].try_into().unwrap()).unwrap();
                if date <= today {
                    if !task_update.contains("done"){
                        if task_update.contains("active"){
                            task_update = task_update.replace("active", "done");
                        } else {
                            task_update.insert_str(0, "done,");
                        }
                    }
                } 
            } 
        }        
        //最後の,を削除
        task_update.pop();
        task_update
    }

    /// 任意の文字列:`cs`が含まれるか一字ずつ走査
    fn check_chars(&mut self, out:&mut String, cs:&str) -> bool {
        for c in cs.chars(){
            loop {
                match self.next_char() {
                    Some(op) =>{
                        out.push(op);
                        if op == c {
                            break;
                        } else {
                            return false;
                        }
                    },
                    None => panic!("{} Not found",cs)
                }
            } 
        }
        return true;
    }
}


/// Mermaid記法で書かれた各ダイアグラムを任意に書き換える
fn main (){ 
    let mut intr = Interpreter::new(Consts::SAMPLE_MD);
    let mut parsed = String::new();
    intr.interpret::<EDiagram>(&mut parsed, EDiagram::Gantt);
    println!("{parsed}");
}

Cargo.toml
[package]
name = "demo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
regex = "*"
chrono = "*"

動作結果

記述したガントチャート文字列

```mermaid
gantt
title 2月の進捗
dateFormat YYYY-MM-DD
section Command
記事投稿:done, milestone,2023-02-12,1d
section Interpreter
記事投稿:active, milestone,2023-02-26,1d
```

Discussion