summaryrefslogtreecommitdiff
path: root/src/syntax/model.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/syntax/model.rs')
-rw-r--r--src/syntax/model.rs134
1 files changed, 134 insertions, 0 deletions
diff --git a/src/syntax/model.rs b/src/syntax/model.rs
new file mode 100644
index 00000000..4eb2abe0
--- /dev/null
+++ b/src/syntax/model.rs
@@ -0,0 +1,134 @@
+//! The syntax model.
+
+use std::any::Any;
+use std::fmt::Debug;
+use async_trait::async_trait;
+
+use crate::{Pass, Feedback};
+use crate::layout::{LayoutContext, Commands, Command};
+use super::span::{Spanned, SpanVec};
+
+/// Represents a parsed piece of source that can be layouted and in the future
+/// also be queried for information used for refactorings, autocomplete, etc.
+#[async_trait(?Send)]
+pub trait Model: Debug + ModelBounds {
+ /// Layout the model into a sequence of commands processed by a
+ /// [`ModelLayouter`](crate::layout::ModelLayouter).
+ async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>;
+}
+
+/// A tree representation of source code.
+#[derive(Debug, Default, Clone, PartialEq)]
+pub struct SyntaxModel {
+ /// The syntactical elements making up this model.
+ pub nodes: SpanVec<Node>,
+}
+
+impl SyntaxModel {
+ /// Create an empty syntax model.
+ pub fn new() -> SyntaxModel {
+ SyntaxModel { nodes: vec![] }
+ }
+
+ /// Add a node to the model.
+ pub fn add(&mut self, node: Spanned<Node>) {
+ self.nodes.push(node);
+ }
+}
+
+#[async_trait(?Send)]
+impl Model for SyntaxModel {
+ async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
+ Pass::new(vec![Command::LayoutSyntaxModel(self)], Feedback::new())
+ }
+}
+
+/// A node in the [syntax model](SyntaxModel).
+#[derive(Debug, Clone)]
+pub enum Node {
+ /// Whitespace containing less than two newlines.
+ Space,
+ /// Whitespace with more than two newlines.
+ Parbreak,
+ /// A forced line break.
+ Linebreak,
+ /// Plain text.
+ Text(String),
+ /// Lines of raw text.
+ Raw(Vec<String>),
+ /// Italics were enabled / disabled.
+ ToggleItalic,
+ /// Bolder was enabled / disabled.
+ ToggleBolder,
+ /// A submodel, typically a function invocation.
+ Model(Box<dyn Model>),
+}
+
+impl PartialEq for Node {
+ fn eq(&self, other: &Node) -> bool {
+ use Node::*;
+ match (self, other) {
+ (Space, Space) => true,
+ (Parbreak, Parbreak) => true,
+ (Linebreak, Linebreak) => true,
+ (Text(a), Text(b)) => a == b,
+ (Raw(a), Raw(b)) => a == b,
+ (ToggleItalic, ToggleItalic) => true,
+ (ToggleBolder, ToggleBolder) => true,
+ (Model(a), Model(b)) => a == b,
+ _ => false,
+ }
+ }
+}
+
+impl dyn Model {
+ /// Downcast this model to a concrete type implementing [`Model`].
+ pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static {
+ self.as_any().downcast_ref::<T>()
+ }
+}
+
+impl PartialEq for dyn Model {
+ fn eq(&self, other: &dyn Model) -> bool {
+ self.bound_eq(other)
+ }
+}
+
+impl Clone for Box<dyn Model> {
+ fn clone(&self) -> Self {
+ self.bound_clone()
+ }
+}
+
+/// This trait describes bounds necessary for types implementing [`Model`]. It is
+/// automatically implemented for all types that are [`Model`], [`PartialEq`],
+/// [`Clone`] and `'static`.
+///
+/// It is necessary to make models comparable and clonable.
+pub trait ModelBounds {
+ /// Convert into a `dyn Any`.
+ fn as_any(&self) -> &dyn Any;
+
+ /// Check for equality with another model.
+ fn bound_eq(&self, other: &dyn Model) -> bool;
+
+ /// Clone into a boxed model trait object.
+ fn bound_clone(&self) -> Box<dyn Model>;
+}
+
+impl<T> ModelBounds for T where T: Model + PartialEq + Clone + 'static {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn bound_eq(&self, other: &dyn Model) -> bool {
+ match other.as_any().downcast_ref::<Self>() {
+ Some(other) => self == other,
+ None => false,
+ }
+ }
+
+ fn bound_clone(&self) -> Box<dyn Model> {
+ Box::new(self.clone())
+ }
+}