1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
mod rewrite;
mod string_case;
mod transformation;

use crate::{DeserializeEnv, RuleCore};

use ast_grep_core::meta_var::MetaVarEnv;
use ast_grep_core::meta_var::MetaVariable;
use ast_grep_core::{Doc, Language};

use std::collections::HashMap;
use thiserror::Error;

use transformation::Transformation as Trans;
pub type Transformation = Trans<String>;

#[derive(Debug, Error)]
pub enum TransformError {
  #[error("`{0}` has a cyclic dependency.")]
  Cyclic(String),
  #[error("Transform var `{0}` has already defined.")]
  AlreadyDefined(String),
  #[error("source `{0}` should be $-prefixed.")]
  MalformedVar(String),
}

pub struct Transform {
  transforms: Vec<(String, Trans<MetaVariable>)>,
}

impl Transform {
  pub fn deserialize<L: Language>(
    map: &HashMap<String, Transformation>,
    env: &DeserializeEnv<L>,
  ) -> Result<Self, TransformError> {
    let orders = env
      .get_transform_order(map)
      .map_err(TransformError::Cyclic)?;
    let transforms: Result<_, _> = orders
      .into_iter()
      .map(|key| map[key].parse(&env.lang).map(|t| (key.to_string(), t)))
      .collect();
    Ok(Self {
      transforms: transforms?,
    })
  }

  pub fn apply_transform<'c, D: Doc>(
    &self,
    env: &mut MetaVarEnv<'c, D>,
    rewriters: &HashMap<String, RuleCore<D::Lang>>,
    enclosing_env: &MetaVarEnv<'c, D>,
  ) {
    let mut ctx = Ctx {
      env,
      rewriters,
      enclosing_env,
    };
    for (key, tr) in &self.transforms {
      tr.insert(key, &mut ctx);
    }
  }

  pub(crate) fn keys(&self) -> impl Iterator<Item = &String> {
    self.transforms.iter().map(|t| &t.0)
  }

  pub(crate) fn values(&self) -> impl Iterator<Item = &Trans<MetaVariable>> {
    self.transforms.iter().map(|t| &t.1)
  }
}

// two lifetime to represent env root lifetime and lang/trans lifetime
struct Ctx<'b, 'c, D: Doc> {
  rewriters: &'b HashMap<String, RuleCore<D::Lang>>,
  env: &'b mut MetaVarEnv<'c, D>,
  enclosing_env: &'b MetaVarEnv<'c, D>,
}

#[cfg(test)]
mod test {
  use super::*;
  use crate::from_str;
  use crate::test::TypeScript;

  #[test]
  fn test_single_cyclic_transform() {
    let mut trans = HashMap::new();
    let trans_a = from_str("substring: {source: $A}").unwrap();
    trans.insert("A".into(), trans_a);
    let env = DeserializeEnv::new(TypeScript::Tsx);
    match Transform::deserialize(&trans, &env) {
      Err(TransformError::Cyclic(a)) => assert_eq!(a, "A"),
      _ => panic!("unexpected error"),
    }
  }

  #[test]
  fn test_cyclic_transform() {
    let mut trans = HashMap::new();
    let trans_a = from_str("substring: {source: $B}").unwrap();
    trans.insert("A".into(), trans_a);
    let trans_b = from_str("substring: {source: $A}").unwrap();
    trans.insert("B".into(), trans_b);
    let env = DeserializeEnv::new(TypeScript::Tsx);
    let ret = Transform::deserialize(&trans, &env);
    assert!(matches!(ret, Err(TransformError::Cyclic(_))));
  }

  #[test]
  fn test_transform_use_matched() {
    let mut trans = HashMap::new();
    let trans_a = from_str("substring: {source: $C}").unwrap();
    trans.insert("A".into(), trans_a);
    let trans_b = from_str("substring: {source: $A}").unwrap();
    trans.insert("B".into(), trans_b);
    let env = DeserializeEnv::new(TypeScript::Tsx);
    let ret = Transform::deserialize(&trans, &env);
    assert!(ret.is_ok());
  }
}