1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//! Adapted from <https://github.com/YarnSpinnerTool/YarnSpinner/blob/da39c7195107d8211f21c263e4084f773b84eaff/YarnSpinner.Compiler/Declaration.cs>
//!
//! ## Implementation notes
//!
//! [`Range`] has been replaced with the more idiomatic [`Range<Position>`].

use crate::prelude::*;
use antlr_rust::rule_context::CustomRuleContext;
use antlr_rust::token::Token;
use antlr_rust::token_factory::TokenFactory;
use std::fmt::{Debug, Display};
use std::ops::Range;
use yarnspinner_core::prelude::*;
use yarnspinner_core::types::Type;

/// Information about a declaration. Stored inside a declaration table,
/// which is produced from the Compiler.
///
/// You do not create instances of this class yourself. They are
/// generated by the [`Compiler`].
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "bevy", reflect(Debug, PartialEq))]
#[cfg_attr(
    all(feature = "bevy", feature = "serde"),
    reflect(Serialize, Deserialize)
)]
pub struct Declaration {
    /// The name of this declaration.
    pub name: String,

    /// The default value of this declaration, if no value has been
    /// specified in code or is available from a Dialogue's variable storage.
    pub default_value: Option<YarnValue>,

    /// A string describing the purpose of this declaration.
    pub description: Option<String>,

    /// The name of the file in which this declaration was found.
    ///
    /// If this declaration was not found in a Yarn source file, this
    /// will be [`DeclarationSource::External`].
    pub source_file_name: DeclarationSource,

    /// The name of the node in which this declaration was found.
    ///
    /// If this declaration was not found in a Yarn source file, this
    /// will be [`None`].
    pub source_node_name: Option<String>,

    /// A value indicating whether this declaration was implicitly
    /// inferred from usage.
    ///
    /// If `true`, this declaration was implicitly inferred from usage.
    /// If `false`, this declaration appears in the source code.
    pub is_implicit: bool,

    /// The type of the variable, as represented by an object found
    /// in a variant of [`Type`].
    pub r#type: Type,

    /// The range of text at which this declaration occurs.
    ///
    /// This range refers to the declaration of the symbol itself, and
    /// not any syntax surrounding it. For example, the declaration
    /// `<<declare $x = 1>>` would have a range referring to the `$x`
    /// symbol.
    pub range: Option<Range<Position>>,
}

impl Declaration {
    /// Gets the line number at which this Declaration was found in the
    /// source file.
    ///
    /// If this [`Declaration`] was not found in a Yarn source file,
    /// this will be [`None`].
    pub fn source_file_line(&self) -> Option<usize> {
        self.range.as_ref()?.start.line.into()
    }

    #[doc(hidden)]
    pub fn new(name: impl Into<String>, r#type: impl Into<Type>) -> Self {
        Self {
            name: name.into(),
            r#type: r#type.into(),
            default_value: Default::default(),
            description: Default::default(),
            source_file_name: Default::default(),
            source_node_name: Default::default(),
            is_implicit: Default::default(),
            range: Default::default(),
        }
    }

    #[doc(hidden)]
    pub fn with_type(mut self, r#type: impl Into<Type>) -> Self {
        self.r#type = r#type.into();
        self
    }

    #[doc(hidden)]
    pub fn with_default_value(mut self, default_value: impl Into<YarnValue>) -> Self {
        self.default_value = Some(default_value.into());
        self
    }

    #[doc(hidden)]
    pub fn with_name(mut self, name: impl Into<String>) -> Self {
        self.name = name.into();
        self
    }

    #[doc(hidden)]
    pub fn with_description(mut self, description: impl Into<String>) -> Self {
        self.description = Some(description.into());
        self
    }

    #[doc(hidden)]
    pub fn with_description_optional(mut self, description: impl Into<Option<String>>) -> Self {
        self.description = description.into();
        self
    }

    #[doc(hidden)]
    pub fn with_source_file_name(mut self, source_file_name: impl Into<DeclarationSource>) -> Self {
        self.source_file_name = source_file_name.into();
        self
    }

    #[doc(hidden)]
    pub fn with_source_node_name(mut self, source_node_name: impl Into<String>) -> Self {
        self.source_node_name = Some(source_node_name.into());
        self
    }

    #[doc(hidden)]
    pub fn with_source_node_name_optional(
        mut self,
        source_node_name: impl Into<Option<String>>,
    ) -> Self {
        self.source_node_name = source_node_name.into();
        self
    }

    #[doc(hidden)]
    pub fn with_implicit(mut self) -> Self {
        self.is_implicit = true;
        self
    }

    #[doc(hidden)]
    pub fn with_range(mut self, range: impl Into<Range<Position>>) -> Self {
        self.range = Some(range.into());
        self
    }

    #[doc(hidden)]
    pub fn eq(&self, other: &Self, epsilon: f32) -> bool {
        self.name == other.name
            && self.description == other.description
            && self.source_file_name == other.source_file_name
            && self.source_node_name == other.source_node_name
            && self.is_implicit == other.is_implicit
            && self.r#type == other.r#type
            && self.range == other.range
            && match (&self.default_value, &other.default_value) {
                (Some(a), Some(b)) => a.eq(b, epsilon),
                (None, None) => true,
                _ => false,
            }
    }
}

/// The source of a declaration.
///
/// ## Implementation notes
///
/// In the original implementation, [`DeclarationSource::External`] is just a magic string.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "bevy", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "bevy", reflect(Debug, PartialEq, Default))]
#[cfg_attr(
    all(feature = "bevy", feature = "serde"),
    reflect(Serialize, Deserialize)
)]
pub enum DeclarationSource {
    /// The declaration was generated by the compiler.
    #[default]
    External,
    /// The name of the file in which the declaration was found.
    File(String),
}

impl<T> From<T> for DeclarationSource
where
    T: Into<String>,
{
    fn from(file_name: T) -> Self {
        Self::File(file_name.into())
    }
}

pub(crate) trait ParserRuleContextExtRangeSource<'input>:
    ParserRuleContextExt<'input>
where
    <<<<Self as CustomRuleContext<'input>>::TF as TokenFactory<'input>>::Inner as Token>::Data as ToOwned>::Owned:
        Into<String>,
{
    fn range(&self) -> Range<Position> {
        let start = Position {
            line: self.start().get_line_as_usize().saturating_sub(1),
            character: self.start().get_column_as_usize(),
        };
        let text: String = self.stop().get_text().to_owned().into();
        let stop = Position {
            line: self.stop().get_line_as_usize().saturating_sub(1),
            character: self.stop().get_column_as_usize() + text.len(),
        };
        start..stop
    }
}

impl<'input, T: ParserRuleContextExt<'input>> ParserRuleContextExtRangeSource<'input> for T where
    <<<<T as CustomRuleContext<'input>>::TF as TokenFactory<'input>>::Inner as Token>::Data as ToOwned>::Owned:
        Into<String>
{
}

impl Display for DeclarationSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::External => write!(f, "(External)"),
            Self::File(file_name) => write!(f, "{}", file_name),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct DeferredTypeDiagnostic {
    pub(crate) name: String,
    pub(crate) diagnostic: Diagnostic,
}