#![forbid(unsafe_code)]
use super::{error::ParseError, lexer::Lexer, span::Span, token::Token};
#[derive(Debug, Clone, PartialEq)]
pub enum TopLevelItem {
Message {
name: String,
span: Span,
body_span: Span,
},
Enum {
name: String,
span: Span,
body_span: Span,
},
Service {
name: String,
span: Span,
body_span: Span,
},
}
#[derive(Debug, Clone, Default)]
pub struct FileOutline {
pub syntax: Option<String>,
pub package: Option<String>,
pub imports: Vec<String>,
pub options: Vec<String>,
pub items: Vec<TopLevelItem>,
}
pub fn parse_outline(source: &str) -> Result<FileOutline, ParseError> {
let mut lexer = Lexer::new(source).peekable();
let mut outline = FileOutline::default();
loop {
let spanned = match next_significant(&mut lexer)? {
None => break,
Some(s) => s,
};
match spanned.value {
Token::Eof => break,
Token::Syntax => {
outline.syntax = Some(parse_syntax(&mut lexer)?);
}
Token::Package => {
outline.package = Some(parse_package(&mut lexer)?);
}
Token::Import => {
let path = parse_import(&mut lexer)?;
outline.imports.push(path);
}
Token::Option => {
let name = parse_option_name(&mut lexer)?;
outline.options.push(name);
}
Token::Message => {
let item = parse_named_block(&mut lexer, spanned.span, |name, span, body_span| {
TopLevelItem::Message {
name,
span,
body_span,
}
})?;
outline.items.push(item);
}
Token::Enum => {
let item = parse_named_block(&mut lexer, spanned.span, |name, span, body_span| {
TopLevelItem::Enum {
name,
span,
body_span,
}
})?;
outline.items.push(item);
}
Token::Service => {
let item = parse_named_block(&mut lexer, spanned.span, |name, span, body_span| {
TopLevelItem::Service {
name,
span,
body_span,
}
})?;
outline.items.push(item);
}
_ => {}
}
}
Ok(outline)
}
type PeekLexer<'a> = std::iter::Peekable<Lexer<'a>>;
fn next_significant(
lexer: &mut PeekLexer<'_>,
) -> Result<Option<super::span::Spanned<Token>>, ParseError> {
loop {
match lexer.next() {
None => return Ok(None),
Some(Err(e)) => return Err(ParseError::Lex(e)),
Some(Ok(s)) => {
if matches!(s.value, Token::LineComment(_) | Token::BlockComment(_)) {
continue;
}
return Ok(Some(s));
}
}
}
}
fn expect_token(
lexer: &mut PeekLexer<'_>,
expected_desc: &str,
predicate: impl Fn(&Token) -> bool,
) -> Result<super::span::Spanned<Token>, ParseError> {
match next_significant(lexer)? {
None => Err(ParseError::UnexpectedEof),
Some(s) => {
if predicate(&s.value) {
Ok(s)
} else {
Err(ParseError::UnexpectedToken {
expected: expected_desc.to_owned(),
found: s.value.to_string(),
span: s.span,
})
}
}
}
}
fn expect_semi(lexer: &mut PeekLexer<'_>) -> Result<(), ParseError> {
expect_token(lexer, ";", |t| matches!(t, Token::Semicolon))?;
Ok(())
}
fn parse_syntax(lexer: &mut PeekLexer<'_>) -> Result<String, ParseError> {
expect_token(lexer, "=", |t| matches!(t, Token::Equals))?;
let s = expect_token(lexer, "string literal", |t| {
matches!(t, Token::StringLit(_))
})?;
let val = match s.value {
Token::StringLit(v) => v,
_ => unreachable!(),
};
expect_semi(lexer)?;
Ok(val)
}
fn parse_package(lexer: &mut PeekLexer<'_>) -> Result<String, ParseError> {
let mut parts = Vec::new();
let first = expect_ident_or_keyword_name(lexer, "package name")?;
parts.push(first);
loop {
match lexer.peek() {
Some(Ok(s)) if matches!(s.value, Token::Dot) => {
let _ = lexer.next();
let part = expect_ident_or_keyword_name(lexer, "package name segment")?;
parts.push(part);
}
_ => break,
}
}
expect_semi(lexer)?;
Ok(parts.join("."))
}
fn expect_ident_or_keyword_name(
lexer: &mut PeekLexer<'_>,
ctx: &str,
) -> Result<String, ParseError> {
let s = match next_significant(lexer)? {
None => return Err(ParseError::UnexpectedEof),
Some(s) => s,
};
match s.value {
Token::Ident(name) => Ok(name),
other => Ok(other.to_string()),
}
.map_err(|_: std::convert::Infallible| ParseError::UnexpectedToken {
expected: ctx.to_owned(),
found: "?".to_owned(),
span: s.span,
})
}
fn parse_import(lexer: &mut PeekLexer<'_>) -> Result<String, ParseError> {
match lexer.peek() {
Some(Ok(s)) if matches!(s.value, Token::Public | Token::Weak) => {
let _ = lexer.next(); }
_ => {}
}
let s = expect_token(lexer, "import path (string literal)", |t| {
matches!(t, Token::StringLit(_))
})?;
let path = match s.value {
Token::StringLit(p) => p,
_ => unreachable!(),
};
expect_semi(lexer)?;
Ok(path)
}
fn parse_option_name(lexer: &mut PeekLexer<'_>) -> Result<String, ParseError> {
let mut name_parts = Vec::<String>::new();
loop {
match lexer.peek() {
None => return Err(ParseError::UnexpectedEof),
Some(Ok(s)) => match &s.value {
Token::Equals => break,
Token::Semicolon => {
break;
}
Token::LineComment(_) | Token::BlockComment(_) => {
let _ = lexer.next();
}
Token::LParen | Token::RParen | Token::Dot => {
let _ = lexer.next(); }
Token::Eof => break,
_ => {
let tok = lexer.next().expect("peeked");
match tok {
Ok(spanned) => name_parts.push(spanned.value.to_string()),
Err(e) => return Err(ParseError::Lex(e)),
}
}
},
Some(Err(_)) => {
let e = lexer.next().expect("peeked");
return Err(ParseError::Lex(e.expect_err("must be err")));
}
}
}
loop {
match lexer.next() {
None => return Err(ParseError::UnexpectedEof),
Some(Err(e)) => return Err(ParseError::Lex(e)),
Some(Ok(s)) => match s.value {
Token::Semicolon => break,
Token::Eof => return Err(ParseError::UnexpectedEof),
_ => {}
},
}
}
Ok(name_parts.join(""))
}
fn parse_named_block(
lexer: &mut PeekLexer<'_>,
kw_span: Span,
make: impl Fn(String, Span, Span) -> TopLevelItem,
) -> Result<TopLevelItem, ParseError> {
let name_tok = expect_token(lexer, "identifier (name)", |t| matches!(t, Token::Ident(_)))?;
let name = match name_tok.value {
Token::Ident(n) => n,
_ => unreachable!(),
};
let lbrace = expect_token(lexer, "{", |t| matches!(t, Token::LBrace))?;
let body_span = consume_braced_body(lexer, lbrace.span)?;
let full_span = Span::new(kw_span.start, body_span.end);
Ok(make(name, full_span, body_span))
}
fn consume_braced_body(
lexer: &mut PeekLexer<'_>,
open_brace_span: Span,
) -> Result<Span, ParseError> {
let mut depth: usize = 1;
loop {
match lexer.next() {
None => {
return Err(ParseError::UnbalancedBraces {
span: open_brace_span,
});
}
Some(Err(e)) => return Err(ParseError::Lex(e)),
Some(Ok(spanned)) => {
let token_end = spanned.span.end;
match spanned.value {
Token::LBrace => depth += 1,
Token::RBrace => {
depth -= 1;
if depth == 0 {
return Ok(Span::new(open_brace_span.start, token_end));
}
}
_ => {}
}
}
}
}
}