summaryrefslogtreecommitdiff
path: root/support/texlab/src/syntax/latex/ast.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/syntax/latex/ast.rs')
-rw-r--r--support/texlab/src/syntax/latex/ast.rs529
1 files changed, 299 insertions, 230 deletions
diff --git a/support/texlab/src/syntax/latex/ast.rs b/support/texlab/src/syntax/latex/ast.rs
index 0af980b595..ab7c6863e1 100644
--- a/support/texlab/src/syntax/latex/ast.rs
+++ b/support/texlab/src/syntax/latex/ast.rs
@@ -1,11 +1,15 @@
-use crate::range::RangeExt;
-use crate::syntax::text::{Span, SyntaxNode};
-use itertools::Itertools;
-use lsp_types::Range;
-use std::sync::Arc;
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum LatexTokenKind {
+use crate::{
+ protocol::{Position, Range, RangeExt},
+ syntax::{
+ generic_ast::{Ast, AstNodeIndex},
+ text::{Span, SyntaxNode},
+ },
+};
+use serde::{Deserialize, Serialize};
+use std::ops::Deref;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub enum TokenKind {
Word,
Command,
Math,
@@ -16,14 +20,14 @@ pub enum LatexTokenKind {
EndOptions,
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexToken {
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Token {
pub span: Span,
- pub kind: LatexTokenKind,
+ pub kind: TokenKind,
}
-impl LatexToken {
- pub fn new(span: Span, kind: LatexTokenKind) -> Self {
+impl Token {
+ pub fn new(span: Span, kind: TokenKind) -> Self {
Self { span, kind }
}
@@ -32,314 +36,379 @@ impl LatexToken {
}
}
-impl SyntaxNode for LatexToken {
+impl SyntaxNode for Token {
fn range(&self) -> Range {
self.span.range()
}
}
-#[derive(Debug, PartialEq, Eq, Clone, Default)]
-pub struct LatexRoot {
- pub children: Vec<LatexContent>,
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub struct Root {
+ pub range: Range,
}
-impl LatexRoot {
- pub fn new(children: Vec<LatexContent>) -> Self {
- Self { children }
+impl SyntaxNode for Root {
+ fn range(&self) -> Range {
+ self.range
}
}
-impl SyntaxNode for LatexRoot {
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
+pub enum GroupKind {
+ Group,
+ Options,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Group {
+ pub range: Range,
+ pub left: Token,
+ pub right: Option<Token>,
+ pub kind: GroupKind,
+}
+
+impl SyntaxNode for Group {
fn range(&self) -> Range {
- if self.children.is_empty() {
- Range::new_simple(0, 0, 0, 0)
- } else {
- Range::new(
- self.children[0].start(),
- self.children[self.children.len() - 1].end(),
- )
- }
+ self.range
}
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum LatexContent {
- Group(Arc<LatexGroup>),
- Command(Arc<LatexCommand>),
- Text(Arc<LatexText>),
- Comma(Arc<LatexComma>),
- Math(Arc<LatexMath>),
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Command {
+ pub range: Range,
+ pub name: Token,
}
-impl LatexContent {
- pub fn accept<T: LatexVisitor>(&self, visitor: &mut T) {
- match self {
- LatexContent::Group(group) => visitor.visit_group(Arc::clone(&group)),
- LatexContent::Command(command) => visitor.visit_command(Arc::clone(&command)),
- LatexContent::Text(text) => visitor.visit_text(Arc::clone(&text)),
- LatexContent::Comma(comma) => visitor.visit_comma(Arc::clone(&comma)),
- LatexContent::Math(math) => visitor.visit_math(Arc::clone(&math)),
- }
+impl Command {
+ pub fn short_name_range(&self) -> Range {
+ Range::new_simple(
+ self.name.start().line,
+ self.name.start().character + 1,
+ self.name.end().line,
+ self.name.end().character,
+ )
}
}
-impl SyntaxNode for LatexContent {
+impl SyntaxNode for Command {
fn range(&self) -> Range {
- match self {
- LatexContent::Group(group) => group.range(),
- LatexContent::Command(command) => command.range(),
- LatexContent::Text(text) => text.range(),
- LatexContent::Comma(comma) => comma.range(),
- LatexContent::Math(math) => math.range(),
- }
+ self.range
}
}
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum LatexGroupKind {
- Group,
- Options,
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Text {
+ pub range: Range,
+ pub words: Vec<Token>,
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexGroup {
- pub range: Range,
- pub left: LatexToken,
- pub children: Vec<LatexContent>,
- pub right: Option<LatexToken>,
- pub kind: LatexGroupKind,
+impl SyntaxNode for Text {
+ fn range(&self) -> Range {
+ self.range
+ }
}
-impl LatexGroup {
- pub fn new(
- left: LatexToken,
- children: Vec<LatexContent>,
- right: Option<LatexToken>,
- kind: LatexGroupKind,
- ) -> Self {
- let end = if let Some(ref right) = right {
- right.end()
- } else if !children.is_empty() {
- children[children.len() - 1].end()
- } else {
- left.end()
- };
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Comma {
+ pub range: Range,
+ pub token: Token,
+}
- Self {
- range: Range::new(left.start(), end),
- left,
- children,
- right,
- kind,
- }
+impl SyntaxNode for Comma {
+ fn range(&self) -> Range {
+ self.range
}
}
-impl SyntaxNode for LatexGroup {
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub struct Math {
+ pub range: Range,
+ pub token: Token,
+}
+
+impl SyntaxNode for Math {
fn range(&self) -> Range {
self.range
}
}
+#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
+pub enum Node {
+ Root(Root),
+ Group(Group),
+ Command(Command),
+ Text(Text),
+ Comma(Comma),
+ Math(Math),
+}
+
+impl SyntaxNode for Node {
+ fn range(&self) -> Range {
+ match self {
+ Self::Root(root) => root.range(),
+ Self::Group(group) => group.range(),
+ Self::Command(cmd) => cmd.range(),
+ Self::Text(text) => text.range(),
+ Self::Comma(comma) => comma.range(),
+ Self::Math(math) => math.range(),
+ }
+ }
+}
+
#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexCommand {
- pub range: Range,
- pub name: LatexToken,
- pub options: Vec<Arc<LatexGroup>>,
- pub args: Vec<Arc<LatexGroup>>,
- pub groups: Vec<Arc<LatexGroup>>,
+pub struct Tree {
+ pub inner: Ast<Node>,
+ pub root: AstNodeIndex,
}
-impl LatexCommand {
- pub fn new(
- name: LatexToken,
- options: Vec<Arc<LatexGroup>>,
- args: Vec<Arc<LatexGroup>>,
- ) -> Self {
- let groups: Vec<Arc<LatexGroup>> = args
- .iter()
- .chain(options.iter())
- .sorted_by_key(|group| group.range.start)
- .map(Arc::clone)
- .collect();
-
- let end = if let Some(group) = groups.last() {
- group.end()
- } else {
- name.end()
- };
+impl Deref for Tree {
+ type Target = Ast<Node>;
- Self {
- range: Range::new(name.start(), end),
- name,
- options,
- args,
- groups,
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+impl Tree {
+ pub fn walk<V: Visitor>(&self, visitor: &mut V, parent: AstNodeIndex) {
+ for child in self.children(parent) {
+ visitor.visit(self, child);
}
}
- pub fn short_name_range(&self) -> Range {
- Range::new_simple(
- self.name.start().line,
- self.name.start().character + 1,
- self.name.end().line,
- self.name.end().character,
- )
+ pub fn find(&self, pos: Position) -> Vec<AstNodeIndex> {
+ let mut finder = Finder::new(pos);
+ finder.visit(self, self.root);
+ finder.results
}
- pub fn extract_text(&self, index: usize) -> Option<&LatexText> {
- if self.args.len() > index && self.args[index].children.len() == 1 {
- if let LatexContent::Text(ref text) = self.args[index].children[0] {
- Some(text)
- } else {
- None
- }
+ pub fn find_command_by_short_name_range(&self, pos: Position) -> Option<AstNodeIndex> {
+ self.find(pos).into_iter().find(|node| {
+ self.as_command(*node)
+ .filter(|cmd| {
+ cmd.name.range().contains(pos) && cmd.name.start().character != pos.character
+ })
+ .is_some()
+ })
+ }
+
+ pub fn print(&self, node: AstNodeIndex) -> String {
+ let start_position = self[node].start();
+ let mut printer = Printer::new(start_position);
+ printer.visit(self, node);
+ printer.output
+ }
+
+ pub fn commands<'a>(&'a self) -> impl Iterator<Item = AstNodeIndex> + 'a {
+ self.inner
+ .nodes()
+ .into_iter()
+ .filter(move |node| self.as_command(*node).is_some())
+ }
+
+ pub fn as_group(&self, node: AstNodeIndex) -> Option<&Group> {
+ if let Node::Group(group) = &self[node] {
+ Some(group)
} else {
None
}
}
- pub fn extract_word(&self, index: usize) -> Option<&LatexToken> {
- let text = self.extract_text(index)?;
- if text.words.len() == 1 {
- Some(&text.words[0])
+ pub fn as_command(&self, node: AstNodeIndex) -> Option<&Command> {
+ if let Node::Command(cmd) = &self[node] {
+ Some(cmd)
} else {
None
}
}
- pub fn has_word(&self, index: usize) -> bool {
- self.extract_word(index).is_some()
- }
-
- pub fn extract_comma_separated_words(&self, index: usize) -> Vec<&LatexToken> {
- let mut words = Vec::new();
- for child in &self.args[index].children {
- if let LatexContent::Text(text) = child {
- for word in &text.words {
- words.push(word);
- }
- }
+ pub fn as_text(&self, node: AstNodeIndex) -> Option<&Text> {
+ if let Node::Text(text) = &self[node] {
+ Some(text)
+ } else {
+ None
}
- words
}
- pub fn has_comma_separated_words(&self, index: usize) -> bool {
- if self.args.len() <= index {
- return false;
- }
-
- for node in &self.args[index].children {
- match node {
- LatexContent::Text(_) | LatexContent::Comma(_) => (),
- LatexContent::Command(_) | LatexContent::Group(_) | LatexContent::Math(_) => {
- return false;
- }
- }
+ pub fn as_math(&self, node: AstNodeIndex) -> Option<&Math> {
+ if let Node::Math(math) = &self[node] {
+ Some(math)
+ } else {
+ None
}
- true
}
-}
-impl SyntaxNode for LatexCommand {
- fn range(&self) -> Range {
- self.range
+ pub fn extract_group(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<AstNodeIndex> {
+ self.children(parent)
+ .filter(|child| {
+ self.as_group(*child)
+ .filter(|group| group.kind == group_kind)
+ .is_some()
+ })
+ .nth(index)
}
-}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexText {
- pub range: Range,
- pub words: Vec<LatexToken>,
-}
-
-impl LatexText {
- pub fn new(words: Vec<LatexToken>) -> Self {
- Self {
- range: Range::new(words[0].start(), words[words.len() - 1].end()),
- words,
+ pub fn extract_text(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<&Text> {
+ let group = self.extract_group(parent, group_kind, index)?;
+ let mut contents = self.children(group);
+ let text = self.as_text(contents.next()?);
+ if contents.next().is_none() {
+ text
+ } else {
+ None
}
}
-}
-impl SyntaxNode for LatexText {
- fn range(&self) -> Range {
- self.range
+ pub fn extract_word(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<&Token> {
+ let text = self.extract_text(parent, group_kind, index)?;
+ if text.words.len() == 1 {
+ Some(&text.words[0])
+ } else {
+ None
+ }
}
-}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexComma {
- pub token: LatexToken,
-}
+ pub fn extract_comma_separated_words(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<Vec<&Token>> {
+ let group = self.extract_group(parent, group_kind, index)?;
+ let mut words = Vec::new();
+ for child in self.children(group) {
+ match &self[child] {
+ Node::Root(_) | Node::Group(_) | Node::Command(_) | Node::Math(_) => return None,
+ Node::Text(text) => {
+ for word in &text.words {
+ words.push(word);
+ }
+ }
+ Node::Comma(_) => (),
+ }
+ }
+ Some(words)
+ }
-impl LatexComma {
- pub fn new(token: LatexToken) -> Self {
- Self { token }
+ pub fn print_group_content(
+ &self,
+ parent: AstNodeIndex,
+ group_kind: GroupKind,
+ index: usize,
+ ) -> Option<String> {
+ let arg = self.extract_group(parent, group_kind, index)?;
+ let text = self.print(arg);
+ self.as_group(arg)?.right.as_ref()?;
+ Some(text[1..text.len() - 1].trim().into())
}
}
-impl SyntaxNode for LatexComma {
- fn range(&self) -> Range {
- self.token.range()
- }
+pub trait Visitor {
+ fn visit(&mut self, tree: &Tree, node: AstNodeIndex);
}
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct LatexMath {
- pub token: LatexToken,
+#[derive(Debug)]
+struct Finder {
+ position: Position,
+ results: Vec<AstNodeIndex>,
}
-impl LatexMath {
- pub fn new(token: LatexToken) -> Self {
- Self { token }
+impl Finder {
+ fn new(position: Position) -> Self {
+ Self {
+ position,
+ results: Vec::new(),
+ }
}
}
-impl SyntaxNode for LatexMath {
- fn range(&self) -> Range {
- self.token.range()
+impl Visitor for Finder {
+ fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
+ if tree[node].range().contains(self.position) {
+ self.results.push(node);
+ tree.walk(self, node);
+ }
}
}
-pub trait LatexVisitor {
- fn visit_root(&mut self, root: Arc<LatexRoot>);
-
- fn visit_group(&mut self, group: Arc<LatexGroup>);
-
- fn visit_command(&mut self, command: Arc<LatexCommand>);
-
- fn visit_text(&mut self, text: Arc<LatexText>);
-
- fn visit_comma(&mut self, comma: Arc<LatexComma>);
-
- fn visit_math(&mut self, math: Arc<LatexMath>);
+#[derive(Debug)]
+struct Printer {
+ output: String,
+ position: Position,
}
-pub struct LatexWalker;
-
-impl LatexWalker {
- pub fn walk_root<T: LatexVisitor>(visitor: &mut T, root: Arc<LatexRoot>) {
- for child in &root.children {
- child.accept(visitor);
+impl Printer {
+ fn new(start_position: Position) -> Self {
+ Self {
+ output: String::new(),
+ position: start_position,
}
}
- pub fn walk_group<T: LatexVisitor>(visitor: &mut T, group: Arc<LatexGroup>) {
- for child in &group.children {
- child.accept(visitor);
+ fn synchronize(&mut self, position: Position) {
+ while self.position.line < position.line {
+ self.output.push('\n');
+ self.position.line += 1;
+ self.position.character = 0;
}
- }
- pub fn walk_command<T: LatexVisitor>(visitor: &mut T, command: Arc<LatexCommand>) {
- for arg in &command.groups {
- visitor.visit_group(Arc::clone(&arg));
+ while self.position.character < position.character {
+ self.output.push(' ');
+ self.position.character += 1;
}
- }
- pub fn walk_text<T: LatexVisitor>(_visitor: &mut T, _text: Arc<LatexText>) {}
+ assert_eq!(self.position, position);
+ }
- pub fn walk_comma<T: LatexVisitor>(_visitor: &mut T, _comma: Arc<LatexComma>) {}
+ fn print_token(&mut self, token: &Token) {
+ self.synchronize(token.start());
+ self.output.push_str(token.text());
+ self.position.character += token.end().character - token.start().character;
+ self.synchronize(token.end());
+ }
+}
- pub fn walk_math<T: LatexVisitor>(_visitor: &mut T, _math: Arc<LatexMath>) {}
+impl Visitor for Printer {
+ fn visit(&mut self, tree: &Tree, node: AstNodeIndex) {
+ match &tree[node] {
+ Node::Root(_) => tree.walk(self, node),
+ Node::Group(group) => {
+ self.print_token(&group.left);
+ tree.walk(self, node);
+ if let Some(right) = &group.right {
+ self.print_token(right);
+ }
+ }
+ Node::Command(cmd) => {
+ self.print_token(&cmd.name);
+ tree.walk(self, node);
+ }
+ Node::Text(text) => {
+ for word in &text.words {
+ self.print_token(word);
+ }
+ }
+ Node::Comma(comma) => {
+ self.print_token(&comma.token);
+ }
+ Node::Math(math) => {
+ self.print_token(&math.token);
+ }
+ }
+ }
}