use super::{
ast::{self, AstNode},
lst::{self, ApmlLst},
};
#[derive(Debug)]
#[repr(transparent)]
pub struct ApmlEditor<'a, 'b>(&'a mut ApmlLst<'b>);
impl<'b> AsRef<ApmlLst<'b>> for ApmlEditor<'_, 'b> {
fn as_ref(&self) -> &ApmlLst<'b> {
self.0
}
}
impl<'a, 'b> ApmlEditor<'a, 'b> {
pub fn wrap(lst: &'a mut ApmlLst<'b>) -> Self {
Self(lst)
}
}
impl<'b> ApmlEditor<'_, 'b> {
pub fn lst_tokens(&mut self) -> &Vec<lst::Token<'b>> {
&self.0.0
}
pub fn lst_tokens_iter(&self) -> impl Iterator<Item = &lst::Token<'b>> {
self.0.0.iter()
}
pub fn lst_tokens_mut(&mut self) -> &mut Vec<lst::Token<'b>> {
&mut self.0.0
}
pub fn lst_variables(
&self,
) -> impl Iterator<Item = &lst::VariableDefinition<'b>> {
self.lst_tokens_iter().filter_map(|token| {
if let lst::Token::Variable(var) = token {
Some(var)
} else {
None
}
})
}
pub fn ast_variables(
&self,
) -> ast::EmitResult<Vec<ast::VariableDefinition<'b>>> {
self.lst_variables()
.map(ast::VariableDefinition::emit_from)
.collect()
}
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.lst_variables().map(|var| var.name.as_ref())
}
pub fn find_var<S: AsRef<str>>(
&self,
name: S,
) -> Option<(usize, &lst::VariableDefinition<'b>)> {
self.lst_tokens_iter().enumerate().find_map(|(idx, token)| {
if let lst::Token::Variable(var) = token {
if var.name.as_ref() == name.as_ref() {
Some((idx, var))
} else {
None
}
} else {
None
}
})
}
pub fn find_var_index<S: AsRef<str>>(&self, name: S) -> Option<usize> {
self.lst_tokens_iter().enumerate().find_map(|(idx, token)| {
if let lst::Token::Variable(var) = token {
if var.name.as_ref() == name.as_ref() {
Some(idx)
} else {
None
}
} else {
None
}
})
}
pub fn find_var_token<S: AsRef<str>>(
&self,
name: S,
) -> Option<&lst::Token<'b>> {
self.lst_tokens_iter().find(|token| {
if let lst::Token::Variable(var) = token
&& var.name.as_ref() == name.as_ref()
{
return true;
}
false
})
}
pub fn ensure_end_newline(&mut self) {
if !matches!(self.lst_tokens().last(), None | Some(lst::Token::Newline))
{
self.lst_tokens_mut().push(lst::Token::Newline);
}
}
pub fn append_var_ast(
&mut self,
name: String,
value: &ast::VariableValue<'b>,
after: Option<&str>,
) {
let definition = lst::VariableDefinition {
name: name.into(),
op: lst::VariableOp::Assignment,
value: value.lower(),
};
let token = lst::Token::Variable(definition);
if let Some(after) = after
&& let Some((index, _)) = self.find_var(after)
{
let after = self
.lst_tokens_iter()
.skip(index)
.take_while(|token| !matches!(token, lst::Token::Newline))
.count();
let index = index + after + 1;
if index <= self.lst_tokens().len() {
self.lst_tokens_mut().insert(index, lst::Token::Newline);
self.lst_tokens_mut().insert(index, token);
return;
}
}
self.ensure_end_newline();
self.lst_tokens_mut().push(token);
self.lst_tokens_mut().push(lst::Token::Newline);
}
pub fn replace_var_ast(
&mut self,
name: &'b str,
value: &ast::VariableValue<'b>,
) {
self.replace_var_lst(name, value.lower())
}
pub fn replace_var_lst(
&mut self,
name: &'b str,
value: lst::VariableValue<'b>,
) {
let definition = lst::VariableDefinition {
name: name.into(),
op: lst::VariableOp::Assignment,
value,
};
let token = lst::Token::Variable(definition);
if let Some((index, _)) = self.find_var(name) {
self.lst_tokens_mut()[index] = token;
return;
}
self.ensure_end_newline();
self.lst_tokens_mut().push(token);
self.lst_tokens_mut().push(lst::Token::Newline);
}
pub fn remove_var(&mut self, index: usize) {
let after = self
.lst_tokens_iter()
.skip(index)
.take_while(|token| !matches!(token, lst::Token::Newline))
.count();
let mut start = index;
let tokens = self.lst_tokens();
if matches!(tokens.get(index - 2), Some(lst::Token::Comment(_))) {
if !tokens
.iter()
.skip(index)
.skip_while(|token| !matches!(token, lst::Token::Newline))
.skip(1)
.take_while(|token| !matches!(token, lst::Token::Newline))
.any(|token| matches!(token, lst::Token::Variable(_)))
{
while matches!(tokens.get(start - 1), Some(lst::Token::Newline))
&& matches!(
tokens.get(start - 2),
Some(lst::Token::Comment(_))
) && matches!(
tokens.get(start - 3),
Some(lst::Token::Newline)
) {
start -= 2;
}
}
}
self.lst_tokens_mut().drain(start..=(index + after));
}
pub fn comments(&self) -> impl Iterator<Item = &str> {
self.lst_tokens_iter().filter_map(|token| {
if let lst::Token::Comment(var) = token {
Some(var.as_ref())
} else {
None
}
})
}
}
#[cfg(test)]
mod test {
use crate::apml::lst::ApmlLst;
use super::*;
#[test]
fn test() {
let mut lst = ApmlLst::parse("a=b\nb=c\nc=\"$1\"").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
assert_eq!(editor.lst_tokens_iter().count(), 5);
assert_eq!(editor.lst_tokens_mut().len(), 5);
assert_eq!(editor.ast_variables().unwrap().len(), 3);
assert_eq!(editor.keys().count(), 3);
assert_eq!(editor.find_var("a").unwrap().0, 0);
assert_eq!(editor.find_var("a").unwrap().1.name, "a");
assert_eq!(editor.find_var("b").unwrap().0, 2);
assert_eq!(editor.find_var_index("b").unwrap(), 2);
assert!(editor.find_var("A").is_none());
if let lst::Token::Variable(var) = editor.find_var_token("b").unwrap() {
assert_eq!(var.name, "b");
} else {
unreachable!();
}
assert!(editor.find_var_token("A").is_none());
assert!(editor.find_var_index("A").is_none());
}
#[test]
fn test_ensure_end_newline() {
let mut lst = ApmlLst::parse("a=b").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
editor.ensure_end_newline();
assert_eq!(lst.to_string(), "a=b\n");
}
#[test]
fn test_append_variable() {
let mut lst = ApmlLst::parse("a=b\nb=c").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
editor.append_var_ast(
"c".to_string(),
&ast::VariableValue::String("a".into()),
None,
);
assert_eq!(lst.to_string(), "a=b\nb=c\nc=\"a\"\n");
let mut lst = ApmlLst::parse("a=b\nb=c").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
editor.append_var_ast(
"c".to_string(),
&ast::VariableValue::String("a".into()),
Some("a"),
);
assert_eq!(lst.to_string(), "a=b\nc=\"a\"\nb=c");
let mut lst = ApmlLst::parse("a=b\nb=c").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
editor.append_var_ast(
"c".to_string(),
&ast::VariableValue::String("a".into()),
Some("b"),
);
assert_eq!(lst.to_string(), "a=b\nb=c\nc=\"a\"\n");
let mut lst = ApmlLst::parse("a=b\nb=c").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
editor.append_var_ast(
"c".to_string(),
&ast::VariableValue::String("a".into()),
Some("eee"),
);
assert_eq!(lst.to_string(), "a=b\nb=c\nc=\"a\"\n");
}
#[test]
fn test_replace_variable() {
let mut lst = ApmlLst::parse("a=b\nb=c").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
editor.replace_var_ast("c", &ast::VariableValue::String("a".into()));
assert_eq!(lst.to_string(), "a=b\nb=c\nc=\"a\"\n");
let mut lst = ApmlLst::parse("a=b\nb=c").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
editor.replace_var_ast("a", &ast::VariableValue::String("a".into()));
assert_eq!(lst.to_string(), "a=\"a\"\nb=c");
}
#[test]
fn test_remove_var() {
let mut lst = ApmlLst::parse("a=b\nb=c\n\nc=\"$1\"").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
editor.remove_var(editor.find_var("b").unwrap().0);
assert_eq!(lst.to_string(), "a=b\n\nc=\"$1\"");
let mut lst =
ApmlLst::parse("a=b # a\n# b\n# c\nb=c\n\n# a\nc=\"$1\"").unwrap();
let mut editor = ApmlEditor::wrap(&mut lst);
editor.remove_var(editor.find_var("b").unwrap().0);
assert_eq!(lst.to_string(), "a=b # a\n\n# a\nc=\"$1\"");
}
#[test]
fn test_comments() {
let mut lst =
ApmlLst::parse("a=b # a\n# b\n# c\nb=c\n\n# a\nc=\"$1\"").unwrap();
let editor = ApmlEditor::wrap(&mut lst);
assert_eq!(editor.comments().count(), 4);
}
}