use crate::document::constructor::{DocumentConstructor, Scope};
use crate::document::interpreter_sink::InterpreterSink;
use crate::document::{ConstructorError, InsertError, NodeId};
use crate::path::PathSegment;
use crate::prelude_internal::*;
use crate::source::{
BindingSource, Comment, EureSource, SectionSource, SourceDocument, SourceId, SourceKey,
SourcePath, SourcePathSegment, Trivia,
};
#[derive(Debug)]
enum BuilderContext {
EureBlock {
source_id: SourceId,
saved_path: SourcePath,
saved_trivia: Vec<Trivia>,
},
SectionItems {
trivia_before: Vec<Trivia>,
path: SourcePath,
value: Option<NodeId>,
bindings: Vec<BindingSource>,
},
}
pub struct SourceConstructor {
inner: DocumentConstructor,
sources: Vec<EureSource>,
builder_stack: Vec<BuilderContext>,
pending_path: Vec<SourcePathSegment>,
pending_trivia: Vec<Trivia>,
last_bound_node: Option<NodeId>,
last_block_id: Option<SourceId>,
}
impl Default for SourceConstructor {
fn default() -> Self {
Self::new()
}
}
impl SourceConstructor {
#[must_use]
pub fn new() -> Self {
let sources = vec![EureSource::default()];
Self {
inner: DocumentConstructor::new(),
sources,
builder_stack: vec![BuilderContext::EureBlock {
source_id: SourceId(0),
saved_path: Vec::new(),
saved_trivia: Vec::new(),
}],
pending_path: Vec::new(),
pending_trivia: Vec::new(),
last_bound_node: None,
last_block_id: None,
}
}
#[must_use]
pub fn finish(mut self) -> SourceDocument {
if !self.pending_trivia.is_empty() {
self.sources[0].trailing_trivia = std::mem::take(&mut self.pending_trivia);
}
SourceDocument::new(self.inner.finish(), self.sources)
}
fn current_source_mut(&mut self) -> &mut EureSource {
for ctx in self.builder_stack.iter().rev() {
if let BuilderContext::EureBlock { source_id, .. } = ctx {
return &mut self.sources[source_id.0];
}
}
&mut self.sources[0]
}
pub fn begin_scope(&mut self) -> Scope {
InterpreterSink::begin_scope(self)
}
pub fn end_scope(&mut self, scope: Scope) -> Result<(), InsertError> {
InterpreterSink::end_scope(self, scope)
}
pub fn navigate(&mut self, segment: PathSegment) -> Result<NodeId, InsertError> {
InterpreterSink::navigate(self, segment)
}
pub fn navigate_partial_map_entry(
&mut self,
key: crate::value::PartialObjectKey,
) -> Result<NodeId, InsertError> {
self.pending_path.push(SourcePathSegment {
key: Self::partial_object_key_to_source_key(&key),
array: None,
});
self.inner.navigate_partial_map_entry(key)
}
pub fn require_hole(&self) -> Result<(), InsertError> {
InterpreterSink::require_hole(self)
}
pub fn bind_primitive(&mut self, value: PrimitiveValue) -> Result<(), InsertError> {
InterpreterSink::bind_primitive(self, value)
}
pub fn bind_hole(&mut self, label: Option<Identifier>) -> Result<(), InsertError> {
InterpreterSink::bind_hole(self, label)
}
pub fn bind_empty_map(&mut self) -> Result<(), InsertError> {
InterpreterSink::bind_empty_map(self)
}
pub fn bind_empty_partial_map(&mut self) -> Result<(), InsertError> {
self.last_bound_node = Some(self.inner.current_node_id());
self.inner.bind_empty_partial_map()
}
pub fn bind_empty_array(&mut self) -> Result<(), InsertError> {
InterpreterSink::bind_empty_array(self)
}
pub fn bind_empty_tuple(&mut self) -> Result<(), InsertError> {
InterpreterSink::bind_empty_tuple(self)
}
pub fn bind_from(&mut self, value: impl Into<PrimitiveValue>) -> Result<(), InsertError> {
InterpreterSink::bind_from(self, value)
}
pub fn current_node_id(&self) -> NodeId {
InterpreterSink::current_node_id(self)
}
pub fn current_path(&self) -> &[PathSegment] {
InterpreterSink::current_path(self)
}
pub fn document(&self) -> &EureDocument {
InterpreterSink::document(self)
}
pub fn document_mut(&mut self) -> &mut EureDocument {
InterpreterSink::document_mut(self)
}
pub fn begin_eure_block(&mut self) {
InterpreterSink::begin_eure_block(self)
}
pub fn set_block_value(&mut self) -> Result<(), InsertError> {
InterpreterSink::set_block_value(self)
}
pub fn end_eure_block(&mut self) -> Result<(), InsertError> {
InterpreterSink::end_eure_block(self)
}
pub fn begin_binding(&mut self) {
InterpreterSink::begin_binding(self)
}
pub fn end_binding_value(&mut self) -> Result<(), InsertError> {
InterpreterSink::end_binding_value(self)
}
pub fn end_binding_block(&mut self) -> Result<(), InsertError> {
InterpreterSink::end_binding_block(self)
}
pub fn begin_section(&mut self) {
InterpreterSink::begin_section(self)
}
pub fn begin_section_items(&mut self) {
InterpreterSink::begin_section_items(self)
}
pub fn end_section_items(&mut self) -> Result<(), InsertError> {
InterpreterSink::end_section_items(self)
}
pub fn end_section_block(&mut self) -> Result<(), InsertError> {
InterpreterSink::end_section_block(self)
}
pub fn comment(&mut self, comment: Comment) {
InterpreterSink::comment(self, comment)
}
pub fn blank_line(&mut self) {
InterpreterSink::blank_line(self)
}
pub fn add_trivia(&mut self, trivia: Trivia) {
self.pending_trivia.push(trivia);
}
fn path_segment_to_source(segment: &PathSegment) -> SourcePathSegment {
match segment {
PathSegment::Ident(id) => SourcePathSegment::ident(id.clone()),
PathSegment::Extension(id) => SourcePathSegment::extension(id.clone()),
PathSegment::PartialValue(key) => SourcePathSegment {
key: Self::partial_object_key_to_source_key(key),
array: None,
},
PathSegment::HoleKey(label) => SourcePathSegment {
key: SourceKey::hole(label.clone()),
array: None,
},
PathSegment::Value(key) => SourcePathSegment {
key: Self::object_key_to_source_key(key),
array: None,
},
PathSegment::TupleIndex(idx) => SourcePathSegment {
key: SourceKey::TupleIndex(*idx),
array: None,
},
PathSegment::ArrayIndex(_) => {
unreachable!(
"ArrayIndex should be merged with previous segment, not converted directly"
)
}
}
}
fn partial_object_key_to_source_key(key: &crate::value::PartialObjectKey) -> SourceKey {
match key {
crate::value::PartialObjectKey::String(s) => {
if let Ok(id) = s.parse::<Identifier>() {
SourceKey::Ident(id)
} else {
SourceKey::quoted(s.clone())
}
}
crate::value::PartialObjectKey::Number(n) => {
if let Ok(n64) = i64::try_from(n) {
SourceKey::Integer(n64)
} else {
SourceKey::quoted(n.to_string())
}
}
crate::value::PartialObjectKey::Hole(label) => SourceKey::hole(label.clone()),
crate::value::PartialObjectKey::Tuple(keys) => SourceKey::Tuple(
keys.iter()
.map(Self::partial_object_key_to_source_key)
.collect(),
),
}
}
fn object_key_to_source_key(key: &ObjectKey) -> SourceKey {
match key {
ObjectKey::String(s) => {
if let Ok(id) = s.parse::<Identifier>() {
SourceKey::Ident(id)
} else {
SourceKey::quoted(s.clone())
}
}
ObjectKey::Number(n) => {
if let Ok(n64) = i64::try_from(n) {
SourceKey::Integer(n64)
} else {
SourceKey::quoted(n.to_string())
}
}
ObjectKey::Tuple(keys) => {
SourceKey::Tuple(keys.iter().map(Self::object_key_to_source_key).collect())
}
}
}
fn push_binding(&mut self, mut binding: BindingSource) {
binding.trivia_before = std::mem::take(&mut self.pending_trivia);
match self.builder_stack.last_mut() {
Some(BuilderContext::SectionItems { bindings, .. }) => {
bindings.push(binding);
}
Some(BuilderContext::EureBlock { source_id, .. }) => {
self.sources[source_id.0].bindings.push(binding);
}
None => {
self.sources[0].bindings.push(binding);
}
}
}
fn push_section(&mut self, mut section: SectionSource, trivia: Vec<Trivia>) {
section.trivia_before = trivia;
self.current_source_mut().sections.push(section);
}
}
impl InterpreterSink for SourceConstructor {
type Error = InsertError;
type Scope = Scope;
fn begin_scope(&mut self) -> Self::Scope {
self.inner.begin_scope()
}
fn end_scope(&mut self, scope: Self::Scope) -> Result<(), Self::Error> {
InterpreterSink::end_scope(&mut self.inner, scope)
}
fn navigate(&mut self, segment: PathSegment) -> Result<NodeId, Self::Error> {
if let PathSegment::ArrayIndex(idx) = &segment {
let last = self.pending_path.last_mut().ok_or_else(|| InsertError {
kind: ConstructorError::StandaloneArrayIndex.into(),
path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
})?;
last.array = Some(*idx);
} else {
let source_segment = Self::path_segment_to_source(&segment);
self.pending_path.push(source_segment);
}
InterpreterSink::navigate(&mut self.inner, segment)
}
fn require_hole(&self) -> Result<(), Self::Error> {
self.inner.require_hole()
}
fn bind_primitive(&mut self, value: PrimitiveValue) -> Result<(), Self::Error> {
self.last_bound_node = Some(self.inner.current_node_id());
InterpreterSink::bind_primitive(&mut self.inner, value)
}
fn bind_hole(&mut self, label: Option<Identifier>) -> Result<(), Self::Error> {
self.last_bound_node = Some(self.inner.current_node_id());
InterpreterSink::bind_hole(&mut self.inner, label)
}
fn bind_empty_map(&mut self) -> Result<(), Self::Error> {
self.last_bound_node = Some(self.inner.current_node_id());
InterpreterSink::bind_empty_map(&mut self.inner)
}
fn bind_empty_array(&mut self) -> Result<(), Self::Error> {
self.last_bound_node = Some(self.inner.current_node_id());
InterpreterSink::bind_empty_array(&mut self.inner)
}
fn bind_empty_tuple(&mut self) -> Result<(), Self::Error> {
self.last_bound_node = Some(self.inner.current_node_id());
InterpreterSink::bind_empty_tuple(&mut self.inner)
}
fn current_node_id(&self) -> NodeId {
self.inner.current_node_id()
}
fn current_path(&self) -> &[PathSegment] {
self.inner.current_path()
}
fn document(&self) -> &EureDocument {
self.inner.document()
}
fn document_mut(&mut self) -> &mut EureDocument {
self.inner.document_mut()
}
fn begin_eure_block(&mut self) {
let source_id = SourceId(self.sources.len());
self.sources.push(EureSource::default());
let saved_path = std::mem::take(&mut self.pending_path);
let saved_trivia = std::mem::take(&mut self.pending_trivia);
self.builder_stack.push(BuilderContext::EureBlock {
source_id,
saved_path,
saved_trivia,
});
}
fn set_block_value(&mut self) -> Result<(), Self::Error> {
let node_id = self.last_bound_node.take().ok_or_else(|| InsertError {
kind: ConstructorError::MissingBindBeforeSetBlockValue.into(),
path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
})?;
self.current_source_mut().value = Some(node_id);
Ok(())
}
fn end_eure_block(&mut self) -> Result<(), Self::Error> {
if !self.pending_trivia.is_empty() {
let source_id = match self.builder_stack.last() {
Some(BuilderContext::EureBlock { source_id, .. }) => *source_id,
_ => {
return Err(InsertError {
kind: ConstructorError::InvalidBuilderStackForEndEureBlock.into(),
path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
});
}
};
self.sources[source_id.0].trailing_trivia = std::mem::take(&mut self.pending_trivia);
}
match self.builder_stack.pop() {
Some(BuilderContext::EureBlock {
source_id,
saved_path,
saved_trivia,
}) => {
self.last_block_id = Some(source_id);
self.pending_path = saved_path;
self.pending_trivia = saved_trivia;
Ok(())
}
_ => Err(InsertError {
kind: ConstructorError::InvalidBuilderStackForEndEureBlock.into(),
path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
}),
}
}
fn begin_binding(&mut self) {
self.pending_path.clear();
}
fn end_binding_value(&mut self) -> Result<(), Self::Error> {
let path = std::mem::take(&mut self.pending_path);
let node_id = self.last_bound_node.take().ok_or_else(|| InsertError {
kind: ConstructorError::MissingBindBeforeEndBindingValue.into(),
path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
})?;
let binding = BindingSource::value(path, node_id);
self.push_binding(binding);
Ok(())
}
fn end_binding_block(&mut self) -> Result<(), Self::Error> {
let path = std::mem::take(&mut self.pending_path);
let source_id = self.last_block_id.take().ok_or_else(|| InsertError {
kind: ConstructorError::MissingEndEureBlockBeforeEndBindingBlock.into(),
path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
})?;
let binding = BindingSource::block(path, source_id);
self.push_binding(binding);
Ok(())
}
fn begin_section(&mut self) {
self.pending_path.clear();
}
fn begin_section_items(&mut self) {
let path = std::mem::take(&mut self.pending_path);
let trivia_before = std::mem::take(&mut self.pending_trivia);
let value = self.last_bound_node.take();
self.builder_stack.push(BuilderContext::SectionItems {
trivia_before,
path,
value,
bindings: Vec::new(),
});
}
fn end_section_items(&mut self) -> Result<(), Self::Error> {
match self.builder_stack.pop() {
Some(BuilderContext::SectionItems {
trivia_before,
path,
value,
bindings,
}) => {
let section = SectionSource::items(path, value, bindings);
self.push_section(section, trivia_before);
Ok(())
}
_ => Err(InsertError {
kind: ConstructorError::InvalidBuilderStackForEndSectionItems.into(),
path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
}),
}
}
fn end_section_block(&mut self) -> Result<(), Self::Error> {
let path = std::mem::take(&mut self.pending_path);
let trivia_before = std::mem::take(&mut self.pending_trivia);
let source_id = self.last_block_id.take().ok_or_else(|| InsertError {
kind: ConstructorError::MissingEndEureBlockBeforeEndSectionBlock.into(),
path: EurePath::from_iter(self.inner.current_path().iter().cloned()),
})?;
let section = SectionSource::block(path, source_id);
self.push_section(section, trivia_before);
Ok(())
}
fn comment(&mut self, comment: Comment) {
self.pending_trivia.push(Trivia::Comment(comment));
}
fn blank_line(&mut self) {
self.pending_trivia.push(Trivia::BlankLine);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::document::InsertErrorKind;
use crate::source::{BindSource, SectionBody};
fn ident(s: &str) -> Identifier {
s.parse().unwrap()
}
#[test]
fn test_pattern1_simple_binding() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("name")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("Alice")))
.unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_binding_value().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.bindings.len(), 1);
assert!(root.sections.is_empty());
assert!(root.value.is_none());
let binding = &root.bindings[0];
assert_eq!(binding.path.len(), 1);
assert_eq!(binding.path[0].key, SourceKey::Ident(ident("name")));
match &binding.bind {
BindSource::Value(node_id) => {
assert!(node_id.0 > 0); }
_ => panic!("Expected BindSource::Value"),
}
}
#[test]
fn test_pattern1_nested_path() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("a")))
.unwrap();
constructor
.navigate(PathSegment::Ident(ident("b")))
.unwrap();
constructor
.navigate(PathSegment::Ident(ident("c")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Integer(42.into()))
.unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_binding_value().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.bindings.len(), 1);
let binding = &root.bindings[0];
assert_eq!(binding.path.len(), 3);
assert_eq!(binding.path[0].key, SourceKey::Ident(ident("a")));
assert_eq!(binding.path[1].key, SourceKey::Ident(ident("b")));
assert_eq!(binding.path[2].key, SourceKey::Ident(ident("c")));
}
#[test]
fn test_pattern2_binding_block() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("user")))
.unwrap();
constructor.begin_eure_block();
constructor.begin_binding();
let inner_scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("name")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("Bob")))
.unwrap();
constructor.end_scope(inner_scope).unwrap();
constructor.end_binding_value().unwrap();
constructor.end_eure_block().unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_binding_block().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.bindings.len(), 1);
let binding = &root.bindings[0];
assert_eq!(binding.path.len(), 1);
assert_eq!(binding.path[0].key, SourceKey::Ident(ident("user")));
match &binding.bind {
BindSource::Block(source_id) => {
let inner_source = source_doc.source(*source_id);
assert!(inner_source.value.is_none());
assert_eq!(inner_source.bindings.len(), 1);
assert_eq!(
inner_source.bindings[0].path[0].key,
SourceKey::Ident(ident("name"))
);
}
_ => panic!("Expected BindSource::Block"),
}
}
#[test]
fn test_pattern3_binding_value_block() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("data")))
.unwrap();
constructor.begin_eure_block();
constructor.bind_empty_array().unwrap();
constructor.set_block_value().unwrap();
constructor.begin_binding();
let inner_scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Extension(ident("schema")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("array")))
.unwrap();
constructor.end_scope(inner_scope).unwrap();
constructor.end_binding_value().unwrap();
constructor.end_eure_block().unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_binding_block().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.bindings.len(), 1);
let binding = &root.bindings[0];
match &binding.bind {
BindSource::Block(source_id) => {
let inner_source = source_doc.source(*source_id);
assert!(inner_source.value.is_some());
assert_eq!(inner_source.bindings.len(), 1);
}
_ => panic!("Expected BindSource::Block"),
}
}
#[test]
fn test_pattern4_section_items() {
let mut constructor = SourceConstructor::new();
constructor.begin_section();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("server")))
.unwrap();
constructor.begin_section_items();
constructor.begin_binding();
let inner_scope1 = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("host")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("localhost")))
.unwrap();
constructor.end_scope(inner_scope1).unwrap();
constructor.end_binding_value().unwrap();
constructor.begin_binding();
let inner_scope2 = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("port")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Integer(8080.into()))
.unwrap();
constructor.end_scope(inner_scope2).unwrap();
constructor.end_binding_value().unwrap();
constructor.end_section_items().unwrap();
constructor.end_scope(scope).unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert!(root.bindings.is_empty());
assert_eq!(root.sections.len(), 1);
let section = &root.sections[0];
assert_eq!(section.path.len(), 1);
assert_eq!(section.path[0].key, SourceKey::Ident(ident("server")));
match §ion.body {
SectionBody::Items { value, bindings } => {
assert!(value.is_none());
assert_eq!(bindings.len(), 2);
assert_eq!(bindings[0].path[0].key, SourceKey::Ident(ident("host")));
assert_eq!(bindings[1].path[0].key, SourceKey::Ident(ident("port")));
}
_ => panic!("Expected SectionBody::Items"),
}
}
#[test]
fn test_pattern5_section_block() {
let mut constructor = SourceConstructor::new();
constructor.begin_section();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("server")))
.unwrap();
constructor.begin_eure_block();
constructor.begin_binding();
let inner_scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("host")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("localhost")))
.unwrap();
constructor.end_scope(inner_scope).unwrap();
constructor.end_binding_value().unwrap();
constructor.end_eure_block().unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_section_block().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert!(root.bindings.is_empty());
assert_eq!(root.sections.len(), 1);
let section = &root.sections[0];
match §ion.body {
SectionBody::Block(source_id) => {
let inner_source = source_doc.source(*source_id);
assert!(inner_source.value.is_none());
assert_eq!(inner_source.bindings.len(), 1);
}
_ => panic!("Expected SectionBody::Block"),
}
}
#[test]
fn test_pattern6_section_value_block() {
let mut constructor = SourceConstructor::new();
constructor.begin_section();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("data")))
.unwrap();
constructor.begin_eure_block();
constructor.bind_empty_array().unwrap();
constructor.set_block_value().unwrap();
constructor.begin_binding();
let inner_scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Extension(ident("schema")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("array")))
.unwrap();
constructor.end_scope(inner_scope).unwrap();
constructor.end_binding_value().unwrap();
constructor.end_eure_block().unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_section_block().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.sections.len(), 1);
let section = &root.sections[0];
match §ion.body {
SectionBody::Block(source_id) => {
let inner_source = source_doc.source(*source_id);
assert!(inner_source.value.is_some());
assert_eq!(inner_source.bindings.len(), 1);
}
_ => panic!("Expected SectionBody::Block"),
}
}
#[test]
fn test_array_index_with_key() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("items")))
.unwrap();
constructor
.navigate(PathSegment::ArrayIndex(Some(0)))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("first")))
.unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_binding_value().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.bindings.len(), 1);
let binding = &root.bindings[0];
assert_eq!(binding.path.len(), 1);
assert_eq!(binding.path[0].key, SourceKey::Ident(ident("items")));
assert_eq!(binding.path[0].array, Some(Some(0)));
}
#[test]
fn test_array_push_marker() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("items")))
.unwrap();
constructor.navigate(PathSegment::ArrayIndex(None)).unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("new")))
.unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_binding_value().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
let binding = &root.bindings[0];
assert_eq!(binding.path.len(), 1);
assert_eq!(binding.path[0].key, SourceKey::Ident(ident("items")));
assert_eq!(binding.path[0].array, Some(None));
}
#[test]
fn test_standalone_array_index_returns_error() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let _scope = constructor.begin_scope();
let result = constructor.navigate(PathSegment::ArrayIndex(Some(0)));
assert!(matches!(
result,
Err(InsertError {
kind: InsertErrorKind::ConstructorError(ConstructorError::StandaloneArrayIndex),
..
})
));
}
#[test]
fn test_end_binding_value_without_bind_returns_error() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("name")))
.unwrap();
constructor.end_scope(scope).unwrap();
let result = constructor.end_binding_value();
assert!(matches!(
result,
Err(InsertError {
kind: InsertErrorKind::ConstructorError(
ConstructorError::MissingBindBeforeEndBindingValue
),
..
})
));
}
#[test]
fn test_set_block_value_without_bind_returns_error() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let _scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("data")))
.unwrap();
constructor.begin_eure_block();
let result = constructor.set_block_value();
assert!(matches!(
result,
Err(InsertError {
kind: InsertErrorKind::ConstructorError(
ConstructorError::MissingBindBeforeSetBlockValue
),
..
})
));
}
#[test]
fn test_end_binding_block_without_end_eure_block_returns_error() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("data")))
.unwrap();
constructor.end_scope(scope).unwrap();
let result = constructor.end_binding_block();
assert!(matches!(
result,
Err(InsertError {
kind: InsertErrorKind::ConstructorError(
ConstructorError::MissingEndEureBlockBeforeEndBindingBlock
),
..
})
));
}
#[test]
fn test_multiple_bindings() {
let mut constructor = SourceConstructor::new();
for (name, value) in [("a", 1), ("b", 2)] {
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident(name)))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Integer(value.into()))
.unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_binding_value().unwrap();
}
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.bindings.len(), 2);
assert_eq!(root.bindings[0].path[0].key, SourceKey::Ident(ident("a")));
assert_eq!(root.bindings[1].path[0].key, SourceKey::Ident(ident("b")));
}
#[test]
fn test_nested_blocks() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope1 = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("outer")))
.unwrap();
constructor.begin_eure_block();
constructor.begin_binding();
let scope2 = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("inner")))
.unwrap();
constructor.begin_eure_block();
constructor.begin_binding();
let scope3 = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("value")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Integer(1.into()))
.unwrap();
constructor.end_scope(scope3).unwrap();
constructor.end_binding_value().unwrap();
constructor.end_eure_block().unwrap();
constructor.end_scope(scope2).unwrap();
constructor.end_binding_block().unwrap();
constructor.end_eure_block().unwrap();
constructor.end_scope(scope1).unwrap();
constructor.end_binding_block().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.bindings.len(), 1);
if let BindSource::Block(outer_id) = &root.bindings[0].bind {
let outer = source_doc.source(*outer_id);
assert_eq!(outer.bindings.len(), 1);
if let BindSource::Block(inner_id) = &outer.bindings[0].bind {
let inner = source_doc.source(*inner_id);
assert_eq!(inner.bindings.len(), 1);
assert!(matches!(inner.bindings[0].bind, BindSource::Value(_)));
} else {
panic!("Expected inner block");
}
} else {
panic!("Expected outer block");
}
}
#[test]
fn test_trivia_before_binding() {
let mut constructor = SourceConstructor::new();
constructor.comment(Comment::Line("This is a comment".to_string()));
constructor.blank_line();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("name")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("Alice")))
.unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_binding_value().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.bindings.len(), 1);
let binding = &root.bindings[0];
assert_eq!(binding.trivia_before.len(), 2);
assert!(matches!(
&binding.trivia_before[0],
Trivia::Comment(Comment::Line(s)) if s == "This is a comment"
));
assert!(matches!(&binding.trivia_before[1], Trivia::BlankLine));
}
#[test]
fn test_trivia_before_section() {
let mut constructor = SourceConstructor::new();
constructor.blank_line();
constructor.begin_section();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("server")))
.unwrap();
constructor.begin_section_items();
constructor.begin_binding();
let inner_scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("host")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("localhost")))
.unwrap();
constructor.end_scope(inner_scope).unwrap();
constructor.end_binding_value().unwrap();
constructor.end_section_items().unwrap();
constructor.end_scope(scope).unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.sections.len(), 1);
let section = &root.sections[0];
assert_eq!(section.trivia_before.len(), 1);
assert!(matches!(§ion.trivia_before[0], Trivia::BlankLine));
}
#[test]
fn test_trailing_trivia() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("name")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Text(Text::plaintext("Alice")))
.unwrap();
constructor.end_scope(scope).unwrap();
constructor.end_binding_value().unwrap();
constructor.blank_line();
constructor.comment(Comment::Line("end of file".to_string()));
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.trailing_trivia.len(), 2);
assert!(matches!(&root.trailing_trivia[0], Trivia::BlankLine));
assert!(matches!(
&root.trailing_trivia[1],
Trivia::Comment(Comment::Line(s)) if s == "end of file"
));
}
#[test]
fn test_trivia_between_bindings() {
let mut constructor = SourceConstructor::new();
constructor.begin_binding();
let scope1 = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("a")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Integer(1.into()))
.unwrap();
constructor.end_scope(scope1).unwrap();
constructor.end_binding_value().unwrap();
constructor.blank_line();
constructor.comment(Comment::Line("Second binding".to_string()));
constructor.begin_binding();
let scope2 = constructor.begin_scope();
constructor
.navigate(PathSegment::Ident(ident("b")))
.unwrap();
constructor
.bind_primitive(PrimitiveValue::Integer(2.into()))
.unwrap();
constructor.end_scope(scope2).unwrap();
constructor.end_binding_value().unwrap();
let source_doc = constructor.finish();
let root = source_doc.root_source();
assert_eq!(root.bindings.len(), 2);
assert!(root.bindings[0].trivia_before.is_empty());
assert_eq!(root.bindings[1].trivia_before.len(), 2);
assert!(matches!(
&root.bindings[1].trivia_before[0],
Trivia::BlankLine
));
assert!(matches!(
&root.bindings[1].trivia_before[1],
Trivia::Comment(Comment::Line(s)) if s == "Second binding"
));
}
}