use links_notation::{
LiNo, ParserConfig, SyntaxReference, format_links, parse_lino, parse_lino_to_links,
parse_lino_to_syntax_links, parse_lino_to_syntax_links_with_config, reference_quote,
};
use std::collections::HashSet;
fn values(link: &LiNo<SyntaxReference>) -> &[LiNo<SyntaxReference>] {
match link {
LiNo::Link { values, .. } => values,
LiNo::Ref(_) => panic!("expected a link, found a reference"),
}
}
fn reference(link: &LiNo<SyntaxReference>) -> &SyntaxReference {
match link {
LiNo::Ref(reference) => reference,
LiNo::Link { .. } => panic!("expected a reference, found a link"),
}
}
fn plain(links: Vec<LiNo<SyntaxReference>>) -> Vec<LiNo<String>> {
links.into_iter().map(LiNo::into_values).collect()
}
#[test]
fn one_and_quoted_one_are_the_same_reference_but_keep_how_they_were_written() {
let links = parse_lino_to_syntax_links("(t 1 '1')").unwrap();
let bare = reference(&values(&links[0])[1]);
let quoted = reference(&values(&links[0])[2]);
assert_eq!(bare.value, "1");
assert_eq!(quoted.value, "1");
assert_eq!(bare.raw.as_deref(), Some("1"));
assert_eq!(quoted.raw.as_deref(), Some("'1'"));
assert_eq!(bare.quote(), None);
assert_eq!(quoted.quote(), Some('\''));
assert_eq!(bare, quoted);
assert_eq!(HashSet::from([bare.clone(), quoted.clone()]).len(), 1);
assert_eq!(format!("{}", links[0]), "(t 1 1)");
assert_eq!(format_links(&plain(links)), "(t 1 1)");
}
#[test]
fn records_every_quote_kind_and_run_length() {
let links = parse_lino_to_syntax_links("(\"a\": 'b' `c` \"\"d\"\" ~1{65} f)").unwrap();
assert_eq!(links[0].raw_id(), Some("\"a\""));
assert_eq!(links[0].quote(), Some('"'));
let written: Vec<_> = values(&links[0])
.iter()
.map(|value| (value.raw_id(), value.quote()))
.collect();
assert_eq!(
written,
[
(Some("'b'"), Some('\'')),
(Some("`c`"), Some('`')),
(Some("\"\"d\"\""), Some('"')),
(Some("~1{65}"), None),
(Some("f"), None),
]
);
}
#[test]
fn keeps_raw_text_from_the_source_not_from_comment_stripping() {
let links = parse_lino_to_syntax_links("(a 'b # c') # d").unwrap();
assert_eq!(values(&links[0])[1].raw_id(), Some("'b # c'"));
}
#[test]
fn keeps_raw_text_of_multiline_and_unicode_references() {
let links = parse_lino_to_syntax_links("(æ ¹: \"one\r\ntwo\" café)").unwrap();
assert_eq!(links[0].raw_id(), Some("æ ¹"));
assert_eq!(values(&links[0])[0].raw_id(), Some("\"one\r\ntwo\""));
assert_eq!(values(&links[0])[1].raw_id(), Some("café"));
}
#[test]
fn links_without_written_ids_have_no_raw_text() {
let links = parse_lino_to_syntax_links("(a b)").unwrap();
assert_eq!(links[0].raw_id(), None);
assert_eq!(links[0].quote(), None);
let built = SyntaxReference::new("x");
assert_eq!(built.raw, None);
assert_eq!(built.quote(), None);
assert_eq!(LiNo::Ref(SyntaxReference::from("x")).raw_id(), None);
}
#[test]
fn indented_ids_and_path_combinations() {
let links = parse_lino_to_syntax_links("'root':\n \"child\"\n leaf").unwrap();
assert_eq!(links[0].raw_id(), Some("'root'"));
assert_eq!(values(&links[0])[0].raw_id(), Some("\"child\""));
let links = parse_lino_to_syntax_links("a\n 'b'").unwrap();
assert_eq!(links[1].raw_id(), None);
let combined = values(&links[1]);
assert_eq!(values(&combined[1])[0].raw_id(), Some("'b'"));
}
#[test]
fn does_not_change_equality_formatting_or_the_ast_mode() {
let quoted = parse_lino_to_syntax_links("('a': 'b')").unwrap();
let bare = parse_lino_to_syntax_links("(a: b)").unwrap();
assert_eq!(quoted, bare);
assert_eq!(format!("{}", quoted[0]), format!("{}", bare[0]));
assert_eq!(
format!("{:?}", plain(quoted.clone())),
format!("{:?}", plain(bare))
);
let ast = parse_lino_to_links("('a': 'b')").unwrap();
assert_eq!(ast, plain(quoted.clone()));
assert_eq!(
ast,
quoted
.into_iter()
.map(LiNo::<String>::from)
.collect::<Vec<_>>()
);
assert_eq!(format_links(&ast), "(a: b)");
assert_eq!(format!("{}", parse_lino("('a': 'b')").unwrap()), "((a: b))");
}
#[test]
fn syntax_links_follow_the_parser_config() {
let config = ParserConfig::without_comments();
let links = parse_lino_to_syntax_links_with_config("(a #b)", &config).unwrap();
assert_eq!(values(&links[0])[1].raw_id(), Some("#b"));
let deep = ParserConfig::new().with_max_depth(1);
assert!(parse_lino_to_syntax_links_with_config("((a))", &deep).is_err());
assert!(parse_lino_to_syntax_links("(a").is_err());
assert!(parse_lino_to_syntax_links("").unwrap().is_empty());
}
#[test]
fn reference_quote_reads_the_delimiter_of_one_complete_reference() {
assert_eq!(reference_quote("'1'"), Some('\''));
assert_eq!(reference_quote("\"\"a b\"\""), Some('"'));
assert_eq!(reference_quote("`x`"), Some('`'));
assert_eq!(reference_quote("1"), None);
assert_eq!(reference_quote("~1{31}"), None);
assert_eq!(reference_quote("'unclosed"), None);
assert_eq!(reference_quote("'a' b"), None);
assert_eq!(reference_quote(""), None);
}
#[test]
fn stream_parser_keeps_syntax_metadata() {
use links_notation::{LiNo, StreamParser};
let document = "(t 1 '1')\nname: \"Ada\"\n child `x`\n";
let expected = links_notation::parse_lino_to_syntax_links(document).unwrap();
let raws = |links: &[LiNo<links_notation::SyntaxReference>]| {
fn walk(link: &LiNo<links_notation::SyntaxReference>, out: &mut Vec<Option<String>>) {
match link {
LiNo::Ref(reference) => out.push(reference.raw.clone()),
LiNo::Link { id, values } => {
out.push(id.as_ref().and_then(|id| id.raw.clone()));
for value in values {
walk(value, out);
}
}
}
}
let mut out = Vec::new();
for link in links {
walk(link, &mut out);
}
out
};
let mut stream = StreamParser::syntax();
for character in document.chars() {
stream.write(&character.to_string()).unwrap();
}
let streamed = stream.finish().unwrap();
assert_eq!(streamed, expected);
assert_eq!(raws(&streamed), raws(&expected));
assert!(raws(&streamed).contains(&Some("'1'".to_string())));
let chunked: Vec<_> = StreamParser::parse_syntax_chunks(["(t 1 ", "'1')\n"])
.collect::<Result<_, _>>()
.unwrap();
let LiNo::Link { values, .. } = &chunked[0] else {
unreachable!()
};
assert_eq!(values[2].raw_id(), Some("'1'"));
assert_eq!(values[2].quote(), Some('\''));
let mut plain = StreamParser::new();
plain.write(document).unwrap();
let plain = plain.finish().unwrap();
assert_eq!(
plain,
links_notation::parse_lino_to_links(document).unwrap()
);
}