ps_parser/parser/
variables.rs

1mod function;
2mod scopes;
3mod variable;
4
5use std::collections::HashMap;
6
7pub(super) use function::FunctionMap;
8use phf::phf_map;
9pub(super) use scopes::SessionScope;
10use thiserror_no_std::Error;
11pub(super) use variable::{Scope, VarName};
12
13use crate::parser::{RuntimeTypeTrait, Val, value::ScriptBlock};
14#[derive(Error, Debug, PartialEq, Clone)]
15pub enum VariableError {
16    #[error("Variable \"{0}\" is not defined")]
17    NotDefined(String),
18    #[error("Cannot overwrite variable \"{0}\" because it is read-only or constant.")]
19    ReadOnly(String),
20}
21
22pub type VariableResult<T> = core::result::Result<T, VariableError>;
23pub type VariableMap = HashMap<String, Val>;
24
25#[derive(Clone, Default)]
26pub struct Variables {
27    env: VariableMap,
28    global_scope: VariableMap,
29    script_scope: VariableMap,
30    local_scopes_stack: Vec<VariableMap>,
31    state: State,
32    force_var_eval: bool,
33    values_persist: bool,
34    global_functions: FunctionMap,
35    script_functions: FunctionMap,
36    top_scope: TopScope,
37    //special variables
38    // status: bool, // $?
39    // first_token: Option<String>,
40    // last_token: Option<String>,
41    // current_pipeline: Option<String>,
42}
43
44#[derive(Debug, Default, Clone)]
45pub(super) enum TopScope {
46    #[default]
47    Session,
48    Script,
49}
50
51impl From<Scope> for TopScope {
52    fn from(scope: Scope) -> Self {
53        match scope {
54            Scope::Global => TopScope::Session,
55            Scope::Script => TopScope::Script,
56            _ => TopScope::Script,
57        }
58    }
59}
60
61#[derive(Clone)]
62enum State {
63    TopScope(TopScope),
64    Stack(u32),
65}
66
67impl Default for State {
68    fn default() -> Self {
69        State::TopScope(TopScope::default())
70    }
71}
72
73impl Variables {
74    const PREDEFINED_VARIABLES: phf::Map<&'static str, Val> = phf_map! {
75        "true" => Val::Bool(true),
76        "false" => Val::Bool(false),
77        "null" => Val::Null,
78    };
79
80    pub(crate) fn set_ps_item(&mut self, ps_item: Val) {
81        let _ = self.set(
82            &VarName::new_with_scope(Scope::Special, "$PSItem".into()),
83            ps_item.clone(),
84        );
85        let _ = self.set(
86            &VarName::new_with_scope(Scope::Special, "$_".into()),
87            ps_item,
88        );
89    }
90
91    pub(crate) fn reset_ps_item(&mut self) {
92        let _ = self.set(
93            &VarName::new_with_scope(Scope::Special, "$PSItem".into()),
94            Val::Null,
95        );
96        let _ = self.set(
97            &VarName::new_with_scope(Scope::Special, "$_".into()),
98            Val::Null,
99        );
100    }
101
102    pub fn set_status(&mut self, b: bool) {
103        let _ = self.set(
104            &VarName::new_with_scope(Scope::Special, "$?".into()),
105            Val::Bool(b),
106        );
107    }
108
109    pub fn status(&mut self) -> bool {
110        let Some(Val::Bool(b)) =
111            self.get_without_types(&VarName::new_with_scope(Scope::Special, "$?".into()))
112        else {
113            return false;
114        };
115        b
116    }
117
118    pub fn load_from_file(
119        &mut self,
120        path: &std::path::Path,
121    ) -> Result<(), Box<dyn std::error::Error>> {
122        let mut config_parser = configparser::ini::Ini::new();
123        let map = config_parser.load(path)?;
124        self.load(map)
125    }
126
127    pub fn load_from_string(&mut self, ini_string: &str) -> Result<(), Box<dyn std::error::Error>> {
128        let mut config_parser = configparser::ini::Ini::new();
129        let map = config_parser.read(ini_string.into())?;
130        self.load(map)
131    }
132
133    pub(super) fn init(&mut self, scope: TopScope) {
134        if !self.values_persist {
135            self.script_scope.clear();
136        }
137        self.local_scopes_stack.clear();
138        self.state = State::TopScope(scope.clone());
139        self.top_scope = scope;
140    }
141
142    fn load(
143        &mut self,
144        conf_map: HashMap<String, HashMap<String, Option<String>>>,
145    ) -> Result<(), Box<dyn std::error::Error>> {
146        for (section_name, properties) in conf_map {
147            for (key, value) in properties {
148                let Some(value) = value else {
149                    continue;
150                };
151
152                let var_name = match section_name.as_str() {
153                    "global" => VarName::new_with_scope(Scope::Global, key.to_lowercase()),
154                    "script" => VarName::new_with_scope(Scope::Script, key.to_lowercase()),
155                    "env" => VarName::new_with_scope(Scope::Env, key.to_lowercase()),
156                    _ => {
157                        continue;
158                    }
159                };
160
161                // Try to parse the value as different types
162                let parsed_value = if let Ok(bool_val) = value.parse::<bool>() {
163                    Val::Bool(bool_val)
164                } else if let Ok(int_val) = value.parse::<i64>() {
165                    Val::Int(int_val)
166                } else if let Ok(float_val) = value.parse::<f64>() {
167                    Val::Float(float_val)
168                } else if value.is_empty() {
169                    Val::Null
170                } else {
171                    Val::String(value.clone().into())
172                };
173
174                // Insert the variable (overwrite if it exists and is not read-only)
175                if let Err(err) = self.set(&var_name, parsed_value.clone()) {
176                    log::error!("Failed to set variable {:?}: {}", var_name, err);
177                }
178            }
179        }
180        Ok(())
181    }
182
183    pub(crate) fn script_scope(&self) -> VariableMap {
184        self.script_scope.clone()
185    }
186
187    pub(crate) fn get_env(&self) -> VariableMap {
188        self.env.clone()
189    }
190
191    pub(crate) fn get_global(&self) -> VariableMap {
192        self.global_scope.clone()
193    }
194
195    pub(crate) fn add_script_function(&mut self, name: String, func: ScriptBlock) {
196        self.script_functions.insert(name, func);
197    }
198
199    pub(crate) fn add_global_function(&mut self, name: String, func: ScriptBlock) {
200        self.global_functions.insert(name, func);
201    }
202
203    pub(crate) fn clear_script_functions(&mut self) {
204        self.script_functions.clear();
205    }
206
207    /// Creates a new empty Variables container.
208    ///
209    /// # Arguments
210    ///
211    /// * initializes the container with PowerShell built-in variables like
212    ///   `$true`, `$false`, `$null`, and `$?`. If `false`,
213    ///
214    /// # Returns
215    ///
216    /// A new `Variables` instance.
217    ///
218    /// # Examples
219    ///
220    /// ```rust
221    /// use ps_parser::Variables;
222    ///
223    /// // Create with built-in variables
224    /// let vars_with_builtins = Variables::new();
225    ///
226    /// // Create empty
227    /// let empty_vars = Variables::new();
228    /// ```
229    pub fn new() -> Variables {
230        Default::default()
231    }
232
233    /// Creates a new Variables container with forced evaluation enabled.
234    ///
235    /// This constructor creates a Variables instance that will return
236    /// `Val::Null` for undefined variables instead of returning `None`.
237    /// This is useful for PowerShell script evaluation where undefined
238    /// variables should be treated as `$null` rather than causing errors.
239    ///
240    /// # Returns
241    ///
242    /// A new `Variables` instance with forced evaluation enabled and built-in
243    /// variables initialized.
244    ///
245    /// # Examples
246    ///
247    /// ```rust
248    /// use ps_parser::{Variables, PowerShellSession};
249    ///
250    /// // Create with forced evaluation
251    /// let vars = Variables::force_eval();
252    /// let mut session = PowerShellSession::new().with_variables(vars);
253    ///
254    /// // Undefined variables will evaluate to $null instead of causing errors
255    /// let result = session.safe_eval("$undefined_variable").unwrap();
256    /// assert_eq!(result, "");  // $null displays as empty string
257    /// ```
258    ///
259    /// # Behavior Difference
260    ///
261    /// - `Variables::new()`: Returns `None` for undefined variables
262    /// - `Variables::force_eval()`: Returns `Val::Null` for undefined variables
263    ///
264    /// This is particularly useful when parsing PowerShell scripts that may
265    /// reference variables that haven't been explicitly defined, allowing
266    /// the script to continue execution rather than failing.
267    pub fn force_eval() -> Self {
268        Self {
269            force_var_eval: true,
270            ..Default::default()
271        }
272    }
273
274    // not exported in this version
275    #[allow(dead_code)]
276    pub(crate) fn values_persist(mut self) -> Self {
277        self.values_persist = true;
278        self
279    }
280
281    /// Loads all environment variables into a Variables container.
282    ///
283    /// This method reads all environment variables from the system and stores
284    /// them in the `env` scope, making them accessible as
285    /// `$env:VARIABLE_NAME` in PowerShell scripts.
286    ///
287    /// # Returns
288    ///
289    /// A new `Variables` instance containing all environment variables.
290    ///
291    /// # Examples
292    ///
293    /// ```rust
294    /// use ps_parser::{Variables, PowerShellSession};
295    ///
296    /// let env_vars = Variables::env();
297    /// let mut session = PowerShellSession::new().with_variables(env_vars);
298    ///
299    /// // Access environment variables
300    /// let path = session.safe_eval("$env:PATH").unwrap();
301    /// let username = session.safe_eval("$env:USERNAME").unwrap();
302    /// ```
303    pub fn env() -> Variables {
304        let mut vars = Variables::new();
305
306        // Load all environment variables
307        for (key, value) in std::env::vars() {
308            // Store environment variables with Env scope so they can be accessed via
309            // $env:variable_name
310            vars.env
311                .insert(key.to_lowercase(), Val::String(value.into()));
312        }
313        vars
314    }
315
316    /// Loads variables from an INI configuration file.
317    ///
318    /// This method parses an INI file and loads its key-value pairs as
319    /// PowerShell variables. Variables are organized by INI sections, with
320    /// the `[global]` section creating global variables and other sections
321    /// creating scoped variables.
322    ///
323    /// # Arguments
324    ///
325    /// * `path` - A reference to the path of the INI file to load.
326    ///
327    /// # Returns
328    ///
329    /// * `Result<Variables, VariableError>` - A Variables instance with the
330    ///   loaded data, or an error if the file cannot be read or parsed.
331    ///
332    /// # Examples
333    ///
334    /// ```rust
335    /// use ps_parser::{Variables, PowerShellSession};
336    /// use std::path::Path;
337    ///
338    /// // Load from INI file
339    /// let variables = Variables::from_ini_string("[global]\nname = John Doe\n[local]\nlocal_var = \"local_value\"").unwrap();
340    /// let mut session = PowerShellSession::new().with_variables(variables);
341    ///
342    /// // Access loaded variables
343    /// let name = session.safe_eval("$global:name").unwrap();
344    /// let local_var = session.safe_eval("$local:local_var").unwrap();
345    /// ```
346    ///
347    /// # INI Format
348    ///
349    /// ```ini
350    /// # Global variables (accessible as $global:key)
351    /// [global]
352    /// name = John Doe
353    /// version = 1.0
354    ///
355    /// # Local scope variables (accessible as $local:key)
356    /// [local]
357    /// temp_dir = /tmp
358    /// debug = true
359    /// ```
360    pub fn from_ini_string(ini_string: &str) -> Result<Self, Box<dyn std::error::Error>> {
361        let mut variables = Self::new();
362        variables.load_from_string(ini_string)?;
363        Ok(variables)
364    }
365
366    /// Create a new Variables instance with variables loaded from an INI file
367    pub fn from_ini_file(path: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
368        let mut variables = Self::new();
369        variables.load_from_file(path)?;
370        Ok(variables)
371    }
372
373    fn top_scope(&self, scope: Option<&TopScope>) -> &VariableMap {
374        let scope = if let Some(s) = scope {
375            s
376        } else {
377            &self.top_scope
378        };
379        match scope {
380            TopScope::Session => &self.global_scope,
381            TopScope::Script => &self.script_scope,
382        }
383    }
384
385    fn mut_top_scope(&mut self, scope: Option<&TopScope>) -> &mut VariableMap {
386        let scope = if let Some(s) = scope {
387            s
388        } else {
389            &self.top_scope
390        };
391
392        match scope {
393            TopScope::Session => &mut self.global_scope,
394            TopScope::Script => &mut self.script_scope,
395        }
396    }
397
398    fn const_map_from_scope(&self, scope: &Scope) -> &VariableMap {
399        match scope {
400            Scope::Global => &self.global_scope,
401            Scope::Script => &self.script_scope,
402            Scope::Env => &self.env,
403            Scope::Local => match &self.state {
404                State::TopScope(scope) => self.top_scope(Some(scope)),
405                State::Stack(depth) => {
406                    if *depth < self.local_scopes_stack.len() as u32 {
407                        &self.local_scopes_stack[*depth as usize]
408                    } else {
409                        &self.script_scope
410                    }
411                }
412            },
413            Scope::Special => {
414                &self.global_scope //todo!(),
415            }
416        }
417    }
418
419    fn local_scope(&mut self) -> &mut VariableMap {
420        match &mut self.state {
421            State::TopScope(scope) => {
422                let scope = scope.clone();
423                self.mut_top_scope(Some(&scope))
424            }
425            State::Stack(depth) => {
426                if *depth < self.local_scopes_stack.len() as u32 {
427                    &mut self.local_scopes_stack[*depth as usize]
428                } else {
429                    &mut self.global_scope
430                }
431            }
432        }
433    }
434    fn map_from_scope(&mut self, scope: Option<&Scope>) -> &mut VariableMap {
435        match scope {
436            Some(Scope::Global) => &mut self.global_scope,
437            Some(Scope::Script) => &mut self.script_scope,
438            Some(Scope::Env) => &mut self.env,
439            Some(Scope::Local) => self.local_scope(),
440            Some(Scope::Special) => {
441                &mut self.global_scope //todo!(),
442            }
443            None => self.mut_top_scope(None),
444        }
445    }
446
447    /// Sets the value of a variable in the specified scope.
448    ///
449    /// # Arguments
450    ///
451    /// * `var_name` - The variable name and scope information.
452    /// * `val` - The value to assign to the variable.
453    ///
454    /// # Returns
455    ///
456    /// * `Result<(), VariableError>` - Success or an error if the variable is
457    ///   read-only.
458    pub(crate) fn set(&mut self, var_name: &VarName, val: Val) -> VariableResult<()> {
459        let var = self.find_mut_variable_in_scopes(var_name)?;
460
461        if let Some(variable) = var {
462            *variable = val;
463        } else {
464            let map = self.map_from_scope(var_name.scope.as_ref());
465            map.insert(var_name.name.to_ascii_lowercase(), val);
466        }
467
468        Ok(())
469    }
470
471    pub(crate) fn set_local(&mut self, name: &str, val: Val) -> VariableResult<()> {
472        let var_name = VarName::new_with_scope(Scope::Local, name.to_ascii_lowercase());
473        self.set(&var_name, val)
474    }
475
476    fn find_mut_variable_in_scopes(
477        &mut self,
478        var_name: &VarName,
479    ) -> VariableResult<Option<&mut Val>> {
480        let name = var_name.name.to_ascii_lowercase();
481        let name_str = name.as_str();
482
483        if let Some(scope) = &var_name.scope
484            && self.const_map_from_scope(scope).contains_key(name_str)
485        {
486            Ok(self.map_from_scope(Some(scope)).get_mut(name_str))
487        } else {
488            if Self::PREDEFINED_VARIABLES.contains_key(name_str) {
489                return Err(VariableError::ReadOnly(name.clone()));
490            }
491
492            // No scope specified, check local scopes first, then globals
493            for local_scope in self.local_scopes_stack.iter_mut().rev() {
494                if local_scope.contains_key(name_str) {
495                    return Ok(local_scope.get_mut(name_str));
496                }
497            }
498
499            if self.script_scope.contains_key(name_str) {
500                return Ok(self.script_scope.get_mut(name_str));
501            }
502
503            if self.global_scope.contains_key(name_str) {
504                return Ok(self.global_scope.get_mut(name_str));
505            }
506
507            Ok(None)
508        }
509    }
510
511    /// Retrieves the value of a variable from the appropriate scope.
512    ///
513    /// # Arguments
514    ///
515    /// * `var_name` - The variable name and scope information.
516    ///
517    /// # Returns
518    ///
519    /// * `VariableResult<Val>` - The variable's value, or an error if not
520    ///   found.
521    pub(crate) fn get(
522        &self,
523        var_name: &VarName,
524        types_map: &HashMap<String, Box<dyn RuntimeTypeTrait>>,
525    ) -> Option<Val> {
526        let var = self.find_variable_in_scopes(var_name);
527
528        if self.force_var_eval && var.is_none() {
529            if let Some(rt) = types_map.get(var_name.name.as_str()) {
530                Some(Val::RuntimeType(rt.clone_rt()))
531            } else {
532                Some(Val::Null)
533            }
534        } else {
535            var.cloned()
536        }
537    }
538
539    pub(crate) fn get_without_types(&self, var_name: &VarName) -> Option<Val> {
540        let var = self.find_variable_in_scopes(var_name);
541
542        if self.force_var_eval && var.is_none() {
543            Some(Val::Null)
544        } else {
545            var.cloned()
546        }
547    }
548
549    fn find_variable_in_scopes(&self, var_name: &VarName) -> Option<&Val> {
550        let name = var_name.name.to_ascii_lowercase();
551        let name_str = name.as_str();
552
553        if let Some(scope) = &var_name.scope {
554            let map = self.const_map_from_scope(scope);
555            let x = map.get(name_str);
556            if x.is_some() {
557                return x;
558            }
559        }
560        if Self::PREDEFINED_VARIABLES.contains_key(name_str) {
561            return Self::PREDEFINED_VARIABLES.get(name_str);
562        }
563
564        // No scope specified, check local scopes first, then globals
565        for local_scope in self.local_scopes_stack.iter().rev() {
566            if local_scope.contains_key(name_str) {
567                return local_scope.get(name_str);
568            }
569        }
570
571        if self.script_scope.contains_key(name_str) {
572            return self.script_scope.get(name_str);
573        }
574
575        if self.global_scope.contains_key(name_str) {
576            return self.global_scope.get(name_str);
577        }
578
579        None
580    }
581
582    pub(crate) fn push_scope_session(&mut self) {
583        let current_map = self.local_scope();
584        let new_map = current_map.clone();
585
586        self.local_scopes_stack.push(new_map);
587        self.state = State::Stack(self.local_scopes_stack.len() as u32 - 1);
588    }
589
590    pub(crate) fn pop_scope_session(&mut self) {
591        match self.local_scopes_stack.len() {
592            0 => {} /* unreachable */
593            1 => {
594                self.local_scopes_stack.pop();
595                self.state = State::TopScope(self.top_scope.clone());
596            }
597            _ => {
598                self.local_scopes_stack.pop();
599                self.state = State::Stack(self.local_scopes_stack.len() as u32 - 1);
600            }
601        }
602    }
603}
604
605#[cfg(test)]
606mod tests {
607    use super::Variables;
608    use crate::{PowerShellSession, PsValue};
609
610    #[test]
611    fn test_builtin_variables() {
612        let mut p = PowerShellSession::new();
613        assert_eq!(p.safe_eval(r#" $true "#).unwrap().as_str(), "True");
614        assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
615        assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
616    }
617
618    #[test]
619    fn test_env_variables() {
620        let v = Variables::env();
621        let mut p = PowerShellSession::new().with_variables(v);
622        assert_eq!(
623            p.safe_eval(r#" $env:path "#).unwrap().as_str(),
624            std::env::var("PATH").unwrap()
625        );
626        assert_eq!(
627            p.safe_eval(r#" $env:username "#).unwrap().as_str(),
628            std::env::var("USERNAME").unwrap()
629        );
630        assert_eq!(
631            p.safe_eval(r#" $env:tEMp "#).unwrap().as_str(),
632            std::env::var("TEMP").unwrap()
633        );
634        assert_eq!(
635            p.safe_eval(r#" $env:tMp "#).unwrap().as_str(),
636            std::env::var("TMP").unwrap()
637        );
638        assert_eq!(
639            p.safe_eval(r#" $env:cOmputername "#).unwrap().as_str(),
640            std::env::var("COMPUTERNAME").unwrap()
641        );
642        assert_eq!(
643            p.safe_eval(r#" $env:programfiles "#).unwrap().as_str(),
644            std::env::var("PROGRAMFILES").unwrap()
645        );
646        assert_eq!(
647            p.safe_eval(r#" $env:temp "#).unwrap().as_str(),
648            std::env::var("TEMP").unwrap()
649        );
650        assert_eq!(
651            p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
652                .unwrap()
653                .as_str(),
654            std::env::var("ProgramFiles(x86)").unwrap()
655        );
656        let env_variables = p.env_variables();
657        assert_eq!(
658            env_variables.get("path").unwrap().to_string(),
659            std::env::var("PATH").unwrap()
660        );
661        assert_eq!(
662            env_variables.get("tmp").unwrap().to_string(),
663            std::env::var("TMP").unwrap()
664        );
665        assert_eq!(
666            env_variables.get("temp").unwrap().to_string(),
667            std::env::var("TMP").unwrap()
668        );
669        assert_eq!(
670            env_variables.get("appdata").unwrap().to_string(),
671            std::env::var("APPDATA").unwrap()
672        );
673        assert_eq!(
674            env_variables.get("username").unwrap().to_string(),
675            std::env::var("USERNAME").unwrap()
676        );
677        assert_eq!(
678            env_variables.get("programfiles").unwrap().to_string(),
679            std::env::var("PROGRAMFILES").unwrap()
680        );
681        assert_eq!(
682            env_variables.get("programfiles(x86)").unwrap().to_string(),
683            std::env::var("PROGRAMFILES(x86)").unwrap()
684        );
685    }
686
687    #[test]
688    fn test_global_variables() {
689        let v = Variables::env();
690        let mut p = PowerShellSession::new().with_variables(v);
691
692        p.parse_script(r#" $global:var_int = 5 "#).unwrap();
693        p.parse_script(r#" $global:var_string = "global";$script:var_string = "script";$local:var_string = "local" "#).unwrap();
694
695        assert_eq!(
696            p.parse_script(r#" $var_int "#).unwrap().result(),
697            PsValue::Int(5)
698        );
699        assert_eq!(
700            p.parse_script(r#" $var_string "#).unwrap().result(),
701            PsValue::String("local".into())
702        );
703
704        let global_variables = p.session_variables();
705        assert_eq!(global_variables.get("var_int").unwrap(), &PsValue::Int(5));
706        assert_eq!(
707            global_variables.get("var_string").unwrap(),
708            &PsValue::String("local".into())
709        );
710    }
711
712    #[test]
713    fn test_script_variables() {
714        let v = Variables::env();
715        let mut p = PowerShellSession::new().with_variables(v);
716
717        let script_res = p
718            .parse_script(r#" $script:var_int = 5;$var_string = "assdfa" "#)
719            .unwrap();
720        let script_variables = script_res.script_variables();
721        assert_eq!(script_variables.get("var_int"), Some(&PsValue::Int(5)));
722        assert_eq!(
723            script_variables.get("var_string"),
724            Some(&PsValue::String("assdfa".into()))
725        );
726    }
727
728    #[test]
729    fn test_env_special_cases() {
730        let v = Variables::env();
731        let mut p = PowerShellSession::new().with_variables(v);
732        p.safe_eval(r#" $global:program = $env:programfiles + "\program" "#)
733            .unwrap();
734        assert_eq!(
735            p.safe_eval(r#" $global:program "#).unwrap().as_str(),
736            format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
737        );
738        assert_eq!(
739            p.safe_eval(r#" $program "#).unwrap().as_str(),
740            format!("{}\\program", std::env::var("PROGRAMFILES").unwrap())
741        );
742
743        assert_eq!(
744            p.safe_eval(r#" ${Env:ProgramFiles(x86):adsf} = 5;${Env:ProgramFiles(x86):adsf} "#)
745                .unwrap()
746                .as_str(),
747            5.to_string()
748        );
749        assert_eq!(
750            p.safe_eval(r#" ${Env:ProgramFiles(x86)} "#)
751                .unwrap()
752                .as_str(),
753            std::env::var("ProgramFiles(x86)").unwrap()
754        );
755    }
756
757    #[test]
758    fn special_last_error() {
759        let input = r#"3+"01234 ?";$a=5;$a;$?"#;
760
761        let mut p = PowerShellSession::new();
762        assert_eq!(p.safe_eval(input).unwrap().as_str(), "True");
763
764        let input = r#"3+"01234 ?";$?"#;
765        assert_eq!(p.safe_eval(input).unwrap().as_str(), "False");
766    }
767
768    #[test]
769    fn test_from_ini() {
770        let input = r#"[global]
771name = radek
772age = 30
773is_admin = true
774height = 5.9
775empty_value =
776
777[script]
778local_var = "local_value"
779        "#;
780        let mut variables = Variables::new().values_persist();
781        variables.load_from_string(input).unwrap();
782        let mut p = PowerShellSession::new().with_variables(variables);
783
784        assert_eq!(
785            p.parse_script(r#" $global:name "#).unwrap().result(),
786            PsValue::String("radek".into())
787        );
788        assert_eq!(
789            p.parse_script(r#" $global:age "#).unwrap().result(),
790            PsValue::Int(30)
791        );
792        assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
793        assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
794        assert_eq!(
795            p.safe_eval(r#" $script:local_var "#).unwrap().as_str(),
796            "\"local_value\""
797        );
798        assert_eq!(
799            p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
800            "\"local_value\""
801        );
802    }
803
804    #[test]
805    fn test_from_ini_string() {
806        let input = r#"[global]
807name = radek
808age = 30
809is_admin = true
810height = 5.9
811empty_value =
812
813[script]
814local_var = "local_value"
815        "#;
816
817        let variables = Variables::from_ini_string(input).unwrap().values_persist();
818        let mut p = PowerShellSession::new().with_variables(variables);
819        assert_eq!(
820            p.parse_script(r#" $global:name "#).unwrap().result(),
821            PsValue::String("radek".into())
822        );
823        assert_eq!(
824            p.parse_script(r#" $global:age "#).unwrap().result(),
825            PsValue::Int(30)
826        );
827        assert_eq!(p.safe_eval(r#" $false "#).unwrap().as_str(), "False");
828        assert_eq!(p.safe_eval(r#" $null "#).unwrap().as_str(), "");
829        assert_eq!(
830            p.safe_eval(r#" $script:local_var "#).unwrap().as_str(),
831            "\"local_value\""
832        );
833        assert_eq!(
834            p.safe_eval(r#" $local_var "#).unwrap().as_str(),
835            "\"local_value\""
836        );
837
838        assert_eq!(
839            p.safe_eval(r#" $local:local_var "#).unwrap().as_str(),
840            "\"local_value\""
841        );
842    }
843}