Teoria de Compiladores & Linguagens Formais
Módulo 05
Parser JSON Descendente Recursivo & AST
Uma implementação artesanal de um interpretador sintático para a especificação RFC 8259 (JSON). Sem recorrer a crates externas como serde, o módulo demonstra as técnicas centrais de engenharia de compiladores: tokenização léxica com rastreamento posicional, recursão mútua e tipos de dados algébricos (ADTs).
01. Arquitetura Clássica: Lexer vs Parser
Interpretar linguagens em uma única passagem misturando caracteres brutos com regras gramaticais resulta em código frágil e de difícil manutenção. Separamos o processo em dois estágios desacoplados:
1. Análise Léxica (Lexer / Tokenizer)
Varre a string de caracteres e agrupa sequências com significado semântico em unidades atômicas chamadas Tokens (LeftBrace, Colon, StringLiteral). Cada token carrega sua coordenada de origem (Span { line, col }).
2. Análise Sintática (Recursive Descent)
Consome o fluxo linear de tokens e valida se a sequência respeita a Gramática Livre de Contexto da linguagem, construindo uma estrutura arbórea hierárquica na memória: a AST (Abstract Syntax Tree).
02. Modelagem Expressiva de ASTs com Enums
Em linguagens orientadas a objetos tradicionais, ASTs exigem herança e ponteiros polimórficos (virtual / interface), provocando indireções de memória. Em Rust, os Enums são Tagged Unions de primeira classe:
pub enum JsonValue { Null, Bool(bool), Number(f64), String(String), Array(Vec<JsonValue>), Object(BTreeMap<String, JsonValue>) }
Essa definição é intrinsecamente recursiva: um JsonValue::Object contém nós que também são JsonValue. O compilador garante casamento de padrões exaustivo (match), assegurando que nenhum caso sintático seja ignorado em tempo de execução.
03. Tratamento de Erros e Diagnósticos Posicionais
Um parser profissional não deve apenas falhar com um booleano ou mensagem vaga. Ao carregar as coordenadas Span durante o lexing, o parser emite diagnósticos precisos que indicam a linha e coluna exatas onde a gramática foi violada.
| Variante do Erro |
Causa Gramatical |
Feedback de Diagnóstico |
| UnexpectedToken |
Token fora da regra (ex: chave sem valor) |
Linha X, Coluna Y: Esperava valor JSON |
| UnexpectedEof |
Payload truncado sem fechar chaves/colchetes |
EOF antes de fechar delimitadores |
| TrailingCharacters |
Tokens residuais após o fechamento da raiz |
Caracteres extras detectados após a raiz |
04. Complexidade & Zero-Copy Considerations
💡 Evolução para Zero-Copy com Lifetimes:
Esta implementação aloca instâncias de String para manter o código modular e simples. Em cenários de altíssimo throughput, o ecossistema Rust utiliza Slices Emprestados (&'a str) com parâmetros de Lifetime: o parser não aloca nenhuma nova string no Heap, apontando diretamente para fatias da memória do buffer original de entrada.
use std::collections::BTreeMap;
use std::fmt::{self, Display, Formatter};
#[derive(Debug, Clone, PartialEq)]
pub enum JsonValue {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<JsonValue>),
Object(BTreeMap<String, JsonValue>),
}
impl JsonValue {
pub fn get(&self, key: &str) -> Option<&JsonValue> {
match self {
JsonValue::Object(map) => map.get(key),
_ => None,
}
}
pub fn get_index(&self, index: usize) -> Option<&JsonValue> {
match self {
JsonValue::Array(arr) => arr.get(index),
_ => None,
}
}
pub fn pretty_print(&self, indent: usize) -> String {
let spaces = " ".repeat(indent);
let inner_spaces = " ".repeat(indent + 2);
match self {
JsonValue::Null => "null".to_string(),
JsonValue::Bool(b) => b.to_string(),
JsonValue::Number(n) => n.to_string(),
JsonValue::String(s) => format!("\"{}\"", s),
JsonValue::Array(arr) => {
if arr.is_empty() {
return "[]".to_string();
}
let items: Vec<String> = arr
.iter()
.map(|item| format!("{}{}", inner_spaces, item.pretty_print(indent + 2)))
.collect();
format!("[\n{}\n{}]", items.join(",\n"), spaces)
}
JsonValue::Object(map) => {
if map.is_empty() {
return "{}".to_string();
}
let pairs: Vec<String> = map
.iter()
.map(|(k, v)| {
format!("{}\"{}\": {}", inner_spaces, k, v.pretty_print(indent + 2))
})
.collect();
format!("{{\n{}\n{}}}", pairs.join(",\n"), spaces)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Span {
pub line: usize,
pub col: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TokenKind {
LeftBrace,
RightBrace,
LeftBracket,
RightBracket,
Colon,
Comma,
StringLiteral(String),
NumberLiteral(f64),
BooleanLiteral(bool),
NullLiteral,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Token {
pub kind: TokenKind,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParseError {
UnexpectedToken(String, Span),
UnexpectedEof,
InvalidNumber(String, Span),
TrailingCharacters(Span),
}
impl Display for ParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ParseError::UnexpectedToken(msg, span) => {
write!(f, "Erro de Sintaxe na Linha {}, Coluna {}: {}", span.line, span.col, msg)
}
ParseError::UnexpectedEof => {
write!(f, "Fim inesperado do arquivo (EOF) antes do fechamento das chaves/colchetes")
}
ParseError::InvalidNumber(raw, span) => {
write!(f, "Número inválido '{}' na Linha {}, Coluna {}", raw, span.line, span.col)
}
ParseError::TrailingCharacters(span) => {
write!(f, "Caracteres extras encontrados após o JSON principal na Linha {}, Coluna {}", span.line, span.col)
}
}
}
}
pub struct Lexer<'a> {
input: &'a str,
chars: Vec<char>,
pos: usize,
line: usize,
col: usize,
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self {
Self {
input,
chars: input.chars().collect(),
pos: 0,
line: 1,
col: 1,
}
}
fn peek(&self) -> Option<char> {
self.chars.get(self.pos).copied()
}
fn advance(&mut self) -> Option<char> {
let ch = self.chars.get(self.pos).copied();
if let Some(c) = ch {
self.pos += 1;
if c == '\n' {
self.line += 1;
self.col = 1;
} else {
self.col += 1;
}
}
ch
}
fn skip_whitespace(&mut self) {
while let Some(c) = self.peek() {
if c.is_whitespace() {
self.advance();
} else {
break;
}
}
}
pub fn tokenize(&mut self) -> Result<Vec<Token>, ParseError> {
let mut tokens = Vec::new();
loop {
self.skip_whitespace();
let span = Span { line: self.line, col: self.col };
let ch = match self.peek() {
Some(c) => c,
None => break,
};
let kind = match ch {
'{' => { self.advance(); TokenKind::LeftBrace }
'}' => { self.advance(); TokenKind::RightBrace }
'[' => { self.advance(); TokenKind::LeftBracket }
']' => { self.advance(); TokenKind::RightBracket }
':' => { self.advance(); TokenKind::Colon }
',' => { self.advance(); TokenKind::Comma }
'"' => {
self.advance();
let s = self.lex_string()?;
TokenKind::StringLiteral(s)
}
c if c.is_ascii_digit() || c == '-' => {
let num = self.lex_number(span)?;
TokenKind::NumberLiteral(num)
}
c if c.is_alphabetic() => {
self.lex_keyword(span)?
}
other => {
return Err(ParseError::UnexpectedToken(
format!("Caractere não reconhecido '{}'", other),
span,
));
}
};
tokens.push(Token { kind, span });
}
Ok(tokens)
}
fn lex_string(&mut self) -> Result<String, ParseError> {
let mut result = String::new();
while let Some(c) = self.advance() {
match c {
'"' => return Ok(result),
'\\' => {
if let Some(esc) = self.advance() {
match esc {
'"' => result.push('"'),
'\\' => result.push('\\'),
'/' => result.push('/'),
'n' => result.push('\n'),
't' => result.push('\t'),
'r' => result.push('\r'),
other => result.push(other),
}
}
}
other => result.push(other),
}
}
Err(ParseError::UnexpectedEof)
}
fn lex_number(&mut self, span: Span) -> Result<f64, ParseError> {
let mut raw = String::new();
while let Some(c) = self.peek() {
if c.is_ascii_digit() || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E' {
raw.push(c);
self.advance();
} else {
break;
}
}
raw.parse::<f64>()
.map_err(|_| ParseError::InvalidNumber(raw, span))
}
fn lex_keyword(&mut self, span: Span) -> Result<TokenKind, ParseError> {
let mut word = String::new();
while let Some(c) = self.peek() {
if c.is_alphabetic() {
word.push(c);
self.advance();
} else {
break;
}
}
match word.as_str() {
"true" => Ok(TokenKind::BooleanLiteral(true)),
"false" => Ok(TokenKind::BooleanLiteral(false)),
"null" => Ok(TokenKind::NullLiteral),
_ => Err(ParseError::UnexpectedToken(
format!("Palavra-chave inválida '{}'", word),
span,
)),
}
}
}
pub struct Parser {
tokens: Vec<Token>,
cursor: usize,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, cursor: 0 }
}
fn peek(&self) -> Option<&Token> {
self.tokens.get(self.cursor)
}
fn advance(&mut self) -> Option<&Token> {
let tok = self.tokens.get(self.cursor);
if tok.is_some() {
self.cursor += 1;
}
tok
}
pub fn parse(&mut self) -> Result<JsonValue, ParseError> {
let root = self.parse_value()?;
if let Some(extra) = self.peek() {
return Err(ParseError::TrailingCharacters(extra.span));
}
Ok(root)
}
fn parse_value(&mut self) -> Result<JsonValue, ParseError> {
let tok = self.peek().ok_or(ParseError::UnexpectedEof)?;
match &tok.kind {
TokenKind::LeftBrace => self.parse_object(),
TokenKind::LeftBracket => self.parse_array(),
TokenKind::StringLiteral(_) => {
if let TokenKind::StringLiteral(s) = self.advance().unwrap().kind.clone() {
Ok(JsonValue::String(s))
} else {
unreachable!()
}
}
TokenKind::NumberLiteral(n) => {
let val = *n;
self.advance();
Ok(JsonValue::Number(val))
}
TokenKind::BooleanLiteral(b) => {
let val = *b;
self.advance();
Ok(JsonValue::Bool(val))
}
TokenKind::NullLiteral => {
self.advance();
Ok(JsonValue::Null)
}
_ => {
let err_tok = self.advance().unwrap();
Err(ParseError::UnexpectedToken(
format!("Esperava valor JSON, encontrou '{:?}'", err_tok.kind),
err_tok.span,
))
}
}
}
fn parse_object(&mut self) -> Result<JsonValue, ParseError> {
self.advance();
let mut map = BTreeMap::new();
if let Some(tok) = self.peek() {
if tok.kind == TokenKind::RightBrace {
self.advance();
return Ok(JsonValue::Object(map));
}
}
loop {
let key_tok = self.advance().ok_or(ParseError::UnexpectedEof)?;
let key = match &key_tok.kind {
TokenKind::StringLiteral(s) => s.clone(),
_ => {
return Err(ParseError::UnexpectedToken(
"Chave de objeto JSON precisa ser uma String entre aspas".to_string(),
key_tok.span,
));
}
};
let colon_tok = self.advance().ok_or(ParseError::UnexpectedEof)?;
if colon_tok.kind != TokenKind::Colon {
return Err(ParseError::UnexpectedToken(
"Esperava ':' após a chave do objeto".to_string(),
colon_tok.span,
));
}
let value = self.parse_value()?;
map.insert(key, value);
let next_tok = self.advance().ok_or(ParseError::UnexpectedEof)?;
match next_tok.kind {
TokenKind::Comma => continue,
TokenKind::RightBrace => break,
_ => {
return Err(ParseError::UnexpectedToken(
"Esperava ',' ou '}' no objeto".to_string(),
next_tok.span,
));
}
}
}
Ok(JsonValue::Object(map))
}
fn parse_array(&mut self) -> Result<JsonValue, ParseError> {
self.advance();
let mut elements = Vec::new();
if let Some(tok) = self.peek() {
if tok.kind == TokenKind::RightBracket {
self.advance();
return Ok(JsonValue::Array(elements));
}
}
loop {
let value = self.parse_value()?;
elements.push(value);
let next_tok = self.advance().ok_or(ParseError::UnexpectedEof)?;
match next_tok.kind {
TokenKind::Comma => continue,
TokenKind::RightBracket => break,
_ => {
return Err(ParseError::UnexpectedToken(
"Esperava ',' ou ']' no array".to_string(),
next_tok.span,
));
}
}
}
Ok(JsonValue::Array(elements))
}
}
pub fn parse_json(input: &str) -> Result<JsonValue, ParseError> {
let mut lexer = Lexer::new(input);
let tokens = lexer.tokenize()?;
let mut parser = Parser::new(tokens);
parser.parse()
}
fn main() {
println!("============================================================");
println!("🦀 RUST COMPILERS: ZERO-DEPENDENCY RECURSIVE JSON PARSER");
println!("============================================================");
let sample_json = r#"{
"service": "telemetry-gateway",
"version": 2.4,
"enabled": true,
"workers": 8,
"metadata": {
"region": "us-east-1",
"cluster_id": null
},
"tags": ["cloud", "systems-engineering", "rust"]
}"#;
println!("\n1. Executando Parsing de JSON Válido:");
match parse_json(sample_json) {
Ok(ast) => {
println!(" AST construída com sucesso!");
println!("\n2. Navegação Tipada na AST gerada:");
if let Some(service) = ast.get("service") {
println!(" ast[\"service\"] => {:?}", service);
}
if let Some(metadata) = ast.get("metadata") {
println!(" ast[\"metadata\"][\"region\"] => {:?}", metadata.get("region"));
}
if let Some(tags) = ast.get("tags") {
println!(" ast[\"tags\"][1] => {:?}", tags.get_index(1));
}
println!("\n3. Formatador Pretty-Printer Reconstruído a partir da AST:");
println!("{}", ast.pretty_print(3));
}
Err(e) => println!(" Falha inesperada: {}", e),
}
println!("\n4. Auditoria de Diagnósticos de Erro Sintático:");
let invalid_json = "{\n \"port\": 8080,\n \"host\": \n}";
println!(" Analisando JSON com erro sintático proposital:\n{}", invalid_json);
match parse_json(invalid_json) {
Ok(_) => println!(" Erro: deveria ter falhado!"),
Err(err) => {
println!("\n ❌ Diagnóstico capturado com sucesso:");
println!(" {}", err);
}
}
}