use muncher::Muncher;
use rowan::SmolStr;
use super::err::{ParseTomlError, TomlErrorKind, TomlResult};
use super::kinds::TomlKind::{self, *};
use chrono::{NaiveDate, NaiveTime};
use super::common::{
cmp_tokens, BOOL_END, DATE_CHAR, DATE_END, DATE_LIKE, DATE_TIME, EOL, IDENT_END, INT_END,
KEY_END, NUM_END, SEG_END, TIME_CHAR, WHITESPACE,
};
use super::kinds::{Element, TomlNode, TomlToken};
use super::syntax::Parser;
impl Into<(TomlKind, SmolStr)> for Element {
fn into(self) -> (TomlKind, SmolStr) {
match self {
Element::Node(n) => (n.kind, n.text),
Element::Token(tkn) => (tkn.kind, tkn.text),
}
}
}
fn is_valid_key(s: &str) -> bool {
s.chars()
.all(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '.' | '-' | '\'' | '"'))
}
fn is_valid_datetime(s: &str) -> TomlResult<bool> {
let dt = s.split(DATE_TIME).collect::<Vec<_>>();
if dt.len() == 1 {
if dt[0].contains(':') {
let time = dt[0].split(':').collect::<Vec<_>>();
if time[2].contains('.') {
let (sec, milli) = {
let fractional = time[2].split('.').collect::<Vec<_>>();
(fractional[0].parse()?, fractional[1].parse()?)
};
NaiveTime::from_hms_milli(time[0].parse()?, time[1].parse()?, sec, milli);
} else {
NaiveTime::from_hms(time[0].parse()?, time[1].parse()?, time[2].parse()?);
};
Ok(true)
} else {
let date = dt[0].split('-').collect::<Vec<_>>();
assert_eq!(date.len(), 3);
let _ = NaiveDate::from_ymd(date[0].parse()?, date[1].parse()?, date[2].parse()?);
Ok(true)
}
} else {
let date = dt[0].split(DATE_CHAR).collect::<Vec<_>>();
let time = dt[1].split(TIME_CHAR).collect::<Vec<_>>();
let _ =
if time.len() > 3 {
if s.contains('+') {
NaiveDate::from_ymd(date[0].parse()?, date[1].parse()?, date[2].parse()?)
.and_hms(time[0].parse()?, time[1].parse()?, time[2].parse()?)
} else {
NaiveDate::from_ymd(date[0].parse()?, date[1].parse()?, date[2].parse()?)
.and_hms_milli(
time[0].parse()?,
time[1].parse()?,
time[2].parse()?,
time[3].parse()?,
)
}
} else {
NaiveDate::from_ymd(date[0].parse()?, date[1].parse()?, date[2].parse()?).and_hms(
time[0].parse()?,
time[1].parse()?,
time[2].parse()?,
)
};
Ok(true)
}
}
impl TomlToken {
fn maybe_whitespace(muncher: &mut Muncher) -> Option<Element> {
let (s, e) = muncher.eat_until_count(|c| !cmp_tokens(c, WHITESPACE));
let text = SmolStr::new(&muncher.text()[s..e]);
if e > s {
Some(Element::Token(Self {
kind: Whitespace,
text,
}))
} else {
None
}
}
fn hash(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_hash());
parser.builder.token(Hash.into(), SmolStr::new("#"));
Ok(())
}
fn equal(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_eq());
parser.builder.token(Equal.into(), SmolStr::new("="));
Ok(())
}
fn maybe_comma(muncher: &mut Muncher) -> Option<Element> {
if muncher.eat_comma() {
Some(Element::Token(Self {
kind: Comma,
text: SmolStr::new(","),
}))
} else {
None
}
}
fn dot(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_dot());
parser.builder.token(Dot.into(), SmolStr::new("."));
Ok(())
}
fn maybe_dot(muncher: &mut Muncher) -> Option<Element> {
if muncher.eat_dot() {
Some(Element::Token(Self {
kind: Dot,
text: SmolStr::new("."),
}))
} else {
None
}
}
fn double_quote(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_double_quote());
parser.builder.token(DoubleQuote.into(), SmolStr::new("\""));
Ok(())
}
fn triple_quote(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_double_quote());
assert!(muncher.eat_double_quote());
assert!(muncher.eat_double_quote());
parser
.builder
.token(TripleQuote.into(), SmolStr::new("\"\"\""));
Ok(())
}
fn single_quote(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_single_quote());
parser.builder.token(SingleQuote.into(), SmolStr::new("\'"));
Ok(())
}
fn ident(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
let (s, e) = muncher.eat_until_count(|c| cmp_tokens(c, IDENT_END));
let text = SmolStr::new(&muncher.text()[s..e]);
parser.builder.token(Ident.into(), text);
Ok(())
}
fn ident_seg(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
let (s, e) = muncher.eat_until_count(|c| cmp_tokens(c, SEG_END));
let text = SmolStr::new(&muncher.text()[s..e]);
parser.builder.token(Ident.into(), text);
Ok(())
}
fn ident_double_str(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
let (s, e) = muncher.eat_until_count(|c| c == &'"');
let text = SmolStr::new(&muncher.text()[s..e]);
parser.builder.token(Ident.into(), text);
Ok(())
}
fn ident_triple_str(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
let (s, e) = muncher.eat_range_of("\"\"\"");
let text = SmolStr::new(&muncher.text()[s..e]);
parser.builder.token(Ident.into(), text);
Ok(())
}
fn ident_single_str(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
let (s, e) = muncher.eat_until_count(|c| c == &'\'');
let text = SmolStr::new(&muncher.text()[s..e]);
parser.builder.token(Ident.into(), text);
Ok(())
}
fn comment_text(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
let (s, e) = muncher.eat_until_count(|c| cmp_tokens(c, EOL));
let text = SmolStr::new(&muncher.text()[s..e]);
parser.builder.token(CommentText.into(), text);
Ok(())
}
fn open_brace(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_open_brc());
parser.builder.token(OpenBrace.into(), SmolStr::new("["));
Ok(())
}
fn close_brace(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_close_brc());
parser.builder.token(CloseBrace.into(), SmolStr::new("]"));
Ok(())
}
fn open_curly(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_open_curly());
parser.builder.token(OpenCurly.into(), SmolStr::new("{"));
Ok(())
}
fn close_curly(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
assert!(muncher.eat_close_curly());
parser.builder.token(CloseCurly.into(), SmolStr::new("}"));
Ok(())
}
fn boolean(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
let (s, e) = muncher.eat_until_count(|c| cmp_tokens(c, BOOL_END));
let boolean = &muncher.text()[s..e];
let text = SmolStr::new(boolean);
if boolean == "true" || boolean == "false" {
parser.builder.token(Bool.into(), text);
Ok(())
} else {
let (col, ln) = muncher.cursor_position();
let msg = "invalid integer".into();
Err(ParseTomlError::new(
msg,
TomlErrorKind::UnexpectedToken {
tkn: boolean.into(),
ln,
col,
},
))
}
}
fn integer(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
let (s, e) = muncher.eat_until_count(|c| cmp_tokens(c, INT_END));
let int = &muncher.text()[s..e];
if int.chars().all(|c| c.is_numeric()) {
let text = SmolStr::new(int);
parser.builder.token(Integer.into(), text);
Ok(())
} else {
let (col, ln) = muncher.cursor_position();
let msg = "invalid integer".into();
Err(ParseTomlError::new(
msg,
TomlErrorKind::UnexpectedToken {
tkn: int.into(),
ln,
col,
},
))
}
}
}
impl TomlNode {
fn comment(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Comment.into());
TomlToken::hash(muncher, parser)?;
TomlToken::comment_text(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
parser.builder.finish_node();
Ok(())
}
fn float(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Float.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlToken::integer(muncher, parser)?;
TomlToken::dot(muncher, parser)?;
TomlToken::integer(muncher, parser)?;
parser.builder.finish_node();
Ok(())
}
fn date_time(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Date.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
let (s, e) = muncher.eat_until_count(|c| cmp_tokens(c, DATE_END));
let text = SmolStr::new(&muncher.text()[s..e]);
if is_valid_datetime(&text) != Ok(true) {
let (col, ln) = muncher.cursor_position();
let msg = "invalid integer".into();
Err(ParseTomlError::new(
msg,
TomlErrorKind::UnexpectedToken {
tkn: text.into(),
ln,
col,
},
))
} else {
parser.builder.token(Ident.into(), text);
parser.builder.finish_node();
Ok(())
}
}
fn single_str(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Str.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlToken::single_quote(muncher, parser)?;
TomlToken::ident_single_str(muncher, parser)?;
TomlToken::single_quote(muncher, parser)?;
parser.builder.finish_node();
Ok(())
}
fn double_str(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Str.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlToken::double_quote(muncher, parser)?;
TomlToken::ident_double_str(muncher, parser)?;
TomlToken::double_quote(muncher, parser)?;
parser.builder.finish_node();
Ok(())
}
fn string(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Str.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
if muncher.seek(2).map(|s| s == "\"\"") == Some(true) {
TomlToken::triple_quote(muncher, parser)?;
TomlToken::ident_triple_str(muncher, parser)?;
TomlToken::triple_quote(muncher, parser)?;
} else {
TomlToken::double_quote(muncher, parser)?;
TomlToken::ident_double_str(muncher, parser)?;
TomlToken::double_quote(muncher, parser)?;
}
parser.builder.finish_node();
Ok(())
}
fn key(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Key.into());
let (s, e) = muncher.peek_until_count(|c| cmp_tokens(c, KEY_END));
match muncher.peek() {
Some(&'"') => TomlNode::double_str(muncher, parser),
Some(&'\'') => TomlNode::single_str(muncher, parser),
Some(ch) if ch.is_ascii() => TomlToken::ident(muncher, parser),
Some(tkn) => {
let (col, ln) = muncher.cursor_position();
let msg = "invalid token in key".into();
let tkn = format!("{}", tkn);
return Err(ParseTomlError::new(
msg,
TomlErrorKind::UnexpectedToken { tkn, ln, col },
));
}
None => unreachable!("NONE in key"),
}?;
let text = SmolStr::new(&muncher.text()[s..e]);
if is_valid_key(&text) {
parser.builder.finish_node();
Ok(())
} else {
let (col, ln) = muncher.cursor_position();
let msg = "invalid token in key".into();
let tkn = format!("{}", text);
Err(ParseTomlError::new(
msg,
TomlErrorKind::UnexpectedToken { tkn, ln, col },
))
}
}
fn value(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Value.into());
match muncher.peek() {
Some('"') => TomlNode::string(muncher, parser),
Some('\'') => TomlNode::single_str(muncher, parser),
Some('t') | Some('f') => TomlToken::boolean(muncher, parser),
Some('[') => TomlNode::array(muncher, parser),
Some('{') => TomlNode::inline_table(muncher, parser),
Some(digi) if digi.is_numeric() => {
muncher.reset_peek();
let raw = muncher
.peek_until(|c| cmp_tokens(c, NUM_END))
.collect::<String>();
if raw.contains(DATE_LIKE) {
TomlNode::date_time(muncher, parser)
} else if raw.contains('.') {
TomlNode::float(muncher, parser)
} else {
TomlToken::integer(muncher, parser)
}
}
None => unimplemented!("found EOF in value"),
_ => {
let msg = "invalid token in value";
let tkn = if let Some(peek) = muncher.peek() {
format!("{:?}", peek)
} else {
"no token".into()
};
let (col, ln) = muncher.cursor_position();
return Err(ParseTomlError::new(
msg.into(),
TomlErrorKind::UnexpectedToken { tkn, ln, col },
));
}
}?;
parser.builder.finish_node();
Ok(())
}
fn key_value(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
if muncher.is_done() {
unreachable!("BUG tokenizer should never hit DONE in key value")
}
if muncher.peek() == Some(&'#') {
TomlNode::comment(muncher, parser)?;
return Ok(());
}
parser.builder.start_node(KeyValue.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlNode::key(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlToken::equal(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlNode::value(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
parser.builder.finish_node();
Ok(())
}
fn inline_key_value(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
if muncher.is_done() {
unreachable!("BUG tokenizer should never hit DONE in inline key value")
}
parser.builder.start_node(KeyValue.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlNode::key(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlToken::equal(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlNode::inline_value(muncher, parser)?;
parser.builder.finish_node();
Ok(())
}
fn array_item(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<Option<()>> {
if muncher.peek() == Some(&']') {
return Ok(None);
}
parser.builder.start_node(ArrayItem.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlNode::value(muncher, parser)?;
if let Some(comma) = TomlToken::maybe_comma(muncher) {
let (kind, text) = comma.into();
parser.builder.token(kind.into(), text);
}
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text);
}
parser.builder.finish_node();
Ok(Some(()))
}
fn array(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Array.into());
TomlToken::open_brace(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
while TomlNode::array_item(muncher, parser)?.is_some() { }
TomlToken::close_brace(muncher, parser)?;
parser.builder.finish_node();
Ok(())
}
fn inline_value(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Value.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
match muncher.peek() {
Some('"') => TomlNode::string(muncher, parser),
Some('\'') => TomlNode::single_str(muncher, parser),
Some('t') | Some('f') => TomlToken::boolean(muncher, parser),
Some('[') => TomlNode::array(muncher, parser),
Some('{') => TomlNode::inline_table(muncher, parser),
Some(digi) if digi.is_numeric() => {
muncher.reset_peek();
let raw = muncher
.peek_until(|c| cmp_tokens(c, NUM_END))
.collect::<String>();
if raw.contains(DATE_LIKE) {
TomlNode::date_time(muncher, parser)
} else if raw.contains('.') {
TomlNode::float(muncher, parser)
} else {
TomlToken::integer(muncher, parser)
}
}
None => unimplemented!("value found EOF"),
_ => {
let msg = "invalid token in key value pairs";
let tkn = if let Some(peek) = muncher.peek() {
format!("{:#?}", peek)
} else {
"no token".into()
};
let (col, ln) = muncher.cursor_position();
return Err(ParseTomlError::new(
msg.into(),
TomlErrorKind::UnexpectedToken { tkn, ln, col },
));
}
}?;
parser.builder.finish_node();
Ok(())
}
fn inline_table(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(InlineTable.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlToken::open_curly(muncher, parser)?;
loop {
if muncher.peek() == Some(&'}') {
break;
}
TomlNode::inline_key_value(muncher, parser)?;
if let Some(comma) = TomlToken::maybe_comma(muncher) {
let (kind, text) = comma.into();
parser.builder.token(kind.into(), text)
}
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
}
TomlToken::close_curly(muncher, parser)?;
parser.builder.finish_node();
Ok(())
}
fn ident_heading(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
let (s, e) = muncher.peek_until_count(|c| c == &']');
let text = SmolStr::new(&muncher.text()[s..e]);
if text.contains('"') {
parser.builder.start_node(SegIdent.into());
if text.contains('.') {
let mut txt = text;
loop {
let mut in_str = false;
let dot_index = |ch: char| -> bool {
if ch == '"' && !in_str {
in_str = true;
} else if ch == '"' && in_str {
in_str = false;
};
ch == '.' && !in_str
};
if txt.starts_with('"') {
TomlNode::double_str(muncher, parser)?;
if let Some(dot) = TomlToken::maybe_dot(muncher) {
let (kind, text) = dot.into();
parser.builder.token(kind.into(), text)
}
if let Some(idx) = txt.chars().position(dot_index) {
txt = SmolStr::from(txt.split_at(idx).1);
} else {
break;
}
} else {
TomlToken::ident_seg(muncher, parser)?;
if let Some(dot) = TomlToken::maybe_dot(muncher) {
let (kind, text) = dot.into();
parser.builder.token(kind.into(), text)
}
if let Some(idx) = txt.chars().position(dot_index) {
txt = SmolStr::from(txt.split_at(idx + 1).1);
} else {
break;
}
}
}
} else {
TomlNode::double_str(muncher, parser)?;
}
parser.builder.finish_node();
Ok(())
} else if text.contains('.') {
parser.builder.start_node(SegIdent.into());
TomlToken::ident_seg(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlToken::dot(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlToken::ident_seg(muncher, parser)?;
for _ in 2..text.split('.').count() {
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
if let Some(dot) = TomlToken::maybe_dot(muncher) {
let (kind, text) = dot.into();
parser.builder.token(kind.into(), text);
TomlToken::ident_seg(muncher, parser)?;
}
}
parser.builder.finish_node();
Ok(())
} else {
parser.builder.token(Ident.into(), text);
Ok(())
}
}
fn heading(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Heading.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
if muncher.seek(2).map(|s| s.starts_with("[[")) == Some(true) {
parser.builder.start_node(TomlKind::ArrayHeading.into());
TomlToken::open_brace(muncher, parser)?;
TomlToken::open_brace(muncher, parser)?;
match muncher.peek() {
Some(ch) if ch.is_ascii() => TomlNode::ident_heading(muncher, parser)?,
Some(tkn) => {
let (col, ln) = muncher.cursor_position();
let msg = "invalid heading token".into();
let tkn = format!("{}", tkn);
return Err(ParseTomlError::new(
msg,
TomlErrorKind::UnexpectedToken { tkn, ln, col },
));
}
None => unreachable!("empty toml heading"),
};
let _eaten = muncher.eat_until(|c| c == &']');
TomlToken::close_brace(muncher, parser)?;
TomlToken::close_brace(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
parser.builder.finish_node();
parser.builder.finish_node();
return Ok(());
};
TomlToken::open_brace(muncher, parser)?;
match muncher.peek() {
Some(ch) if ch.is_ascii() => TomlNode::ident_heading(muncher, parser)?,
Some(tkn) => {
let (col, ln) = muncher.cursor_position();
let msg = "invalid heading token".into();
let tkn = format!("{}", tkn);
return Err(ParseTomlError::new(
msg,
TomlErrorKind::UnexpectedToken { tkn, ln, col },
));
}
None => unreachable!("empty toml heading"),
};
let _eaten = muncher.eat_until(|c| c == &']');
TomlToken::close_brace(muncher, parser)?;
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
parser.builder.finish_node();
Ok(())
}
fn table(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Table.into());
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
TomlNode::heading(muncher, parser)?;
loop {
if muncher.seek(5).map(|s| s.contains('[')) == Some(true) {
break;
}
muncher.reset_peek();
if muncher.is_done() {
break;
}
TomlNode::key_value(muncher, parser)?;
}
if let Some(ws) = TomlToken::maybe_whitespace(muncher) {
let (kind, text) = ws.into();
parser.builder.token(kind.into(), text)
}
parser.builder.finish_node();
Ok(())
}
}
pub struct Tokenizer;
impl Tokenizer {
pub fn parse(input: &str, mut p: Parser) -> TomlResult<Parser> {
let mut muncher = Muncher::new(input);
Tokenizer::parse_file(&mut muncher, &mut p)?;
Ok(p)
}
fn parse_file(muncher: &mut Muncher, parser: &mut Parser) -> TomlResult<()> {
parser.builder.start_node(Root.into());
loop {
if muncher.is_done() {
parser.builder.token(EoF.into(), SmolStr::default());
break;
}
match muncher.peek() {
Some('#') => {
TomlNode::comment(muncher, parser)?;
}
Some('[') => {
TomlNode::table(muncher, parser)?;
}
Some(ch) if ch.is_ascii() => {
TomlNode::key_value(muncher, parser)?;
}
Some(tkn) => {
let msg = "toml file must be key values or tables".into();
let tkn = format!("{}", tkn);
let (col, ln) = muncher.cursor_position();
return Err(ParseTomlError::new(
msg,
TomlErrorKind::UnexpectedToken { tkn, ln, col },
));
}
None => {
parser.builder.token(EoF.into(), SmolStr::default());
break;
}
}
}
parser.builder.finish_node();
Ok(())
}
}