use std::collections::BTreeSet;
use crate::parser::ast::{Argument, CommandInvocation, File, Statement};
use crate::parser::{self};
use crate::spec::registry::CommandRegistry;
use crate::spec::{CommandForm, KwargSpec};
pub fn semantic_equivalent(left: &str, right: &str) -> bool {
let registry = CommandRegistry::builtins();
match (parser::parse(left), parser::parse(right)) {
(Ok(left), Ok(right)) => {
normalize_semantics(left, registry) == normalize_semantics(right, registry)
}
_ => true,
}
}
pub fn normalize_semantics(mut file: File, registry: &CommandRegistry) -> File {
file.statements
.retain(|s| !matches!(s, Statement::Comment(_) | Statement::BlankLines(_)));
for statement in &mut file.statements {
match statement {
Statement::Command(command) => {
command.span = (0, 0);
command.name.make_ascii_lowercase();
normalize_command_literals(command);
normalize_keyword_args(command, registry);
}
Statement::TemplatePlaceholder(value) => normalize_line_endings(value),
Statement::Comment(_) | Statement::BlankLines(_) => unreachable!(),
}
}
file
}
pub fn normalize_command_literals(command: &mut CommandInvocation) {
command.trailing_comment = None;
command
.arguments
.retain(|a| !matches!(a, Argument::InlineComment(_)));
for argument in &mut command.arguments {
match argument {
Argument::Bracket(bracket) => normalize_line_endings(&mut bracket.raw),
Argument::Quoted(value) | Argument::Unquoted(value) => normalize_line_endings(value),
Argument::InlineComment(_) => unreachable!(),
}
}
}
pub fn normalize_keyword_args(command: &mut CommandInvocation, registry: &CommandRegistry) {
let spec = registry.get(&command.name);
let first_arg = command.arguments.iter().find_map(first_arg_text);
let form = spec.form_for(first_arg);
let keyword_set = collect_keywords(form);
for arg in &mut command.arguments {
if let Argument::Unquoted(value) = arg {
let upper = value.to_ascii_uppercase();
if keyword_set.contains(upper.as_str()) {
*value = upper;
}
}
}
}
pub fn normalize_line_endings(value: &mut String) {
if value.contains('\r') {
*value = value.replace("\r\n", "\n");
}
}
fn first_arg_text(argument: &Argument) -> Option<&str> {
match argument {
Argument::Quoted(_) | Argument::Bracket(_) | Argument::InlineComment(_) => None,
Argument::Unquoted(value) => Some(value.as_str()),
}
}
fn collect_keywords(form: &CommandForm) -> BTreeSet<String> {
let mut keywords = BTreeSet::new();
collect_form_keywords(form, &mut keywords);
keywords
}
fn collect_form_keywords(form: &CommandForm, keywords: &mut BTreeSet<String>) {
keywords.extend(form.flags.iter().cloned());
for (name, spec) in &form.kwargs {
keywords.insert(name.clone());
collect_kwarg_keywords(spec, keywords);
}
}
fn collect_kwarg_keywords(spec: &KwargSpec, keywords: &mut BTreeSet<String>) {
keywords.extend(spec.flags.iter().cloned());
for (name, child) in &spec.kwargs {
keywords.insert(name.clone());
collect_kwarg_keywords(child, keywords);
}
}