Skip to content

Instantly share code, notes, and snippets.

@udzura
Created April 20, 2026 12:25
Show Gist options
  • Select an option

  • Save udzura/b0ad405eeb3f799752f5ce9509aa3c64 to your computer and use it in GitHub Desktop.

Select an option

Save udzura/b0ad405eeb3f799752f5ce9509aa3c64 to your computer and use it in GitHub Desktop.
Rustの練習: JSONパーサを自作してみましょう

字句解析(Lexer)とトークンの設計

最初のステップは字句解析です。これは、単なる文字列の連続を、意味のある最小単位である 「トークン(Token)」 に分割する作業です。

例えば、 {"age": 20} という文字列は、以下のトークンに分解されます。

{ (左波括弧)

"age" (文字列)

: (コロン)

20 (数値)

} (右波括弧)

トークンの定義

src/main.rs にトークンを表す enum を定義しましょう。

#[derive(Debug, PartialEq, Clone)]
pub enum Token {
    Null,
    True,
    False,
    Number(f64),
    String(String),
    LeftBrace,    // {
    RightBrace,   // }
    LeftBracket,  // [
    RightBracket, // ]
    Comma,        // ,
    Colon,        // :
}

トークナイザーの実装

#[derive(Debug, PartialEq, Clone)]
pub enum Token {
    Null,
    True,
    False,
    Number(f64),
    String(String),
    LeftBrace,    // {
    RightBrace,   // }
    LeftBracket,  // [
    RightBracket, // ]
    Comma,        // ,
    Colon,        // :
}

pub struct Lexer {
    // 文字列の参照ではなく、文字の配列としてデータを「所有」する
    chars: Vec<char>,
    pos: usize,
}

impl Lexer {
    pub fn new(input: &str) -> Self {
        Lexer {
            // 入力文字列を char の Vec に変換して保存
            chars: input.chars().collect(),
            pos: 0,
        }
    }

    // 現在の文字を覗き見する(進めない)
    fn peek(&self) -> Option<char> {
        if self.pos < self.chars.len() {
            Some(self.chars[self.pos])
        } else {
            None
        }
    }

    // 現在の文字を取得し、次の文字へ進む
    fn advance(&mut self) -> Option<char> {
        if self.pos < self.chars.len() {
            let c = self.chars[self.pos];
            self.pos += 1;
            Some(c)
        } else {
            None
        }
    }

    pub fn tokenize(&mut self) -> Result<Vec<Token>, String> {
        let mut tokens = Vec::new();

        while let Some(c) = self.peek() {
            match c {
                // 空白文字のスキップ
                ' ' | '\n' | '\r' | '\t' => { self.advance(); }
                // 記号の処理
                '{' => { tokens.push(Token::LeftBrace); self.advance(); }
                '}' => { tokens.push(Token::RightBrace); self.advance(); }
                '[' => { tokens.push(Token::LeftBracket); self.advance(); }
                ']' => { tokens.push(Token::RightBracket); self.advance(); }
                ':' => { tokens.push(Token::Colon); self.advance(); }
                ',' => { tokens.push(Token::Comma); self.advance(); }
                // 文字列の処理
                '"' => tokens.push(self.read_string()?),
                // 数値の処理
                '0'..='9' | '-' => tokens.push(self.read_number()?),
                // キーワードの処理 (true, false, null)
                't' | 'f' | 'n' => tokens.push(self.read_keyword()?),
                _ => return Err(format!("予期しない文字です: {}", c)),
            }
        }
        Ok(tokens)
    }

    // --- 以下、ヘルパー関数 ---
    fn read_string(&mut self) -> Result<Token, String> {
        self.advance(); // 最初の '"' を消費
        let mut result = String::new();
        while let Some(c) = self.advance() {
            if c == '"' {
                return Ok(Token::String(result));
            }
            result.push(c);
        }
        Err("文字列が閉じられていません".to_string())
    }

    fn read_number(&mut self) -> Result<Token, String> {
        let mut num_str = String::new();
        while let Some(c) = self.peek() {
            // is_ascii_digit() で 0-9 かどうか判定
            if c.is_ascii_digit() || c == '.' || c == '-' {
                num_str.push(c);
                self.advance();
            } else {
                break;
            }
        }
        num_str.parse::<f64>()
            .map(Token::Number)
            .map_err(|_| "数値のパースに失敗しました".to_string())
    }

    fn read_keyword(&mut self) -> Result<Token, String> {
        let mut word = String::new();
        while let Some(c) = self.peek() {
            if c.is_ascii_alphabetic() {
                word.push(c);
                self.advance();
            } else {
                break;
            }
        }
        match word.as_str() {
            "true" => Ok(Token::True),
            "false" => Ok(Token::False),
            "null" => Ok(Token::Null),
            _ => Err(format!("不明なキーワードです: {}", word)),
        }
    }
}

