最初のステップは字句解析です。これは、単なる文字列の連続を、意味のある最小単位である 「トークン(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)),
}
}
}パースした結果を格納するための 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),
}
}