df_ls_structure 0.3.0-rc.1

A language server for Dwarf Fortress RAW files
Documentation
use df_ls_core::{ArgN, Choose, Clamp, Reference, ReferenceTo, Referenceable};
use df_ls_diagnostics::{hash_map, DiagnosticsInfo};
use df_ls_syntax_analysis::{Token, TokenDeserialize, TryFromArgumentGroup};
use serde::{Deserialize, Serialize};

use crate::{BpCriteriaTokenArg, MaterialToken, TissueToken};

#[derive(
    Serialize, Deserialize, Clone, Debug, Default, TokenDeserialize, PartialEq, Eq, Referenceable,
)]
pub struct BodyDetailPlanToken {
    /// Argument 1 of `[BODY_DETAIL_PLAN:...]`
    #[token_de(token = "BODY_DETAIL_PLAN", on_duplicate_to_parent, primary_token)]
    #[referenceable(self_reference)]
    pub reference: Option<ReferenceTo<Self>>,
    /// Adds a new material to the creature based on the specified template and assigned to the
    /// specified identifier.
    #[token_de(token = "ADD_MATERIAL")]
    pub add_material: Vec<(Reference, ReferenceTo<MaterialToken>)>,
    /// Adds a new tissue to the creature based on the specified template and assigned to the
    /// specified identifier.
    #[token_de(token = "ADD_TISSUE")]
    pub add_tissue: Vec<(Reference, ReferenceTo<TissueToken>)>,
    /// Defines a series of tissue layers composing the specified body parts. Alternatively to
    /// specifying a tissue, variable arguments can be entered (numbered arbitrarily to a max of 5)
    /// to be filled with tissues when the plan is called in the creature entry. The `SELECT_TISSUE`
    /// creature token with `TL_RELATIVE_THICKNESS` can change tissue thickness, but tissue layering
    /// is hard to do without a new detail plan.
    #[token_de(token = "BP_LAYERS")]
    pub bp_layers: Vec<(BpCriteriaTokenArg, Vec<BpLayerTokenArg>)>,
    /// Defines a series of tissue layers over the specified body parts. Alternatively to specifying
    /// a tissue, variable arguments can be entered (numbered arbitrarily to a max of 5) to be
    /// filled with tissues when the plan is called in the creature entry. The `SELECT_TISSUE`
    /// creature token with `TL_RELATIVE_THICKNESS` can change tissue thickness, but tissue layering
    /// is hard to do without a new detail plan.
    #[token_de(token = "BP_LAYERS_OVER")]
    pub bp_layers_over: Vec<(BpCriteriaTokenArg, Vec<BpLayerTokenArg>)>,
    /// Defines a series of tissue layers under the specified body parts. Alternatively to
    /// specifying a tissue, variable arguments can be entered (numbered arbitrarily to a max of 5)
    /// to be filled with tissues when the plan is called in the creature entry. The `SELECT_TISSUE`
    /// creature token with `TL_RELATIVE_THICKNESS` can change tissue thickness, but tissue layering
    /// is hard to do without a new detail plan.
    #[token_de(token = "BP_LAYERS_UNDER")]
    pub bp_layers_under: Vec<(BpCriteriaTokenArg, Vec<BpLayerTokenArg>)>,
    /// Defines a position for the specified body part (the nose is assigned the position `FRONT`,
    /// as it's on the front of the face). This has some effects on combat, attacks and the like.
    ///
    /// The position token `SIDES` is of unverified validity.
    #[token_de(token = "BP_POSITION")]
    pub bp_position: Vec<(BpCriteriaTokenArg, PositionEnum)>,
    /// Defines a positional relationship between one body part and another (for example, the right
    /// eyelid is `AROUND` the right eye with coverage 50, as it only partially covers the eye).
    /// This has some effects on combat, attacks and the like.
    #[token_de(token = "BP_RELATION")]
    pub bp_relation: Vec<(
        BpCriteriaTokenArg,
        BpRelationEnum,
        BpCriteriaTokenArg,
        Option<u8>,
    )>,
    /// Defines a relsize for the selected body part for the current body detail plan.
    #[token_de(token = "BP_RELSIZE")]
    pub bp_relsize: Vec<(BpCriteriaTokenArg, u32)>,
}