JSON値の表現(AST)

パースした結果を格納するための enum を定義します。オブジェクトは HashMap で、配列は Vec で表現します。

use std::collections::HashMap;

#[derive(Debug, PartialEq)]
pub enum JsonValue {
    Null,
    Boolean(bool),
    Number(f64),
    String(String),
    Array(Vec<JsonValue>),
    Object(HashMap<String, JsonValue>),
}

パーサーの実装

トークンのリストを前から順番に読み込み、再帰的に JsonValue を組み立てます(再帰的下向き構文解析)。

pub struct Parser {
    tokens: Vec<Token>,
    pos: usize,
}

impl Parser {
    pub fn new(tokens: Vec<Token>) -> Self {
        Parser { tokens, pos: 0 }
    }

    fn current(&self) -> Option<&Token> {
        self.tokens.get(self.pos)
    }

    fn advance(&mut self) {
        self.pos += 1;
    }

    pub fn parse(&mut self) -> Result<JsonValue, String> {
        let value = self.parse_value()?;
        if self.pos < self.tokens.len() {
            return Err("JSONの後に余分なトークンがあります".to_string());
        }
        Ok(value)
    }

    fn parse_value(&mut self) -> Result<JsonValue, String> {
        let token = self.current().ok_or("予期しないEOF(入力の終わり)です")?.clone();
        
        match token {
            Token::Null => { self.advance(); Ok(JsonValue::Null) }
            Token::True => { self.advance(); Ok(JsonValue::Boolean(true)) }
            Token::False => { self.advance(); Ok(JsonValue::Boolean(false)) }
            Token::Number(n) => { self.advance(); Ok(JsonValue::Number(n)) }
            Token::String(s) => { self.advance(); Ok(JsonValue::String(s)) }
            Token::LeftBracket => self.parse_array(),
            Token::LeftBrace => self.parse_object(),
            _ => Err(format!("値が期待されていますが、{:?} が来ました", token)),
        }
    }

    fn parse_array(&mut self) -> Result<JsonValue, String> {
        self.advance(); // '[' を消費
        let mut array = Vec::new();

        if let Some(Token::RightBracket) = self.current() {
            self.advance();
            return Ok(JsonValue::Array(array));
        }

        loop {
            array.push(self.parse_value()?);

            match self.current() {
                Some(Token::Comma) => { self.advance(); } // ',' なら次の要素へ
                Some(Token::RightBracket) => {
                    self.advance(); // ']' なら配列終了
                    return Ok(JsonValue::Array(array));
                }
                _ => return Err("配列の要素の区切りにカンマがありません".to_string()),
            }
        }
    }

    fn parse_object(&mut self) -> Result<JsonValue, String> {
        self.advance(); // '{' を消費
        let mut map = HashMap::new();

        if let Some(Token::RightBrace) = self.current() {
            self.advance();
            return Ok(JsonValue::Object(map));
        }

        loop {
            // キーのパース
            let key = match self.current().cloned() {
                Some(Token::String(k)) => { self.advance(); k }
                _ => return Err("オブジェクトのキーは文字列である必要があります".to_string()),
            };

            // コロンのチェック
            match self.current() {
                Some(Token::Colon) => self.advance(),
                _ => return Err("キーの後にコロンが必要です".to_string()),
            }

            // 値のパース
            let value = self.parse_value()?;
            map.insert(key, value);

            // カンマか閉じ波括弧のチェック
            match self.current() {
                Some(Token::Comma) => self.advance(),
                Some(Token::RightBrace) => {
                    self.advance();
                    return Ok(JsonValue::Object(map));
                }
                _ => return Err("オブジェクトの要素の区切りにカンマがありません".to_string()),
            }
        }
    }
}

実行してテストしてみよう!

最後に、これまで作った Lexer と Parser を組み合わせて、実際に JSON 文字列をパースしてみましょう。

fn main() {
    let json_input = r#"
    {
        "name": "Rustacean",
        "age": 10,
        "is_active": true,
        "skills": ["Rust", "Parsing"],
        "metadata": null
    }
    "#;

    println!("--- Input JSON ---");
    println!("{}", json_input);

    // 1. 字句解析
    let mut lexer = Lexer::new(json_input);
    match lexer.tokenize() {
        Ok(tokens) => {
            println!("\n--- Tokens ---");
            println!("{:?}", tokens);

            // 2. 構文解析
            let mut parser = Parser::new(tokens);
            match parser.parse() {
                Ok(ast) => {
                    println!("\n--- Abstract Syntax Tree (AST) ---");
                    println!("{:#?}", ast);
                }
                Err(e) => println!("Parse Error: {}", e),
            }
        }
        Err(e) => println!("Tokenize Error: {}", e),
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment