diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-08-02 22:05:49 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-08-02 22:05:49 +0200 |
| commit | 266d457292e7461d448f9141030028ea68b573d1 (patch) | |
| tree | ff3ff3cc289d34040db421b6a7faa1f2aa402b05 /src/syntax | |
| parent | cbbc46215fe0a0ad8a50e991ec442890b8eadc0a (diff) | |
Refactor model into tree 🛒
Diffstat (limited to 'src/syntax')
| -rw-r--r-- | src/syntax/expr.rs | 6 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 4 | ||||
| -rw-r--r-- | src/syntax/model.rs | 134 | ||||
| -rw-r--r-- | src/syntax/parsing.rs | 76 | ||||
| -rw-r--r-- | src/syntax/scope.rs | 12 | ||||
| -rw-r--r-- | src/syntax/test.rs | 18 | ||||
| -rw-r--r-- | src/syntax/tokens.rs | 4 | ||||
| -rw-r--r-- | src/syntax/tree.rs | 100 |
8 files changed, 157 insertions, 197 deletions
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index a551c2b6..0a9ab149 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -7,7 +7,7 @@ use std::u8; use crate::Feedback; use crate::length::Length; -use super::span::Spanned; +use super::span::{Spanned, SpanVec}; use super::tokens::is_identifier; use super::value::Value; @@ -237,7 +237,7 @@ impl fmt::Display for ParseColorError { /// (false, 12cm, "hi") /// ``` #[derive(Default, Clone, PartialEq)] -pub struct Tuple(pub Vec<Spanned<Expr>>); +pub struct Tuple(pub SpanVec<Expr>); impl Tuple { /// Create an empty tuple. @@ -333,7 +333,7 @@ impl Deref for NamedTuple { /// { fit: false, width: 12cm, items: (1, 2, 3) } /// ``` #[derive(Default, Clone, PartialEq)] -pub struct Object(pub Vec<Spanned<Pair>>); +pub struct Object(pub SpanVec<Pair>); /// A key-value pair in an object. #[derive(Debug, Clone, PartialEq)] diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index e844fdf1..86c2fd24 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -1,4 +1,4 @@ -//! Syntax models, parsing and tokenization. +//! Syntax trees, parsing and tokenization. #[cfg(test)] #[macro_use] @@ -6,7 +6,7 @@ mod test; pub mod decoration; pub mod expr; -pub mod model; +pub mod tree; pub mod parsing; pub mod span; pub mod scope; diff --git a/src/syntax/model.rs b/src/syntax/model.rs deleted file mode 100644 index 4eb2abe0..00000000 --- a/src/syntax/model.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! 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()) - } -} diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 75e30177..7594c14d 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -1,4 +1,4 @@ -//! Parsing of source code into syntax models. +//! Parsing of source code into syntax trees. use std::str::FromStr; @@ -8,10 +8,10 @@ use super::expr::*; use super::scope::Scope; use super::span::{Pos, Span, Spanned}; use super::tokens::{is_newline_char, Token, Tokens, TokenMode}; -use super::model::{SyntaxModel, Node, Model}; +use super::tree::{SyntaxTree, SyntaxNode, DynamicNode}; -/// A function which parses a function call into a model. -pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn Model>>; +/// A function which parses a function call into a tree. +pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn DynamicNode>>; /// An invocation of a function. #[derive(Debug, Clone, PartialEq)] @@ -73,12 +73,12 @@ pub struct ParseState { /// Parse a string of source code. /// -/// All spans in the resulting model and feedback are offset by the given +/// All spans in the resulting tree and feedback are offset by the given /// `offset` position. This is used to make spans of a function body relative to /// the start of the function as a whole as opposed to the start of the /// function's body. -pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> { - let mut model = SyntaxModel::new(); +pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> { + let mut tree = SyntaxTree::new(); let mut feedback = Feedback::new(); for token in Tokens::new(src, offset, TokenMode::Body) { @@ -87,9 +87,9 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> { // Starting from two newlines counts as a paragraph break, a single // newline does not. Token::Space(newlines) => if newlines >= 2 { - Node::Parbreak + SyntaxNode::Parbreak } else { - Node::Space + SyntaxNode::Space } Token::Function { header, body, terminated } => { @@ -103,19 +103,19 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> { parsed.output } - Token::Star => Node::ToggleBolder, - Token::Underscore => Node::ToggleItalic, - Token::Backslash => Node::Linebreak, + Token::Star => SyntaxNode::ToggleBolder, + Token::Underscore => SyntaxNode::ToggleItalic, + Token::Backslash => SyntaxNode::Linebreak, Token::Raw { raw, terminated } => { if !terminated { error!(@feedback, Span::at(span.end), "expected backtick"); } - Node::Raw(unescape_raw(raw)) + SyntaxNode::Raw(unescape_raw(raw)) } - Token::Text(text) => Node::Text(text.to_string()), + Token::Text(text) => SyntaxNode::Text(text.to_string()), Token::LineComment(_) | Token::BlockComment(_) => continue, unexpected => { @@ -124,10 +124,10 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> { } }; - model.add(Spanned::new(node, span)); + tree.push(Spanned::new(node, span)); } - Pass::new(model, feedback) + Pass::new(tree, feedback) } struct FuncParser<'s> { @@ -164,7 +164,7 @@ impl<'s> FuncParser<'s> { } } - fn parse(mut self) -> Pass<Node> { + fn parse(mut self) -> Pass<SyntaxNode> { let (parser, header) = if let Some(header) = self.parse_func_header() { let name = header.name.v.as_str(); let (parser, deco) = match self.state.scope.get_parser(name) { @@ -197,9 +197,8 @@ impl<'s> FuncParser<'s> { let call = FuncCall { header, body: self.body }; let parsed = parser(call, self.state); - self.feedback.extend(parsed.feedback); - Pass::new(Node::Model(parsed.output), self.feedback) + Pass::new(SyntaxNode::Dyn(parsed.output), self.feedback) } fn parse_func_header(&mut self) -> Option<FuncHeader> { @@ -662,26 +661,27 @@ fn unescape_raw(raw: &str) -> Vec<String> { #[allow(non_snake_case)] mod tests { use crate::length::Length; - use super::super::test::{check, DebugFn}; + use crate::syntax::span::SpanVec; + use crate::syntax::test::{check, DebugFn}; use super::*; use Decoration::*; use Expr::{Number as Num, Length as Len, Bool}; - use Node::{ + use SyntaxNode::{ Space as S, ToggleItalic as Italic, ToggleBolder as Bold, Parbreak, Linebreak, }; /// Test whether the given string parses into - /// - the given node list (required). + /// - the given SyntaxNode list (required). /// - the given error list (optional, if omitted checks against empty list). /// - the given decoration list (optional, if omitted it is not tested). macro_rules! p { - ($source:expr => [$($model:tt)*]) => { - p!($source => [$($model)*], []); + ($source:expr => [$($tree:tt)*]) => { + p!($source => [$($tree)*], []); }; - ($source:expr => [$($model:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => { + ($source:expr => [$($tree:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => { let mut scope = Scope::new::<DebugFn>(); scope.add::<DebugFn>("f"); scope.add::<DebugFn>("n"); @@ -691,9 +691,9 @@ mod tests { let state = ParseState { scope }; let pass = parse($source, Pos::ZERO, &state); - // Test model. - let (exp, cmp) = span_vec![$($model)*]; - check($source, exp, pass.output.nodes, cmp); + // Test tree. + let (exp, cmp) = span_vec![$($tree)*]; + check($source, exp, pass.output, cmp); // Test diagnostics. let (exp, cmp) = span_vec![$($diagnostics)*]; @@ -728,7 +728,7 @@ mod tests { fn Sub(e1: Expr, e2: Expr) -> Expr { Expr::Sub(Box::new(Z(e1)), Box::new(Z(e2))) } fn Mul(e1: Expr, e2: Expr) -> Expr { Expr::Mul(Box::new(Z(e1)), Box::new(Z(e2))) } fn Div(e1: Expr, e2: Expr) -> Expr { Expr::Div(Box::new(Z(e1)), Box::new(Z(e2))) } - fn T(text: &str) -> Node { Node::Text(text.to_string()) } + fn T(text: &str) -> SyntaxNode { SyntaxNode::Text(text.to_string()) } fn Z<T>(v: T) -> Spanned<T> { Spanned::zero(v) } macro_rules! tuple { @@ -757,7 +757,7 @@ mod tests { macro_rules! raw { ($($line:expr),* $(,)?) => { - Node::Raw(vec![$($line.to_string()),*]) + SyntaxNode::Raw(vec![$($line.to_string()),*]) }; } @@ -769,7 +769,7 @@ mod tests { #[allow(unused_mut)] let mut args = FuncArgs::new(); $( - let items: Vec<Spanned<Expr>> = span_vec![$($pos)*].0; + let items: SpanVec<Expr> = span_vec![$($pos)*].0; for item in items { args.push(item.map(|v| FuncArg::Pos(v))); } @@ -778,7 +778,7 @@ mod tests { value: Z($value), })));)*)? )? - Node::Model(Box::new(DebugFn { + SyntaxNode::Dyn(Box::new(DebugFn { header: FuncHeader { name: span_item!($name).map(|s| Ident(s.to_string())), args, @@ -786,7 +786,7 @@ mod tests { body: func!(@body $($($body)*)?), })) }}; - (@body [$($body:tt)*]) => { Some(SyntaxModel { nodes: span_vec![$($body)*].0 }) }; + (@body [$($body:tt)*]) => { Some(span_vec![$($body)*].0) }; (@body) => { None }; } @@ -818,8 +818,8 @@ mod tests { #[test] fn unescape_raws() { - fn test(raw: &str, expected: Node) { - let vec = if let Node::Raw(v) = expected { v } else { panic!() }; + fn test(raw: &str, expected: SyntaxNode) { + let vec = if let SyntaxNode::Raw(v) = expected { v } else { panic!() }; assert_eq!(unescape_raw(raw), vec); } @@ -834,8 +834,8 @@ mod tests { } #[test] - fn parse_basic_nodes() { - // Basic nodes. + fn parse_basic_SyntaxNodes() { + // Basic SyntaxNodes. p!("" => []); p!("hi" => [T("hi")]); p!("*hi" => [Bold, T("hi")]); @@ -855,7 +855,7 @@ mod tests { p!("`hi\nyou" => [raw!["hi", "you"]], [(1:3, 1:3, "expected backtick")]); p!("`hi\\`du`" => [raw!["hi`du"]]); - // Spanned nodes. + // Spanned SyntaxNodes. p!("Hi" => [(0:0, 0:2, T("Hi"))]); p!("*Hi*" => [(0:0, 0:1, Bold), (0:1, 0:3, T("Hi")), (0:3, 0:4, Bold)]); p!("🌎\n*/[n]" => diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs index 83b9fdee..d3092944 100644 --- a/src/syntax/scope.rs +++ b/src/syntax/scope.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter}; use crate::func::ParseFunc; use super::parsing::CallParser; -use super::model::Model; +use super::tree::DynamicNode; /// A map from identifiers to function parsers. pub struct Scope { @@ -17,7 +17,7 @@ impl Scope { /// Create a new empty scope with a fallback parser that is invoked when no /// match is found. pub fn new<F>() -> Scope - where F: ParseFunc<Meta=()> + Model + 'static { + where F: ParseFunc<Meta=()> + DynamicNode + 'static { Scope { parsers: HashMap::new(), fallback: make_parser::<F>(()), @@ -31,14 +31,14 @@ impl Scope { /// Associate the given name with a type that is parseable into a function. pub fn add<F>(&mut self, name: &str) - where F: ParseFunc<Meta=()> + Model + 'static { + where F: ParseFunc<Meta=()> + DynamicNode + 'static { self.add_with_meta::<F>(name, ()); } /// Add a parseable type with additional metadata that is given to the /// parser (other than the default of `()`). pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta) - where F: ParseFunc + Model + 'static { + where F: ParseFunc + DynamicNode + 'static { self.parsers.insert( name.to_string(), make_parser::<F>(metadata), @@ -65,9 +65,9 @@ impl Debug for Scope { } fn make_parser<F>(metadata: <F as ParseFunc>::Meta) -> Box<CallParser> -where F: ParseFunc + Model + 'static { +where F: ParseFunc + DynamicNode + 'static { Box::new(move |f, s| { F::parse(f, s, metadata.clone()) - .map(|model| Box::new(model) as Box<dyn Model>) + .map(|tree| Box::new(tree) as Box<dyn DynamicNode>) }) } diff --git a/src/syntax/test.rs b/src/syntax/test.rs index 38a124aa..b701e577 100644 --- a/src/syntax/test.rs +++ b/src/syntax/test.rs @@ -5,7 +5,7 @@ use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair}; use super::parsing::{FuncHeader, FuncArgs, FuncArg}; use super::span::Spanned; use super::tokens::Token; -use super::model::{SyntaxModel, Model, Node}; +use super::tree::{SyntaxTree, SyntaxNode, DynamicNode}; /// Check whether the expected and found results are the same. pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool) @@ -62,7 +62,7 @@ function! { #[derive(Debug, Clone, PartialEq)] pub struct DebugFn { pub header: FuncHeader, - pub body: Option<SyntaxModel>, + pub body: Option<SyntaxTree>, } parse(header, body, state, f) { @@ -83,20 +83,14 @@ pub trait SpanlessEq<Rhs=Self> { fn spanless_eq(&self, other: &Rhs) -> bool; } -impl SpanlessEq for SyntaxModel { - fn spanless_eq(&self, other: &SyntaxModel) -> bool { - self.nodes.spanless_eq(&other.nodes) - } -} - -impl SpanlessEq for Node { - fn spanless_eq(&self, other: &Node) -> bool { - fn downcast<'a>(func: &'a (dyn Model + 'static)) -> &'a DebugFn { +impl SpanlessEq for SyntaxNode { + fn spanless_eq(&self, other: &SyntaxNode) -> bool { + fn downcast<'a>(func: &'a (dyn DynamicNode + 'static)) -> &'a DebugFn { func.downcast::<DebugFn>().expect("not a debug fn") } match (self, other) { - (Node::Model(a), Node::Model(b)) => { + (SyntaxNode::Dyn(a), SyntaxNode::Dyn(b)) => { downcast(a.as_ref()).spanless_eq(downcast(b.as_ref())) } (a, b) => a == b, diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index a0af5a02..1ea11449 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -83,8 +83,8 @@ pub enum Token<'s> { ExprHex(&'s str), /// A plus in a function header, signifying the addition of expressions. Plus, - /// A hyphen in a function header, - /// signifying the subtraction of expressions. + /// A hyphen in a function header, signifying the subtraction of + /// expressions. Hyphen, /// A slash in a function header, signifying the division of expressions. Slash, diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs new file mode 100644 index 00000000..41a03fae --- /dev/null +++ b/src/syntax/tree.rs @@ -0,0 +1,100 @@ +//! The syntax tree. + +use std::any::Any; +use std::fmt::Debug; + +use crate::layout::Layout; +use super::span::SpanVec; + +/// A list of nodes which forms a tree together with the nodes' children. +pub type SyntaxTree = SpanVec<SyntaxNode>; + +/// A syntax node, which encompasses a single logical entity of parsed source +/// code. +#[derive(Debug, Clone)] +pub enum SyntaxNode { + /// 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 subtree, typically a function invocation. + Dyn(Box<dyn DynamicNode>), +} + +impl PartialEq for SyntaxNode { + fn eq(&self, other: &SyntaxNode) -> bool { + use SyntaxNode::*; + 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, + (Dyn(a), Dyn(b)) => a == b, + _ => false, + } + } +} + +/// Dynamic syntax nodes. +/// +/// *Note*: This is automatically implemented for all types which are +/// `Debug + Clone + PartialEq`, `Layout` and `'static`. +pub trait DynamicNode: Debug + Layout { + /// Convert into a `dyn Any`. + fn as_any(&self) -> &dyn Any; + + /// Check for equality with another dynamic node. + fn dyn_eq(&self, other: &dyn DynamicNode) -> bool; + + /// Clone into a boxed node trait object. + fn box_clone(&self) -> Box<dyn DynamicNode>; +} + +impl dyn DynamicNode { + /// Downcast this dynamic node to a concrete node. + pub fn downcast<N>(&self) -> Option<&N> where N: DynamicNode + 'static { + self.as_any().downcast_ref::<N>() + } +} + +impl PartialEq for dyn DynamicNode { + fn eq(&self, other: &dyn DynamicNode) -> bool { + self.dyn_eq(other) + } +} + +impl Clone for Box<dyn DynamicNode> { + fn clone(&self) -> Self { + self.box_clone() + } +} + +impl<T> DynamicNode for T where T: Debug + PartialEq + Clone + Layout + 'static { + fn as_any(&self) -> &dyn Any { + self + } + + fn dyn_eq(&self, other: &dyn DynamicNode) -> bool { + match other.as_any().downcast_ref::<Self>() { + Some(other) => self == other, + None => false, + } + } + + fn box_clone(&self) -> Box<dyn DynamicNode> { + Box::new(self.clone()) + } +} |
