use super::node::*;
use super::specs;
#[derive(Debug, Clone)]
enum Stop {
EndOfInput,
CloseBrace,
CloseBracket,
DollarMath,
DisplayDollarMath,
MathBracket,
MathParen,
EndEnvironment(String),
}
pub struct Parser<'a> {
chars: &'a [char],
}
pub fn parse(source: &str) -> (Vec<Node>, Vec<char>) {
let chars: Vec<char> = source.chars().collect();
let (nodes, _end) = {
let parser = Parser { chars: &chars };
parser.parse_nodelist(0, &Stop::EndOfInput)
};
(nodes, chars)
}
impl<'a> Parser<'a> {
fn len(&self) -> usize {
self.chars.len()
}
fn parse_nodelist(&self, mut pos: usize, stop: &Stop) -> (Vec<Node>, usize) {
let mut nodes: Vec<Node> = Vec::new();
let mut chars_start: Option<usize> = None;
let n = self.len();
while pos < n {
if let Some(stop_end) = self.matches_stop(pos, stop) {
self.flush_chars(&mut nodes, &mut chars_start, pos);
return (nodes, stop_end);
}
if let Some(matched) = self.match_specials(pos) {
self.flush_chars(&mut nodes, &mut chars_start, pos);
let len = matched.chars().count();
nodes.push(Node::Specials(SpecialsNode {
span: Span { pos, len },
chars: matched.to_string(),
}));
pos += len;
continue;
}
match self.chars[pos] {
'%' => {
self.flush_chars(&mut nodes, &mut chars_start, pos);
let (node, next) = self.parse_comment(pos);
nodes.push(Node::Comment(node));
pos = next;
}
'\\' => {
self.flush_chars(&mut nodes, &mut chars_start, pos);
let (node, next) = self.parse_backslash(pos);
nodes.push(node);
pos = next;
}
'{' => {
self.flush_chars(&mut nodes, &mut chars_start, pos);
let (node, next) = self.parse_group(pos, GroupDelims::Brace);
nodes.push(Node::Group(node));
pos = next;
}
'$' => {
self.flush_chars(&mut nodes, &mut chars_start, pos);
let (node, next) = self.parse_dollar_math(pos);
nodes.push(Node::Math(node));
pos = next;
}
_ => {
if chars_start.is_none() {
chars_start = Some(pos);
}
pos += 1;
}
}
}
self.flush_chars(&mut nodes, &mut chars_start, pos);
(nodes, pos)
}
fn flush_chars(&self, nodes: &mut Vec<Node>, chars_start: &mut Option<usize>, end: usize) {
if let Some(start) = chars_start.take() {
if end > start {
nodes.push(Node::Chars(CharsNode {
span: Span {
pos: start,
len: end - start,
},
chars: self.chars[start..end].iter().collect(),
}));
}
}
}
fn matches_stop(&self, pos: usize, stop: &Stop) -> Option<usize> {
match stop {
Stop::EndOfInput => None,
Stop::CloseBrace => (self.chars[pos] == '}').then_some(pos + 1),
Stop::CloseBracket => (self.chars[pos] == ']').then_some(pos + 1),
Stop::DollarMath => {
(self.chars[pos] == '$' && !self.starts_with(pos, "$$")).then_some(pos + 1)
}
Stop::DisplayDollarMath => self.starts_with(pos, "$$").then_some(pos + 2),
Stop::MathBracket => self.starts_with(pos, r"\]").then_some(pos + 2),
Stop::MathParen => self.starts_with(pos, r"\)").then_some(pos + 2),
Stop::EndEnvironment(name) => {
let marker = format!(r"\end{{{name}}}");
self.starts_with(pos, &marker)
.then_some(pos + marker.chars().count())
}
}
}
fn starts_with(&self, pos: usize, needle: &str) -> bool {
let needle_chars: Vec<char> = needle.chars().collect();
if pos + needle_chars.len() > self.len() {
return false;
}
self.chars[pos..pos + needle_chars.len()] == needle_chars[..]
}
fn match_specials(&self, pos: usize) -> Option<&'static str> {
specs::specials()
.iter()
.find(|&&s| self.starts_with(pos, s))
.copied()
}
fn skip_tex_whitespace(&self, pos: usize) -> usize {
let n = self.len();
let mut i = pos;
while i < n && self.chars[i].is_whitespace() {
i += 1;
if i - pos >= 2 && self.chars[i - 2] == '\n' && self.chars[i - 1] == '\n' {
i -= 2;
break;
}
}
i
}
fn parse_comment(&self, pos: usize) -> (CommentNode, usize) {
debug_assert_eq!(self.chars[pos], '%');
let n = self.len();
let mut i = pos + 1;
while i < n && self.chars[i] != '\n' && self.chars[i] != '\r' {
i += 1;
}
let text_end = i;
let is_newline_class = |c: char| c == '\n' || c == '\r';
let span_end = if i < n && !(i + 1 < n && is_newline_class(self.chars[i + 1])) {
i + 1
} else {
i
};
(
CommentNode {
span: Span {
pos,
len: span_end - pos,
},
comment: self.chars[pos + 1..text_end].iter().collect(),
},
span_end,
)
}
fn parse_group(&self, pos: usize, delims: GroupDelims) -> (GroupNode, usize) {
debug_assert!(self.chars[pos] == '{' || self.chars[pos] == '[');
let stop = match delims {
GroupDelims::Brace => Stop::CloseBrace,
GroupDelims::Bracket => Stop::CloseBracket,
};
let (nodelist, end) = self.parse_nodelist(pos + 1, &stop);
(
GroupNode {
span: Span {
pos,
len: end - pos,
},
delims,
nodelist,
},
end,
)
}
fn parse_dollar_math(&self, pos: usize) -> (MathNode, usize) {
debug_assert_eq!(self.chars[pos], '$');
if self.starts_with(pos, "$$") {
let (nodelist, end) = self.parse_nodelist(pos + 2, &Stop::DisplayDollarMath);
(
MathNode {
span: Span {
pos,
len: end - pos,
},
kind: MathKind::Display,
nodelist,
},
end,
)
} else {
let (nodelist, end) = self.parse_nodelist(pos + 1, &Stop::DollarMath);
(
MathNode {
span: Span {
pos,
len: end - pos,
},
kind: MathKind::Inline,
nodelist,
},
end,
)
}
}
fn parse_backslash(&self, pos: usize) -> (Node, usize) {
debug_assert_eq!(self.chars[pos], '\\');
let n = self.len();
if pos + 1 >= n {
return (
Node::Macro(MacroNode {
span: Span { pos, len: 1 },
macroname: String::new(),
args: Vec::new(),
}),
pos + 1,
);
}
if self.starts_with(pos, r"\[") {
let (nodelist, end) = self.parse_nodelist(pos + 2, &Stop::MathBracket);
return (
Node::Math(MathNode {
span: Span {
pos,
len: end - pos,
},
kind: MathKind::Display,
nodelist,
}),
end,
);
}
if self.starts_with(pos, r"\(") {
let (nodelist, end) = self.parse_nodelist(pos + 2, &Stop::MathParen);
return (
Node::Math(MathNode {
span: Span {
pos,
len: end - pos,
},
kind: MathKind::Inline,
nodelist,
}),
end,
);
}
let next = self.chars[pos + 1];
let (name, name_end, is_word) = if next.is_ascii_alphabetic() || next == '@' {
let mut i = pos + 1;
while i < n && (self.chars[i].is_ascii_alphabetic() || self.chars[i] == '@') {
i += 1;
}
(self.chars[pos + 1..i].iter().collect::<String>(), i, true)
} else {
(next.to_string(), pos + 2, false)
};
let after_ws = if is_word {
self.skip_tex_whitespace(name_end)
} else {
name_end
};
if is_word && name == "begin" {
return self.parse_environment(pos, after_ws);
}
if is_word && specs::is_verbatim_macro(&name) {
return self.parse_verbatim_macro(pos, name, after_ws);
}
if let Some(argspec) = specs::macro_argspec(&name) {
let (args, end) = self.parse_args(argspec, after_ws);
return (
Node::Macro(MacroNode {
span: Span {
pos,
len: end - pos,
},
macroname: name,
args,
}),
end,
);
}
(
Node::Macro(MacroNode {
span: Span {
pos,
len: after_ws - pos,
},
macroname: name,
args: Vec::new(),
}),
after_ws,
)
}
fn parse_verbatim_macro(&self, pos: usize, name: String, after_ws: usize) -> (Node, usize) {
let n = self.len();
if after_ws >= n {
return (
Node::Macro(MacroNode {
span: Span {
pos,
len: after_ws - pos,
},
macroname: name,
args: Vec::new(),
}),
after_ws,
);
}
let delim = self.chars[after_ws];
let content_start = after_ws + 1;
let mut i = content_start;
while i < n && self.chars[i] != delim {
i += 1;
}
let content_end = i;
let span_end = if i < n { i + 1 } else { i }; let content: String = self.chars[content_start..content_end].iter().collect();
let arg = Node::Chars(CharsNode {
span: Span {
pos: content_start,
len: content_end - content_start,
},
chars: content,
});
(
Node::Macro(MacroNode {
span: Span {
pos,
len: span_end - pos,
},
macroname: name,
args: vec![Some(arg)],
}),
span_end,
)
}
fn parse_environment(&self, macro_start: usize, mut pos: usize) -> (Node, usize) {
let n = self.len();
if pos >= n || self.chars[pos] != '{' {
return (
Node::Macro(MacroNode {
span: Span {
pos: macro_start,
len: pos - macro_start,
},
macroname: "begin".to_string(),
args: Vec::new(),
}),
pos,
);
}
let (name, name_group_end) = self.read_brace_delimited_name(pos);
pos = name_group_end;
let args = if let Some(argspec) = specs::environment_argspec(&name) {
let (a, next) = self.parse_args(argspec, pos);
pos = next;
a
} else {
Vec::new()
};
if specs::is_verbatim_environment(&name) {
return self.parse_verbatim_environment(macro_start, name, pos);
}
let (nodelist, end) = self.parse_nodelist(pos, &Stop::EndEnvironment(name.clone()));
(
Node::Environment(EnvironmentNode {
span: Span {
pos: macro_start,
len: end - macro_start,
},
environmentname: name,
args,
nodelist,
}),
end,
)
}
fn parse_verbatim_environment(
&self,
macro_start: usize,
name: String,
pos: usize,
) -> (Node, usize) {
let marker: Vec<char> = format!(r"\end{{{name}}}").chars().collect();
let n = self.len();
let mut i = pos;
while i < n {
if i + marker.len() <= n && self.chars[i..i + marker.len()] == marker[..] {
break;
}
i += 1;
}
let body_end = i;
let span_end = if i < n { i + marker.len() } else { i };
let body_text: String = self.chars[pos..body_end].iter().collect();
let verbatim_arg = vec![Some(Node::Chars(CharsNode {
span: Span {
pos,
len: body_end - pos,
},
chars: body_text,
}))];
(
Node::Environment(EnvironmentNode {
span: Span {
pos: macro_start,
len: span_end - macro_start,
},
environmentname: name,
args: verbatim_arg,
nodelist: Vec::new(),
}),
span_end,
)
}
fn read_brace_delimited_name(&self, pos: usize) -> (String, usize) {
debug_assert_eq!(self.chars[pos], '{');
let n = self.len();
let mut i = pos + 1;
while i < n && self.chars[i] != '}' {
i += 1;
}
let name: String = self.chars[pos + 1..i].iter().collect();
let end = if i < n { i + 1 } else { i };
(name, end)
}
fn parse_args(&self, argspec: &str, mut pos: usize) -> (Args, usize) {
let mut args: Args = Vec::with_capacity(argspec.chars().count());
for slot in argspec.chars() {
match slot {
'{' => {
pos = self.skip_tex_whitespace(pos);
if pos < self.len() && self.chars[pos] == '{' {
let (g, next) = self.parse_group(pos, GroupDelims::Brace);
pos = next;
args.push(Some(Node::Group(g)));
} else if pos < self.len() {
let (node, next) = self.parse_single_token(pos);
pos = next;
args.push(Some(node));
} else {
args.push(None);
}
}
'[' => {
let peek = self.skip_tex_whitespace(pos);
if peek < self.len() && self.chars[peek] == '[' {
let (g, next) = self.parse_group(peek, GroupDelims::Bracket);
pos = next;
args.push(Some(Node::Group(g)));
} else {
args.push(None); }
}
'*' => {
if pos < self.len() && self.chars[pos] == '*' {
args.push(Some(Node::Chars(CharsNode {
span: Span { pos, len: 1 },
chars: "*".to_string(),
})));
pos += 1;
} else {
args.push(None);
}
}
_ => {}
}
}
(args, pos)
}
fn parse_single_token(&self, pos: usize) -> (Node, usize) {
if self.chars[pos] == '}' {
return (
Node::Chars(CharsNode {
span: Span { pos, len: 0 },
chars: String::new(),
}),
pos,
);
}
if self.chars[pos] == '\\' {
let n = self.len();
if pos + 1 >= n {
return (
Node::Macro(MacroNode {
span: Span { pos, len: 1 },
macroname: String::new(),
args: Vec::new(),
}),
pos + 1,
);
}
let next = self.chars[pos + 1];
if next.is_ascii_alphabetic() || next == '@' {
let mut i = pos + 1;
while i < n && (self.chars[i].is_ascii_alphabetic() || self.chars[i] == '@') {
i += 1;
}
let end = self.skip_tex_whitespace(i);
return (
Node::Macro(MacroNode {
span: Span {
pos,
len: end - pos,
},
macroname: self.chars[pos + 1..i].iter().collect(),
args: Vec::new(),
}),
end,
);
}
return (
Node::Macro(MacroNode {
span: Span { pos, len: 2 },
macroname: next.to_string(),
args: Vec::new(),
}),
pos + 2,
);
}
(
Node::Chars(CharsNode {
span: Span { pos, len: 1 },
chars: self.chars[pos].to_string(),
}),
pos + 1,
)
}
}