use super::*;
use workshop_rs::source::{Position as WorkshopPosition, Span as WorkshopSpan};
pub(super) fn merge_extensions(
settings: Option<crate::hir::Settings>,
directives: &[crate::hir::DirectiveRecord],
) -> Result<Option<workshop_rs::settings::Settings>, IntegrationError> {
let mut extension_nodes: Vec<workshop_rs::settings::SettingsNode> = Vec::new();
for directive in directives
.iter()
.filter(|directive| directive.name == "extension")
{
let Some(name) = directive.value.as_deref() else {
return Err(IntegrationError::new(
"directive-invalid",
"`#!extension` expects one argument",
directive.span,
));
};
let span = directive.span;
if extension_nodes.iter().any(|node| node.name() == name) {
continue;
}
let path = [
workshop_rs::settings::PathPart::Part("extensions"),
workshop_rs::settings::PathPart::Part(name),
];
let Some(definition) = workshop_rs::settings::definition(&path) else {
return Err(IntegrationError::new(
"directive-invalid",
format!("unknown Workshop extension `{name}`"),
span,
));
};
if !matches!(
definition.domain(),
workshop_rs::settings::SettingValueDomain::PresenceOnly
) {
return Err(IntegrationError::new(
"directive-invalid",
format!("Workshop extension `{name}` is not a presence-only setting"),
span,
));
}
extension_nodes.push(workshop_rs::settings::SettingsNode::Flag {
name: name.to_string(),
span: span.map(convert_settings_span),
});
}
if extension_nodes.is_empty() {
return Ok(settings.map(convert_settings));
}
let mut settings = settings
.map(convert_settings)
.unwrap_or(workshop_rs::settings::Settings {
span: None,
children: Vec::new(),
});
if let Some(workshop_rs::settings::SettingsNode::Group { children, .. }) = settings
.children
.iter_mut()
.find(|node| node.name() == "extensions")
{
for extension in extension_nodes {
if !children.iter().any(|node| node.name() == extension.name()) {
children.push(extension);
}
}
} else {
settings
.children
.push(workshop_rs::settings::SettingsNode::Group {
name: "extensions".to_string(),
children: extension_nodes,
span: None,
});
}
Ok(Some(settings))
}
pub(super) fn expand_settings_constants(
settings: crate::hir::Settings,
constants: &HashMap<String, &Expr>,
) -> crate::hir::Settings {
crate::hir::Settings {
span: settings.span,
children: settings
.children
.into_iter()
.map(|node| expand_settings_node(node, constants))
.collect(),
}
}
fn expand_settings_node(
node: crate::hir::SettingsNode,
constants: &HashMap<String, &Expr>,
) -> crate::hir::SettingsNode {
use crate::hir::SettingsNode;
match node {
SettingsNode::Group {
name,
children,
span,
} => {
let children = children
.into_iter()
.map(|child| expand_settings_node(child, constants))
.collect::<Vec<_>>();
let children = if matches!(name.as_str(), "team1" | "team2" | "allTeams") {
general_first(children)
} else {
children
};
SettingsNode::Group {
name,
children,
span,
}
}
SettingsNode::Raw { name, value, span } => constants
.get(&value)
.and_then(|expr| settings_node_from_expr(name.clone(), expr, constants, span))
.unwrap_or(SettingsNode::Raw { name, value, span }),
node => node,
}
}
fn settings_node_from_expr(
name: String,
expr: &Expr,
constants: &HashMap<String, &Expr>,
span: Option<HirSpan>,
) -> Option<crate::hir::SettingsNode> {
use crate::hir::SettingsNode;
match expr {
Expr::Constant { name: value, .. } => constants
.get(value)
.and_then(|expr| settings_node_from_expr(name, expr, constants, span)),
Expr::Dict { entries, .. } => Some(SettingsNode::Group {
name,
children: general_first(
entries
.iter()
.filter_map(|entry| {
let Expr::String {
value: child_name, ..
} = entry.key.as_ref()
else {
return None;
};
settings_node_from_expr(
child_name.clone(),
&entry.value,
constants,
entry.span,
)
})
.collect(),
),
span,
}),
Expr::Number { value, .. } => Some(SettingsNode::Number {
name,
value: *value,
span,
}),
Expr::Bool { value, .. } => Some(SettingsNode::Bool {
name,
value: *value,
span,
}),
Expr::String { value, .. } => Some(SettingsNode::String {
name,
value: value.clone(),
span,
}),
_ => None,
}
}
pub(super) fn convert_settings(settings: crate::hir::Settings) -> workshop_rs::settings::Settings {
workshop_rs::settings::Settings {
span: settings.span.map(convert_settings_span),
children: settings
.children
.into_iter()
.map(convert_settings_node)
.collect(),
}
}
fn convert_settings_node(node: crate::hir::SettingsNode) -> workshop_rs::settings::SettingsNode {
use crate::hir::SettingsNode as SourceNode;
use workshop_rs::settings::{SettingsListElement, SettingsNode as TargetNode};
match node {
SourceNode::Group {
name,
children,
span,
} if name == "workshop" => TargetNode::Workshop {
children: children.into_iter().map(convert_workshop_node).collect(),
span: span.map(convert_settings_span),
},
SourceNode::Group {
name,
children,
span,
} => TargetNode::Group {
children: children
.into_iter()
.map(|child| escape_main_string(&name, child))
.map(convert_settings_node)
.collect(),
name,
span: span.map(convert_settings_span),
},
SourceNode::Number { name, value, span } => TargetNode::Number {
name,
value,
span: span.map(convert_settings_span),
},
SourceNode::Bool { name, value, span } => TargetNode::Bool {
name,
value,
span: span.map(convert_settings_span),
},
SourceNode::String { name, value, span } => TargetNode::String {
name: name.clone(),
value: if name == "mapRotation" && value == "afterGame" {
"afterAGame".to_string()
} else {
value
},
span: span.map(convert_settings_span),
},
SourceNode::Raw { name, value, span } => TargetNode::Raw {
name,
value,
span: span.map(convert_settings_span),
},
SourceNode::List {
name,
elements,
span,
} => TargetNode::List {
name,
elements: elements
.into_iter()
.map(|element| SettingsListElement {
value: element.value,
span: element.span.map(convert_settings_span),
})
.collect(),
span: span.map(convert_settings_span),
},
}
}
fn escape_main_string(group: &str, node: crate::hir::SettingsNode) -> crate::hir::SettingsNode {
use crate::hir::SettingsNode;
match node {
SettingsNode::String { name, value, span }
if group == "main" && matches!(name.as_str(), "modeName" | "description") =>
{
SettingsNode::String {
value: super::lowering::escape_bad_words(&value),
name,
span,
}
}
node => node,
}
}
fn convert_workshop_node(node: crate::hir::SettingsNode) -> workshop_rs::settings::SettingsNode {
use crate::hir::SettingsNode as SourceNode;
use workshop_rs::settings::SettingsNode as TargetNode;
match node {
SourceNode::Group {
name,
children,
span,
} => TargetNode::Group {
name,
children: children.into_iter().map(convert_workshop_node).collect(),
span: span.map(convert_settings_span),
},
SourceNode::Number { name, value, span } => TargetNode::Raw {
name,
value: value.to_string(),
span: span.map(convert_settings_span),
},
SourceNode::Bool { name, value, span } => TargetNode::Raw {
name,
value: value.to_string(),
span: span.map(convert_settings_span),
},
SourceNode::String { name, value, span } | SourceNode::Raw { name, value, span } => {
TargetNode::Raw {
name,
value,
span: span.map(convert_settings_span),
}
}
SourceNode::List {
name,
elements,
span,
} => TargetNode::Raw {
name,
value: format!(
"[{}]",
elements
.into_iter()
.map(|element| element.value)
.collect::<Vec<_>>()
.join(", ")
),
span: span.map(convert_settings_span),
},
}
}
fn convert_settings_span(span: HirSpan) -> WorkshopSpan {
WorkshopSpan::new(
workshop_rs::source::FileId::from_index(span.file as usize),
WorkshopPosition::new(span.start.line, span.start.col),
WorkshopPosition::new(span.end.line, span.end.col),
)
}
fn general_first(children: Vec<crate::hir::SettingsNode>) -> Vec<crate::hir::SettingsNode> {
use crate::hir::SettingsNode;
let mut general = Vec::new();
let mut rest = Vec::new();
for child in children {
match child {
SettingsNode::Group { name, children, .. } if name == "general" => {
general.extend(children);
}
child => rest.push(child),
}
}
general.extend(rest);
general
}