Skip to content

Commit f85263b

Browse files
committed
rust: transformation module todo fixed
1 parent 75c6cb7 commit f85263b

File tree

1 file changed

+210
-57
lines changed

1 file changed

+210
-57
lines changed

rust/bear/src/semantic/transformation.rs

Lines changed: 210 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,81 +6,109 @@
66
//! It can also alter the compiler flags of the compiler calls. The actions
77
//! are defined in the configuration this module is given.
88
9-
use crate::config;
10-
use crate::semantic;
11-
use crate::semantic::Transform;
9+
use crate::{config, semantic};
1210

13-
pub enum Transformation {
14-
None,
15-
Config(Vec<config::Compiler>),
11+
use std::collections::HashMap;
12+
use std::path::PathBuf;
13+
14+
/// Transformation contains rearranged information from the configuration.
15+
///
16+
/// The configuration is a list of instruction on how to transform the compiler call.
17+
/// The transformation group the instructions by the compiler path, so it can be
18+
/// applied to the compiler call when it matches the path.
19+
#[derive(Debug, PartialEq)]
20+
pub struct Transformation {
21+
compilers: HashMap<PathBuf, Vec<config::Compiler>>,
1622
}
1723

1824
impl From<&config::Output> for Transformation {
1925
fn from(config: &config::Output) -> Self {
2026
match config {
21-
config::Output::Clang { compilers, .. } => {
22-
if compilers.is_empty() {
23-
Transformation::None
24-
} else {
25-
let compilers = compilers.clone();
26-
Transformation::Config(compilers)
27-
}
28-
}
29-
config::Output::Semantic { .. } => Transformation::None,
27+
config::Output::Clang { compilers, .. } => compilers.as_slice().into(),
28+
config::Output::Semantic { .. } => Transformation::new(),
29+
}
30+
}
31+
}
32+
33+
impl From<&[config::Compiler]> for Transformation {
34+
fn from(config: &[config::Compiler]) -> Self {
35+
let mut compilers = HashMap::new();
36+
for compiler in config {
37+
compilers
38+
.entry(compiler.path.clone())
39+
.or_insert_with(Vec::new)
40+
.push(compiler.clone());
3041
}
42+
Transformation { compilers }
3143
}
3244
}
3345

34-
impl Transform for Transformation {
46+
impl semantic::Transform for Transformation {
3547
fn apply(&self, input: semantic::CompilerCall) -> Option<semantic::CompilerCall> {
36-
let semantic::CompilerCall {
37-
compiler,
38-
passes,
39-
working_dir,
40-
} = &input;
41-
match self.lookup(compiler) {
42-
Some(config::Compiler {
43-
ignore: config::IgnoreOrConsider::Always,
44-
..
45-
}) => None,
46-
Some(config::Compiler {
47-
ignore: config::IgnoreOrConsider::Conditional,
48-
arguments,
49-
..
50-
}) => {
51-
if Self::filter(arguments, passes) {
52-
None
53-
} else {
54-
Some(input)
55-
}
56-
}
57-
Some(config::Compiler {
58-
ignore: config::IgnoreOrConsider::Never,
59-
arguments,
60-
..
61-
}) => {
62-
let new_passes = Transformation::execute(arguments, passes);
63-
Some(semantic::CompilerCall {
64-
compiler: compiler.clone(),
65-
working_dir: working_dir.clone(),
66-
passes: new_passes,
67-
})
68-
}
69-
None => Some(input),
48+
if let Some(configs) = self.compilers.get(&input.compiler) {
49+
Self::apply_when_not_empty(configs.as_slice(), input)
50+
} else {
51+
Some(input)
7052
}
7153
}
7254
}
7355

7456
impl Transformation {
75-
// TODO: allow multiple matches for the same compiler
76-
fn lookup(&self, compiler: &std::path::Path) -> Option<&config::Compiler> {
77-
match self {
78-
Transformation::Config(compilers) => compilers.iter().find(|c| c.path == compiler),
79-
_ => None,
57+
fn new() -> Self {
58+
Transformation {
59+
compilers: HashMap::new(),
60+
}
61+
}
62+
63+
/// Apply the transformation to the compiler call.
64+
///
65+
/// Multiple configurations can be applied to the same compiler call.
66+
/// And depending on the instruction from the configuration, the compiler call
67+
/// can be ignored, modified, or left unchanged. The conditional ignore will
68+
/// check if the compiler call matches the flags defined in the configuration.
69+
fn apply_when_not_empty(
70+
configs: &[config::Compiler],
71+
input: semantic::CompilerCall,
72+
) -> Option<semantic::CompilerCall> {
73+
let mut current_input = Some(input);
74+
75+
for config in configs {
76+
current_input = match config {
77+
config::Compiler {
78+
ignore: config::IgnoreOrConsider::Always,
79+
..
80+
} => None,
81+
config::Compiler {
82+
ignore: config::IgnoreOrConsider::Conditional,
83+
arguments,
84+
..
85+
} => current_input.filter(|input| !Self::match_condition(arguments, &input.passes)),
86+
config::Compiler {
87+
ignore: config::IgnoreOrConsider::Never,
88+
arguments,
89+
..
90+
} => current_input.map(|input| semantic::CompilerCall {
91+
compiler: input.compiler.clone(),
92+
working_dir: input.working_dir.clone(),
93+
passes: Transformation::apply_argument_changes(
94+
arguments,
95+
input.passes.as_slice(),
96+
),
97+
}),
98+
};
99+
100+
if current_input.is_none() {
101+
break;
102+
}
80103
}
104+
current_input
81105
}
82106

83-
fn filter(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool {
107+
/// Check if the compiler call matches the condition defined in the configuration.
108+
///
109+
/// Any compiler pass that matches the flags defined in the configuration will cause
110+
/// the whole compiler call to be ignored.
111+
fn match_condition(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool {
84112
let match_flags = arguments.match_.as_slice();
85113
passes.iter().any(|pass| match pass {
86114
semantic::CompilerPass::Compile { flags, .. } => {
@@ -90,7 +118,11 @@ impl Transformation {
90118
})
91119
}
92120

93-
fn execute(
121+
/// Apply the changes defined in the configuration to the compiler call.
122+
///
123+
/// The changes can be to remove or add flags to the compiler call.
124+
/// Only the flags will be changed, but applies to all compiler passes.
125+
fn apply_argument_changes(
94126
arguments: &config::Arguments,
95127
passes: &[semantic::CompilerPass],
96128
) -> Vec<semantic::CompilerPass> {
@@ -122,3 +154,124 @@ impl Transformation {
122154
new_passes
123155
}
124156
}
157+
158+
#[cfg(test)]
159+
mod tests {
160+
use super::*;
161+
use crate::config::{Arguments, Compiler, IgnoreOrConsider};
162+
use crate::semantic::{CompilerCall, CompilerPass, Transform};
163+
use std::path::PathBuf;
164+
165+
#[test]
166+
fn test_apply_no_filter() {
167+
let input = CompilerCall {
168+
compiler: std::path::PathBuf::from("gcc"),
169+
passes: vec![CompilerPass::Compile {
170+
source: PathBuf::from("main.c"),
171+
output: PathBuf::from("main.o").into(),
172+
flags: vec!["-O2".into()],
173+
}],
174+
working_dir: std::path::PathBuf::from("/project"),
175+
};
176+
177+
let sut = Transformation::from(&config::Output::Semantic {});
178+
let result = sut.apply(input);
179+
180+
let expected = CompilerCall {
181+
compiler: std::path::PathBuf::from("gcc"),
182+
passes: vec![CompilerPass::Compile {
183+
source: PathBuf::from("main.c"),
184+
output: PathBuf::from("main.o").into(),
185+
flags: vec!["-O2".into()],
186+
}],
187+
working_dir: std::path::PathBuf::from("/project"),
188+
};
189+
assert_eq!(result, Some(expected));
190+
}
191+
192+
#[test]
193+
fn test_apply_filter_match() {
194+
let input = CompilerCall {
195+
compiler: std::path::PathBuf::from("cc"),
196+
passes: vec![CompilerPass::Compile {
197+
source: PathBuf::from("main.c"),
198+
output: PathBuf::from("main.o").into(),
199+
flags: vec!["-O2".into()],
200+
}],
201+
working_dir: std::path::PathBuf::from("/project"),
202+
};
203+
204+
let sut: Transformation = vec![Compiler {
205+
path: std::path::PathBuf::from("cc"),
206+
ignore: IgnoreOrConsider::Always,
207+
arguments: Arguments::default(),
208+
}]
209+
.as_slice()
210+
.into();
211+
let result = sut.apply(input);
212+
assert!(result.is_none());
213+
}
214+
215+
#[test]
216+
fn test_apply_conditional_match() {
217+
let input = CompilerCall {
218+
compiler: std::path::PathBuf::from("gcc"),
219+
passes: vec![CompilerPass::Compile {
220+
source: PathBuf::from("main.c"),
221+
output: PathBuf::from("main.o").into(),
222+
flags: vec!["-O2".into(), "-Wall".into()],
223+
}],
224+
working_dir: std::path::PathBuf::from("/project"),
225+
};
226+
227+
let sut: Transformation = vec![Compiler {
228+
path: std::path::PathBuf::from("gcc"),
229+
ignore: IgnoreOrConsider::Conditional,
230+
arguments: Arguments {
231+
match_: vec!["-O2".into()],
232+
..Arguments::default()
233+
},
234+
}]
235+
.as_slice()
236+
.into();
237+
let result = sut.apply(input);
238+
assert!(result.is_none());
239+
}
240+
241+
#[test]
242+
fn test_apply_ignore_never_modify_arguments() {
243+
let input = CompilerCall {
244+
compiler: std::path::PathBuf::from("gcc"),
245+
passes: vec![CompilerPass::Compile {
246+
source: PathBuf::from("main.c"),
247+
output: PathBuf::from("main.o").into(),
248+
flags: vec!["-O2".into()],
249+
}],
250+
working_dir: std::path::PathBuf::from("/project"),
251+
};
252+
253+
let sut: Transformation = vec![Compiler {
254+
path: std::path::PathBuf::from("gcc"),
255+
ignore: IgnoreOrConsider::Never,
256+
arguments: Arguments {
257+
add: vec!["-Wall".into()],
258+
remove: vec!["-O2".into()],
259+
..Arguments::default()
260+
},
261+
}]
262+
.as_slice()
263+
.into();
264+
let result = sut.apply(input);
265+
266+
let expected = CompilerCall {
267+
compiler: std::path::PathBuf::from("gcc"),
268+
passes: vec![CompilerPass::Compile {
269+
source: PathBuf::from("main.c"),
270+
output: PathBuf::from("main.o").into(),
271+
flags: vec!["-Wall".into()],
272+
}],
273+
working_dir: std::path::PathBuf::from("/project"),
274+
};
275+
assert_eq!(result, Some(expected));
276+
}
277+
}

0 commit comments

Comments
 (0)