Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions libs/sqf/src/analyze/inspector/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,18 @@ impl Inspector {
expected: var_types.iter().cloned().collect(),
found: vec![default_value.clone()],
span: element[1][0].1.clone(),
default: (element[2]
.first()
.map(|(_, s)| s.clone())
.unwrap_or_default()
.start)
..(element[2]
.last()
default: Some(
(element[2]
.first()
.map(|(_, s)| s.clone())
.unwrap_or_default()
.end),
.start)
..(element[2]
.last()
.map(|(_, s)| s.clone())
.unwrap_or_default()
.end),
),
});
}
var_types.insert(default_value);
Expand Down
211 changes: 136 additions & 75 deletions libs/sqf/src/analyze/inspector/game_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::ops::Range;

use arma3_wiki::model::{Arg, Call, Param, Value};
use indexmap::IndexSet;
#[allow(unused_imports)]
use tracing::{trace, warn};

use crate::{Expression, parser::database::Database};
Expand Down Expand Up @@ -88,6 +89,8 @@ impl GameValue {

let mut expected_lhs = IndexSet::new();
let mut expected_rhs = IndexSet::new();
let mut lhs_matched_all = true;
let mut rhs_matched_all = true;

for syntax in command.syntax() {
// println!("syntax {:?}", syntax.call());
Expand All @@ -106,6 +109,7 @@ impl GameValue {
rhs_arg,
syntax.params(),
);
rhs_matched_all = rhs_matched_all && is_match;
expected_rhs.extend(expected);
is_match
}
Expand All @@ -125,6 +129,7 @@ impl GameValue {
lhs_arg,
syntax.params(),
);
lhs_matched_all = lhs_matched_all && is_match;
expected_lhs.extend(expected);
is_match
};
Expand All @@ -135,6 +140,7 @@ impl GameValue {
rhs_arg,
syntax.params(),
);
rhs_matched_all = rhs_matched_all && is_match;
expected_rhs.extend(expected);
is_match
};
Expand All @@ -144,9 +150,15 @@ impl GameValue {
}
}
let value = &syntax.ret().0;
let game_value = Self::from_wiki_value(value);
let game_value = Self::from_wiki_value(value, NilSource::CommandReturn);
// println!("match syntax {syntax:?}");
return_types.insert(game_value);
return_types.extend(game_value);
}
if rhs_matched_all {
expected_rhs.clear();
}
if lhs_matched_all {
expected_lhs.clear();
}
// println!("lhs_set {lhs_set:?}");
// println!("rhs_set {rhs_set:?}");
Expand All @@ -169,43 +181,15 @@ impl GameValue {
match arg {
Arg::Item(name) => {
let Some(param) = params.iter().find(|p| p.name() == name) else {
/// Varidic cmds which will be missing wiki param matches (only affects debug logging)
const WIKI_CMDS_IGNORE_MISSING_PARAM: &[&str] = &[
"addResources",
"createTask",
"ctRemoveRows",
"format",
"formatText",
"getGraphValues",
"inAreaArray",
"inAreaArrayIndexes",
"insert",
"kbReact",
"kbTell",
"lineIntersects",
"lineIntersectsObjs",
"lineIntersectsSurfaces",
"lineIntersectsWith",
"param",
"params",
"ppEffectCreate",
"set3DENAttributes",
"set3DENMissionAttributes",
"setAttributes",
"setGroupId",
"setGroupIdGlobal",
"setPiPEffect",
"textLogFormat",
];
if !WIKI_CMDS_IGNORE_MISSING_PARAM.contains(&cmd_name) {
// warn!("cmd {cmd_name} - param {name} not found");
}
return (true, IndexSet::new());
};
let (is_match, expected_gv) =
let (is_match, expected_gvs) =
Self::match_set_to_value(set, param.typ(), param.optional());
let mut expected = IndexSet::new();
expected.insert(expected_gv);
expected.extend(expected_gvs);
(is_match, expected)
}
Arg::Array(arg_array) => {
Expand All @@ -214,6 +198,7 @@ impl GameValue {
match s {
Self::Anything | Self::Array(None, _) => true,
Self::Array(Some(gv_array), _) => {
let mut expected_array_args = Vec::new();
// println!("{cmd_name}: array (gv: {}) expected (arg: {})", gv_array.len(), arg_array.len());
// note: some syntaxes take more than others
for (index, arg) in arg_array.iter().enumerate() {
Expand All @@ -224,7 +209,9 @@ impl GameValue {
};
let (is_match, expected_gv) =
Self::match_set_to_arg(cmd_name, &possible, arg, params);
expected.extend(expected_gv);
// Convert IndexSet<GameValue> to Vec<(GameValue, Range<usize>)> with dummy ranges.
expected_array_args
.push(expected_gv.into_iter().map(|gv| (gv, 0..0)).collect());
if !is_match {
if let Arg::Array(args) = arg {
// handle edge case on varidic cmds that take arrays (e.g. `createHashMapFromArray`)
Expand All @@ -239,6 +226,7 @@ impl GameValue {
}
}
// println!("array arg {index} no match {arg:?} in {s:?}");
expected.insert(Self::Array(Some(expected_array_args), None));
return false;
}
}
Expand All @@ -257,18 +245,22 @@ impl GameValue {

#[must_use]
/// checks if a set of `GameValues` matches a wiki Value (with optional flag)
/// returns the expected `GameValue` type
/// returns the expected `GameValue` types
pub fn match_set_to_value(
set: &IndexSet<Self>,
right_wiki: &Value,
optional: bool,
) -> (bool, Self) {
) -> (bool, IndexSet<Self>) {
// println!("Checking {set:?} against {right_wiki:?} [O:{optional}]");
let right = Self::from_wiki_value(right_wiki);
let right = Self::from_wiki_value(right_wiki, NilSource::CommandReturn);
if optional && (set.is_empty() || set.iter().any(|gv| matches!(gv, Self::Nothing(_)))) {
return (true, right);
}
(set.iter().any(|gv| Self::match_values(gv, &right)), right)
(
set.iter()
.any(|gv| right.iter().any(|r| Self::match_values(gv, r))),
right,
)
}

#[must_use]
Expand All @@ -292,12 +284,11 @@ impl GameValue {
}
std::mem::discriminant(left) == std::mem::discriminant(right)
}

#[must_use]
/// Maps from Wiki:Value to Inspector:GameValue
pub fn from_wiki_value(value: &Value) -> Self {
/// Maps from `Wiki:Value` to Set of `GameValues`
pub fn from_wiki_value(value: &Value, nil_type: NilSource) -> IndexSet<Self> {
match value {
Value::Anything | Value::EdenEntity => Self::Anything,
Value::Anything | Value::EdenEntity => IndexSet::from([Self::Anything]),
Value::ArrayColor
| Value::ArrayColorRgb
| Value::ArrayColorRgba
Expand All @@ -306,43 +297,51 @@ impl GameValue {
| Value::ArrayUnknown
| Value::ArrayUnsized { .. }
| Value::Position // position is often too generic to match?
| Value::Waypoint => Self::Array(None, None),
| Value::Waypoint => IndexSet::from([Self::Array(None, None)]),
// Value::Position3dAGLS => Self::Array(None, Some(ArrayType::PosAGLS)),
Value::Position3dAGLS | Value::Position3dAGL => Self::Array(None, Some(ArrayType::PosAGL)), // merge
Value::Position3dASL => Self::Array(None, Some(ArrayType::PosASL)),
Value::Position3DASLW => Self::Array(None, Some(ArrayType::PosASLW)),
Value::Position3dATL => Self::Array(None, Some(ArrayType::PosATL)),
Value::Boolean => Self::Boolean(None),
Value::Code => Self::Code(None),
Value::Config => Self::Config,
Value::Control => Self::Control,
Value::DiaryRecord => Self::DiaryRecord,
Value::Display => Self::Display,
Value::ForType => Self::ForType(None),
Value::Group => Self::Group,
Value::HashMapUnknown => Self::HashMap,
Value::IfType => Self::IfType,
Value::Location => Self::Location,
Value::Namespace => Self::Namespace,
Value::Nothing => Self::Nothing(NilSource::CommandReturn),
Value::Number => Self::Number(None),
Value::Object => Self::Object,
Value::ScriptHandle => Self::ScriptHandle,
Value::Side => Self::Side,
Value::String => Self::String(None),
Value::SwitchType => Self::SwitchType,
Value::StructuredText => Self::StructuredText,
Value::Task => Self::Task,
Value::TeamMember => Self::TeamMember,
Value::WhileType => Self::WhileType,
Value::WithType => Self::WithType,
Value::Position3dAGLS | Value::Position3dAGL => IndexSet::from([Self::Array(None, Some(ArrayType::PosAGL))]), // merge
Value::Position3dASL => IndexSet::from([Self::Array(None, Some(ArrayType::PosASL))]),
Value::Position3DASLW => IndexSet::from([Self::Array(None, Some(ArrayType::PosASLW))]),
Value::Position3dATL => IndexSet::from([Self::Array(None, Some(ArrayType::PosATL))]),
Value::Boolean => IndexSet::from([Self::Boolean(None)]),
Value::Code => IndexSet::from([Self::Code(None)]),
Value::Config => IndexSet::from([Self::Config]),
Value::Control => IndexSet::from([Self::Control]),
Value::DiaryRecord => IndexSet::from([Self::DiaryRecord]),
Value::Display => IndexSet::from([Self::Display]),
Value::ForType => IndexSet::from([Self::ForType(None)]),
Value::Group => IndexSet::from([Self::Group]),
Value::HashMapUnknown => IndexSet::from([Self::HashMap]),
Value::IfType => IndexSet::from([Self::IfType]),
Value::Location => IndexSet::from([Self::Location]),
Value::Namespace => IndexSet::from([Self::Namespace]),
Value::Nothing => IndexSet::from([Self::Nothing(nil_type)]),
Value::Number => IndexSet::from([Self::Number(None)]),
Value::Object => IndexSet::from([Self::Object]),
Value::ScriptHandle => IndexSet::from([Self::ScriptHandle]),
Value::Side => IndexSet::from([Self::Side]),
Value::String => IndexSet::from([Self::String(None)]),
Value::SwitchType => IndexSet::from([Self::SwitchType]),
Value::StructuredText => IndexSet::from([Self::StructuredText]),
Value::Task => IndexSet::from([Self::Task]),
Value::TeamMember => IndexSet::from([Self::TeamMember]),
Value::WhileType => IndexSet::from([Self::WhileType]),
Value::WithType => IndexSet::from([Self::WithType]),
Value::OneOf(vec) => {
let mut set = IndexSet::new();
for (v, _) in vec {
set.extend(Self::from_wiki_value(v, nil_type.clone()));
}
set
}
Value::Unknown => {
#[cfg(debug_assertions)]
trace!("wiki has syntax with [unknown] type");
Self::Anything
IndexSet::from([Self::Anything])
}
_ => {
warn!("wiki type [{value:?}] not matched");
Self::Anything
IndexSet::from([Self::Anything])
}
}
}
Expand All @@ -356,6 +355,7 @@ impl GameValue {
Self::ForType(_) => Self::ForType(None),
Self::Number(_) => Self::Number(None),
Self::String(_) => Self::String(None),
Self::Nothing(_) => Self::Nothing(NilSource::Generic),
_ => self.clone(),
}
}
Expand Down Expand Up @@ -387,6 +387,38 @@ impl GameValue {
}
result.unwrap_or(Self::Anything)
}
/// Converts a vector of `GameValues` to a string representation
/// if it only contains a single element which is an array, it will format the array contents
pub fn vec_to_string(vec: &[Self], depth: usize) -> String {
fn to_string_array(value: &GameValue, depth: usize) -> String {
if depth == 0 {
return format!("{value}");
}
match value {
GameValue::Array(Some(vec), _) => {
let inner_types = vec.iter().map(|outer| {
let types = outer
.iter()
.map(|(gv, _)| to_string_array(gv, depth - 1))
.collect::<Vec<_>>();
format!("({})", types.join(", "))
});
format!("{value}[{}]", inner_types.collect::<Vec<_>>().join(", "))
}
_ => format!("{value}"),
}
}
if vec.len() == 1
&& let Some(gv) = vec.first()
{
to_string_array(gv, depth)
} else {
vec.iter()
.map(Self::to_string)
.collect::<Vec<_>>()
.join(", ")
}
}
}

impl std::fmt::Display for GameValue {
Expand All @@ -398,10 +430,10 @@ impl std::fmt::Display for GameValue {
f,
"{}",
match self {
Self::Anything => "Anything",
Self::Anything => "Any",
Self::Array(_, _) => "Array",
Self::Assignment => "Assignment",
Self::Boolean(_) => "Boolean",
Self::Boolean(_) => "Bool",
Self::Code(_) => "Code",
Self::Config => "Config",
Self::Control => "Control",
Expand All @@ -414,7 +446,7 @@ impl std::fmt::Display for GameValue {
Self::Location => "Location",
Self::Namespace => "Namespace",
Self::Number(_) => "Number",
Self::Nothing(_) => "Nothing",
Self::Nothing(_) => "Nil",
Self::Object => "Object",
Self::ScriptHandle => "ScriptHandle",
Self::Side => "Side",
Expand All @@ -429,3 +461,32 @@ impl std::fmt::Display for GameValue {
)
}
}

/// Varidic cmds which will be missing wiki param matches (only affects debug logging)
const WIKI_CMDS_IGNORE_MISSING_PARAM: &[&str] = &[
"addResources",
"createTask",
"ctRemoveRows",
"format",
"formatText",
"getGraphValues",
"inAreaArray",
"inAreaArrayIndexes",
"insert",
"kbReact",
"kbTell",
"lineIntersects",
"lineIntersectsObjs",
"lineIntersectsSurfaces",
"lineIntersectsWith",
"param",
"params",
"ppEffectCreate",
"set3DENAttributes",
"set3DENMissionAttributes",
"setAttributes",
"setGroupId",
"setGroupIdGlobal",
"setPiPEffect",
"textLogFormat",
];
Loading