fn line_has_unquoted_var(line: &str) -> bool {
let bytes = line.as_bytes();
let mut in_double_quotes = false;
let mut in_single_quotes = false;
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b'\'' && !in_double_quotes {
in_single_quotes = !in_single_quotes;
i += 1;
continue;
}
if b == b'"' && !in_single_quotes {
in_double_quotes = !in_double_quotes;
i += 1;
continue;
}
if b == b'\\' && i + 1 < bytes.len() {
i += 2;
continue;
}
if b == b'$' && !in_single_quotes && !in_double_quotes {
if i + 1 < bytes.len() {
let next = bytes[i + 1];
if next.is_ascii_alphabetic() || next == b'_' || next == b'{' {
return true;
}
}
}
i += 1;
}
false
}
fn score_to_grade(score: f64) -> String {
match score as u32 {
97..=100 => "A+",
93..=96 => "A",
90..=92 => "A-",
87..=89 => "B+",
83..=86 => "B",
80..=82 => "B-",
77..=79 => "C+",
73..=76 => "C",
70..=72 => "C-",
60..=69 => "D",
_ => "F",
}
.to_string()
}
pub fn export_jsonl(rows: &[DatasetRow]) -> String {
rows.iter()
.filter_map(|row| serde_json::to_string(row).ok())
.collect::<Vec<_>>()
.join("\n")
}
pub fn export_classification_jsonl(rows: &[DatasetRow]) -> String {
rows.iter()
.map(|row| {
let cr = classify_single(
&row.input_rust,
row.transpiled,
row.lint_clean,
row.deterministic,
);
serde_json::to_string(&cr).unwrap_or_default()
})
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("\n")
}
pub fn classify_single(
original_input: &str,
transpiled: bool,
lint_clean: bool,
deterministic: bool,
) -> ClassificationRow {
let label = if transpiled && lint_clean && deterministic {
0
} else {
1
};
ClassificationRow {
input: strip_shell_preamble(original_input),
label,
}
}
pub fn strip_shell_preamble(script: &str) -> String {
let body: Vec<&str> = script
.lines()
.filter(|line| {
let s = line.trim();
!is_shell_preamble(s) && s != "main() {" && s != "}" && s != "'"
})
.map(|line| {
let trimmed = line.trim_start();
if trimmed.is_empty() {
line
} else {
trimmed
}
})
.collect();
if body.is_empty() {
return script.to_string();
}
body.join("\n")
}
pub fn is_shell_preamble(s: &str) -> bool {
s.is_empty()
|| s.starts_with('#')
|| s.starts_with("set ")
|| s.starts_with("IFS=")
|| s.starts_with("export ")
|| s.starts_with("trap ")
|| s == "main \"$@\""
}
pub fn export_multi_label_classification_jsonl(rows: &[DatasetRow]) -> String {
rows.iter()
.filter(|row| row.transpiled)
.map(|row| {
let labels = derive_multi_label(
&row.actual_output,
row.transpiled,
row.lint_clean,
row.deterministic,
);
let ml = MultiLabelClassificationRow {
input: strip_shell_preamble(&row.actual_output),
labels,
};
serde_json::to_string(&ml).unwrap_or_default()
})
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("\n")
}
pub fn derive_multi_label(
shell_output: &str,
transpiled: bool,
lint_clean: bool,
deterministic: bool,
) -> [f32; 5] {
let mut labels = [0.0f32; 5];
if !transpiled || !lint_clean {
labels[4] = 1.0;
}
if !deterministic {
labels[2] = 1.0;
}
if has_non_idempotent_pattern(shell_output) {
labels[3] = 1.0;
}
if has_unquoted_variable(shell_output) {
labels[1] = 1.0;
}
if labels.iter().all(|&v| v < 0.5) {
labels[0] = 1.0;
}
labels
}
include!("dataset_export.rs");