#[derive(Serialize, Deserialize, Clone, Debug, TokenDeserialize, PartialEq, Eq)]
#[token_de(enum_value)]
pub enum PositionEnum {
    #[token_de(token = "FRONT")]
    Front,
    #[token_de(token = "BACK")]
    Back,
    #[token_de(token = "LEFT")]
    Left,
    #[token_de(token = "RIGHT")]
    Right,
    #[token_de(token = "TOP")]
    Top,
    #[token_de(token = "BOTTOM")]
    Bottom,
}
impl Default for PositionEnum {
    fn default() -> Self {
        Self::Front
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, TokenDeserialize, PartialEq, Eq)]
#[token_de(enum_value)]
pub enum BpRelationEnum {
    /// Used to specify that the previously defined body part surrounds the following body part.
    #[token_de(token = "AROUND")]
    Around,
    /// Used to specify that the previously defined body part is surrounded by the following body part.
    #[token_de(token = "SURROUNDED_BY")]
    SurroundedBy,
    /// Used to specify that the previously defined body part is above the following body part.
    #[token_de(token = "ABOVE")]
    Above,
    /// Used to specify that the previously defined body part is below the following body part.
    #[token_de(token = "BELOW")]
    Below,
    /// Used to specify that the previously defined body part is in front of the following body part.
    #[token_de(token = "IN_FRONT")]
    InFront,
    /// Used to specify that the previously defined body part is behind the following body part.
    #[token_de(token = "BEHIND")]
    Behind,
    /// Used to specify a part that is cleaned by the previously defined part
    /// (eg, an eyelid cleans an eye).
    #[token_de(token = "CLEANS")]
    Cleans,
    /// Used to specify a part that cleans the previously defined part
    /// (eg, an eye is cleaned by an eyelid).
    #[token_de(token = "CLEANED_BY")]
    CleanedBy,
}
impl Default for BpRelationEnum {
    fn default() -> Self {
        Self::Around
    }
}

// region: BP_LAYERS args =========================================================================

// TODO: research; can you have more than 5 of these total in one BP_LAYERS?
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
pub struct BpLayerTokenArg {
    pub tissue: (
        // TODO: research; are multiple uses of the same arg allowed?
        Choose<Clamp<ArgN, 1, 5>, Reference>,
        // TODO: research; find out if this is actually a percentage
        u32,
    ),
    pub position_or_relation:
        Option<Choose<PositionEnum, (BpRelationEnum, BpCriteriaTokenArg, Option<u8>)>>,
}

// Deserialize a token with following pattern: `[REF:bp_layer_token_arg:...]`
df_ls_syntax_analysis::token_deserialize_unary_token!(BpLayerTokenArg);

impl TryFromArgumentGroup for BpLayerTokenArg {
    fn try_from_argument_group(
        token: &mut Token,
        source: &str,
        diagnostics: &mut DiagnosticsInfo,
        add_diagnostics_on_err: bool,
    ) -> Result<Self, ()> {
        let mut result = Self {
            tissue: <(Choose<Clamp<ArgN, 1, 5>, Reference>, u32)>::try_from_argument_group(
                token,
                source,
                diagnostics,
                add_diagnostics_on_err,
            )?,
            ..Default::default()
        };

        // Now that the actual tissue is sorted out, see if it has the optional arguments;
        // if not, finish this BpLayerTokenArg and thus move to the next.
        let mut token_clone1 = token.clone();
        let mut token_clone2 = token.clone();
        // Check which option finished without errors
        let option1 =
            PositionEnum::try_from_argument_group(&mut token_clone1, source, diagnostics, false);
        let option2 =
            BpRelationEnum::try_from_argument_group(&mut token_clone2, source, diagnostics, false);

        if option1.is_ok() {
            // Use option 1, even if option2 is valid
            // Add potential error messages
            let position = PositionEnum::try_from_argument_group(
                token,
                source,
                diagnostics,
                add_diagnostics_on_err,
            )?;
            result.position_or_relation = Some(Choose::Choice1(position));
        } else if option2.is_ok() {
            // Add potential error messages
            let relationship =
                <(BpRelationEnum, BpCriteriaTokenArg, Option<u8>)>::try_from_argument_group(
                    token,
                    source,
                    diagnostics,
                    add_diagnostics_on_err,
                )?;
            result.position_or_relation = Some(Choose::Choice2(relationship));
        } else {
            // It is a Ref but not one we expect, so do not consume and move on
        }
        Ok(result)
    }
}
// endregion ======================================================================================