use std::collections::HashSet;
use crate::document::{EureDocument, NodeId};
use crate::prelude_internal::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SourceId(pub usize);
#[derive(Debug, Clone, Default)]
pub struct EureSource {
pub leading_trivia: Vec<Trivia>,
pub value: Option<NodeId>,
pub bindings: Vec<BindingSource>,
pub sections: Vec<SectionSource>,
pub trailing_trivia: Vec<Trivia>,
}
#[derive(Debug, Clone)]
pub struct BindingSource {
pub trivia_before: Vec<Trivia>,
pub path: SourcePath,
pub bind: BindSource,
pub trailing_comment: Option<Comment>,
}
#[derive(Debug, Clone)]
pub enum BindSource {
Value(NodeId),
Array {
node: NodeId,
elements: Vec<ArrayElementSource>,
},
Block(SourceId),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ArrayElementSource {
pub trivia_before: Vec<Trivia>,
pub index: usize,
pub trailing_comment: Option<Comment>,
}
#[derive(Debug, Clone)]
pub struct SectionSource {
pub trivia_before: Vec<Trivia>,
pub path: SourcePath,
pub body: SectionBody,
pub trailing_comment: Option<Comment>,
}
#[derive(Debug, Clone)]
pub enum SectionBody {
Items {
value: Option<NodeId>,
bindings: Vec<BindingSource>,
},
Block(SourceId),
}
#[derive(Debug, Clone)]
pub struct SourceDocument {
pub document: EureDocument,
pub sources: Vec<EureSource>,
pub root: SourceId,
pub multiline_arrays: HashSet<NodeId>,
}
impl SourceDocument {
#[must_use]
pub fn new(document: EureDocument, sources: Vec<EureSource>) -> Self {
Self {
document,
sources,
root: SourceId(0),
multiline_arrays: HashSet::new(),
}
}
pub fn empty() -> Self {
Self {
document: EureDocument::new_empty(),
sources: vec![EureSource::default()],
root: SourceId(0),
multiline_arrays: HashSet::new(),
}
}
pub fn mark_multiline_array(&mut self, node_id: NodeId) {
self.multiline_arrays.insert(node_id);
}
pub fn is_multiline_array(&self, node_id: NodeId) -> bool {
self.multiline_arrays.contains(&node_id)
}
pub fn document(&self) -> &EureDocument {
&self.document
}
pub fn document_mut(&mut self) -> &mut EureDocument {
&mut self.document
}
pub fn root_source(&self) -> &EureSource {
&self.sources[self.root.0]
}
pub fn source(&self, id: SourceId) -> &EureSource {
&self.sources[id.0]
}
pub fn source_mut(&mut self, id: SourceId) -> &mut EureSource {
&mut self.sources[id.0]
}
}
pub type SourcePath = Vec<SourcePathSegment>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourcePathSegment {
pub key: SourceKey,
pub array: Option<crate::path::ArrayIndexKind>,
}
impl SourcePathSegment {
pub fn ident(name: Identifier) -> Self {
Self {
key: SourceKey::Ident(name),
array: None,
}
}
pub fn extension(name: Identifier) -> Self {
Self {
key: SourceKey::Extension(name),
array: None,
}
}
pub fn with_array_push(mut self) -> Self {
self.array = Some(crate::path::ArrayIndexKind::Push);
self
}
pub fn with_array_index(mut self, index: usize) -> Self {
self.array = Some(crate::path::ArrayIndexKind::Specific(index));
self
}
pub fn with_array_current(mut self) -> Self {
self.array = Some(crate::path::ArrayIndexKind::Current);
self
}
pub fn quoted_string(s: impl Into<String>) -> Self {
Self {
key: SourceKey::quoted(s),
array: None,
}
}
pub fn literal_string(s: impl Into<String>) -> Self {
Self {
key: SourceKey::literal(s),
array: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum StringStyle {
#[default]
Quoted,
Literal,
DelimitedLitStr(u8),
DelimitedCode(u8),
}
#[derive(Debug, Clone)]
pub enum SourceKey {
Ident(Identifier),
Extension(Identifier),
Hole(Option<Identifier>),
String(String, StringStyle),
Integer(i64),
Tuple(Vec<SourceKey>),
TupleIndex(u8),
}
impl PartialEq for SourceKey {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Ident(a), Self::Ident(b)) => a == b,
(Self::Extension(a), Self::Extension(b)) => a == b,
(Self::Hole(a), Self::Hole(b)) => a == b,
(Self::String(a, _), Self::String(b, _)) => a == b,
(Self::Integer(a), Self::Integer(b)) => a == b,
(Self::Tuple(a), Self::Tuple(b)) => a == b,
(Self::TupleIndex(a), Self::TupleIndex(b)) => a == b,
_ => false,
}
}
}
impl Eq for SourceKey {}
impl SourceKey {
pub fn hole(label: Option<Identifier>) -> Self {
SourceKey::Hole(label)
}
pub fn quoted(s: impl Into<String>) -> Self {
SourceKey::String(s.into(), StringStyle::Quoted)
}
pub fn literal(s: impl Into<String>) -> Self {
SourceKey::String(s.into(), StringStyle::Literal)
}
pub fn delimited_lit_str(s: impl Into<String>, level: u8) -> Self {
SourceKey::String(s.into(), StringStyle::DelimitedLitStr(level))
}
pub fn delimited_code(s: impl Into<String>, level: u8) -> Self {
SourceKey::String(s.into(), StringStyle::DelimitedCode(level))
}
}
impl From<Identifier> for SourceKey {
fn from(id: Identifier) -> Self {
SourceKey::Ident(id)
}
}
impl From<i64> for SourceKey {
fn from(n: i64) -> Self {
SourceKey::Integer(n)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Comment {
Line(String),
Block(String),
}
impl Comment {
pub fn line(s: impl Into<String>) -> Self {
Comment::Line(s.into())
}
pub fn block(s: impl Into<String>) -> Self {
Comment::Block(s.into())
}
pub fn text(&self) -> &str {
match self {
Comment::Line(s) | Comment::Block(s) => s,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Trivia {
Comment(Comment),
BlankLine,
}
impl Trivia {
pub fn line_comment(s: impl Into<String>) -> Self {
Trivia::Comment(Comment::Line(s.into()))
}
pub fn block_comment(s: impl Into<String>) -> Self {
Trivia::Comment(Comment::Block(s.into()))
}
pub fn blank_line() -> Self {
Trivia::BlankLine
}
}
impl From<Comment> for Trivia {
fn from(comment: Comment) -> Self {
Trivia::Comment(comment)
}
}
impl EureSource {
pub fn new() -> Self {
Self::default()
}
pub fn push_binding(&mut self, binding: BindingSource) {
self.bindings.push(binding);
}
pub fn push_section(&mut self, section: SectionSource) {
self.sections.push(section);
}
}
impl BindingSource {
pub fn value(path: SourcePath, node: NodeId) -> Self {
Self {
trivia_before: Vec::new(),
path,
bind: BindSource::Value(node),
trailing_comment: None,
}
}
pub fn block(path: SourcePath, source_id: SourceId) -> Self {
Self {
trivia_before: Vec::new(),
path,
bind: BindSource::Block(source_id),
trailing_comment: None,
}
}
pub fn with_trailing_comment(mut self, comment: Comment) -> Self {
self.trailing_comment = Some(comment);
self
}
pub fn with_trivia(mut self, trivia: Vec<Trivia>) -> Self {
self.trivia_before = trivia;
self
}
pub fn array(path: SourcePath, node: NodeId, elements: Vec<ArrayElementSource>) -> Self {
Self {
trivia_before: Vec::new(),
path,
bind: BindSource::Array { node, elements },
trailing_comment: None,
}
}
}
impl SectionSource {
pub fn items(path: SourcePath, value: Option<NodeId>, bindings: Vec<BindingSource>) -> Self {
Self {
trivia_before: Vec::new(),
path,
body: SectionBody::Items { value, bindings },
trailing_comment: None,
}
}
pub fn block(path: SourcePath, source_id: SourceId) -> Self {
Self {
trivia_before: Vec::new(),
path,
body: SectionBody::Block(source_id),
trailing_comment: None,
}
}
pub fn with_trailing_comment(mut self, comment: Comment) -> Self {
self.trailing_comment = Some(comment);
self
}
pub fn with_trivia(mut self, trivia: Vec<Trivia>) -> Self {
self.trivia_before = trivia;
self
}
}
impl ArrayElementSource {
pub fn new(index: usize) -> Self {
Self {
trivia_before: Vec::new(),
index,
trailing_comment: None,
}
}
pub fn with_trivia(mut self, trivia: Vec<Trivia>) -> Self {
self.trivia_before = trivia;
self
}
pub fn with_trailing_comment(mut self, comment: Comment) -> Self {
self.trailing_comment = Some(comment);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_source_path_segment_ident() {
let actual = SourcePathSegment::ident(Identifier::new_unchecked("foo"));
let expected = SourcePathSegment {
key: SourceKey::Ident(Identifier::new_unchecked("foo")),
array: None,
};
assert_eq!(actual, expected);
}
#[test]
fn test_source_path_segment_with_array_push() {
let actual = SourcePathSegment::ident(Identifier::new_unchecked("items")).with_array_push();
let expected = SourcePathSegment {
key: SourceKey::Ident(Identifier::new_unchecked("items")),
array: Some(crate::path::ArrayIndexKind::Push),
};
assert_eq!(actual, expected);
}
#[test]
fn test_source_path_segment_with_array_index() {
let actual =
SourcePathSegment::ident(Identifier::new_unchecked("items")).with_array_index(0);
let expected = SourcePathSegment {
key: SourceKey::Ident(Identifier::new_unchecked("items")),
array: Some(crate::path::ArrayIndexKind::Specific(0)),
};
assert_eq!(actual, expected);
}
#[test]
fn test_source_path_segment_with_array_current() {
let actual =
SourcePathSegment::ident(Identifier::new_unchecked("items")).with_array_current();
let expected = SourcePathSegment {
key: SourceKey::Ident(Identifier::new_unchecked("items")),
array: Some(crate::path::ArrayIndexKind::Current),
};
assert_eq!(actual, expected);
}
#[test]
fn test_binding_source_value() {
let path = vec![SourcePathSegment::ident(Identifier::new_unchecked("foo"))];
let binding = BindingSource::value(path.clone(), NodeId(1));
assert_eq!(binding.path, path);
assert!(matches!(binding.bind, BindSource::Value(NodeId(1))));
assert!(binding.trivia_before.is_empty());
}
#[test]
fn test_binding_source_block() {
let path = vec![SourcePathSegment::ident(Identifier::new_unchecked("user"))];
let binding = BindingSource::block(path.clone(), SourceId(1));
assert_eq!(binding.path, path);
assert!(matches!(binding.bind, BindSource::Block(SourceId(1))));
assert!(binding.trivia_before.is_empty());
}
#[test]
fn test_binding_with_trivia() {
let path = vec![SourcePathSegment::ident(Identifier::new_unchecked("foo"))];
let trivia = vec![Trivia::BlankLine, Trivia::line_comment("comment")];
let binding = BindingSource::value(path.clone(), NodeId(1)).with_trivia(trivia.clone());
assert_eq!(binding.trivia_before, trivia);
}
#[test]
fn test_section_source_items() {
let path = vec![SourcePathSegment::ident(Identifier::new_unchecked(
"server",
))];
let section = SectionSource::items(path.clone(), None, vec![]);
assert_eq!(section.path, path);
assert!(matches!(
section.body,
SectionBody::Items {
value: None,
bindings
} if bindings.is_empty()
));
assert!(section.trivia_before.is_empty());
}
#[test]
fn test_section_source_block() {
let path = vec![SourcePathSegment::ident(Identifier::new_unchecked(
"config",
))];
let section = SectionSource::block(path.clone(), SourceId(2));
assert_eq!(section.path, path);
assert!(matches!(section.body, SectionBody::Block(SourceId(2))));
assert!(section.trivia_before.is_empty());
}
#[test]
fn test_section_with_trivia() {
let path = vec![SourcePathSegment::ident(Identifier::new_unchecked(
"server",
))];
let trivia = vec![Trivia::BlankLine];
let section = SectionSource::items(path.clone(), None, vec![]).with_trivia(trivia.clone());
assert_eq!(section.trivia_before, trivia);
}
#[test]
fn test_source_document_empty() {
let doc = SourceDocument::empty();
assert_eq!(doc.sources.len(), 1);
assert_eq!(doc.root, SourceId(0));
assert!(doc.root_source().bindings.is_empty());
assert!(doc.root_source().sections.is_empty());
}
}