Skip to main content

fiddler_script/
lib.rs

1//! # FiddlerScript
2//!
3//! A minimal C-style scripting language with a Rust-based interpreter.
4//!
5//! ## Features
6//!
7//! - Variables with `let` declarations (integers and strings)
8//! - Control flow with `if-else` statements and `for` loops
9//! - User-defined functions with `fn` syntax
10//! - Built-in functions (starting with `print()`)
11//! - Single-line comments with `//`
12//!
13//! ## Example
14//!
15//! ```
16//! use fiddler_script::Interpreter;
17//!
18//! let source = r#"
19//!     let x = 10;
20//!     let y = 20;
21//!     print(x + y);
22//! "#;
23//!
24//! let mut interpreter = Interpreter::new();
25//! interpreter.run(source).expect("Failed to run script");
26//! ```
27//!
28//! ## Custom Built-in Functions
29//!
30//! You can extend the interpreter with custom built-in functions:
31//!
32//! ```
33//! use fiddler_script::{Interpreter, Value, RuntimeError};
34//! use std::collections::HashMap;
35//!
36//! let mut builtins: HashMap<String, fn(Vec<Value>) -> Result<Value, RuntimeError>> = HashMap::new();
37//! builtins.insert("double".to_string(), |args| {
38//!     if let Some(Value::Integer(n)) = args.first() {
39//!         Ok(Value::Integer(n * 2))
40//!     } else {
41//!         Err(RuntimeError::invalid_argument("Expected integer"))
42//!     }
43//! });
44//!
45//! let mut interpreter = Interpreter::with_builtins(builtins);
46//! ```
47
48pub mod ast;
49pub mod builtins;
50pub mod error;
51pub mod interpreter;
52pub mod lexer;
53pub mod parser;
54
55use indexmap::IndexMap;
56
57// Re-export main types for convenience
58pub use ast::{Expression, Program, Statement};
59pub use builtins::BuiltinFn;
60pub use error::{FiddlerError, LexError, ParseError, RuntimeError};
61pub use interpreter::Interpreter;
62pub use lexer::{Lexer, Token};
63pub use parser::Parser;
64
65/// A runtime value in FiddlerScript.
66#[derive(Debug, Clone)]
67pub enum Value {
68    /// Integer value
69    Integer(i64),
70    /// Float value (64-bit floating point)
71    Float(f64),
72    /// String value
73    String(String),
74    /// Boolean value
75    Boolean(bool),
76    /// Bytes value (raw binary data)
77    Bytes(Vec<u8>),
78    /// Array value (list of values)
79    Array(Vec<Value>),
80    /// Dictionary value (key-value pairs with insertion order preserved)
81    Dictionary(IndexMap<String, Value>),
82    /// Represents no value (e.g., from a function with no return)
83    Null,
84}
85
86// Custom PartialEq to handle cross-type numeric comparisons and NaN
87impl PartialEq for Value {
88    fn eq(&self, other: &Self) -> bool {
89        match (self, other) {
90            (Value::Integer(a), Value::Integer(b)) => a == b,
91            (Value::Float(a), Value::Float(b)) => a == b, // NaN != NaN per IEEE 754
92            (Value::Integer(a), Value::Float(b)) => (*a as f64) == *b,
93            (Value::Float(a), Value::Integer(b)) => *a == (*b as f64),
94            (Value::String(a), Value::String(b)) => a == b,
95            (Value::Boolean(a), Value::Boolean(b)) => a == b,
96            (Value::Bytes(a), Value::Bytes(b)) => a == b,
97            (Value::Array(a), Value::Array(b)) => a == b,
98            (Value::Dictionary(a), Value::Dictionary(b)) => a == b,
99            (Value::Null, Value::Null) => true,
100            _ => false,
101        }
102    }
103}
104
105impl std::fmt::Display for Value {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        match self {
108            Value::Integer(n) => write!(f, "{}", n),
109            Value::Float(fl) => {
110                if fl.is_nan() {
111                    write!(f, "NaN")
112                } else if fl.is_infinite() {
113                    if fl.is_sign_positive() {
114                        write!(f, "Infinity")
115                    } else {
116                        write!(f, "-Infinity")
117                    }
118                } else if fl.fract() == 0.0 && fl.abs() < 1e15 {
119                    // Integer-valued floats: show as 1.0, 2.0, etc.
120                    write!(f, "{:.1}", fl)
121                } else {
122                    // General floats: format with precision, strip trailing zeros
123                    let s = format!("{:.10}", fl);
124                    let s = s.trim_end_matches('0').trim_end_matches('.');
125                    write!(f, "{}", s)
126                }
127            }
128            Value::String(s) => write!(f, "{}", s),
129            Value::Boolean(b) => write!(f, "{}", b),
130            Value::Bytes(bytes) => write!(f, "<bytes: {} bytes>", bytes.len()),
131            Value::Array(arr) => {
132                write!(f, "[")?;
133                let mut first = true;
134                for v in arr {
135                    if !first {
136                        write!(f, ", ")?;
137                    }
138                    write!(f, "{}", v)?;
139                    first = false;
140                }
141                write!(f, "]")
142            }
143            Value::Dictionary(dict) => {
144                write!(f, "{{")?;
145                let mut first = true;
146                for (k, v) in dict {
147                    if !first {
148                        write!(f, ", ")?;
149                    }
150                    write!(f, "{}: {}", k, v)?;
151                    first = false;
152                }
153                write!(f, "}}")
154            }
155            Value::Null => write!(f, "null"),
156        }
157    }
158}
159
160impl Value {
161    /// Convert the value to bytes representation.
162    pub fn to_bytes(&self) -> Vec<u8> {
163        match self {
164            Value::Integer(n) => n.to_string().into_bytes(),
165            Value::Float(f) => f.to_string().into_bytes(),
166            Value::String(s) => s.as_bytes().to_vec(),
167            Value::Boolean(b) => b.to_string().into_bytes(),
168            Value::Bytes(bytes) => bytes.clone(),
169            Value::Array(_) | Value::Dictionary(_) => self.to_string().into_bytes(),
170            Value::Null => Vec::new(),
171        }
172    }
173
174    /// Check if value is a number (integer or float).
175    pub fn is_number(&self) -> bool {
176        matches!(self, Value::Integer(_) | Value::Float(_))
177    }
178
179    /// Try to get as f64, converting integers to floats.
180    pub fn as_f64(&self) -> Option<f64> {
181        match self {
182            Value::Integer(n) => Some(*n as f64),
183            Value::Float(f) => Some(*f),
184            _ => None,
185        }
186    }
187
188    /// Try to get as i64, truncating floats.
189    pub fn as_i64(&self) -> Option<i64> {
190        match self {
191            Value::Integer(n) => Some(*n),
192            Value::Float(f) => Some(*f as i64),
193            _ => None,
194        }
195    }
196
197    /// Try to create a Value from bytes, interpreting as UTF-8 string.
198    pub fn from_bytes(bytes: Vec<u8>) -> Self {
199        Value::Bytes(bytes)
200    }
201
202    /// Convert to string, handling both String and Bytes variants.
203    ///
204    /// Returns an error if the value is not a String or Bytes type.
205    /// For Bytes, invalid UTF-8 sequences are replaced with the Unicode
206    /// replacement character.
207    pub fn as_string_lossy(&self) -> Result<String, RuntimeError> {
208        match self {
209            Value::String(s) => Ok(s.clone()),
210            Value::Bytes(b) => Ok(String::from_utf8_lossy(b).into_owned()),
211            _ => Err(RuntimeError::invalid_argument(
212                "Expected string or bytes value",
213            )),
214        }
215    }
216
217    /// Check if the value is an array.
218    pub fn is_array(&self) -> bool {
219        matches!(self, Value::Array(_))
220    }
221
222    /// Check if the value is a dictionary.
223    pub fn is_dictionary(&self) -> bool {
224        matches!(self, Value::Dictionary(_))
225    }
226
227    /// Get as array if this value is an array.
228    pub fn as_array(&self) -> Option<&Vec<Value>> {
229        match self {
230            Value::Array(arr) => Some(arr),
231            _ => None,
232        }
233    }
234
235    /// Get as dictionary if this value is a dictionary.
236    pub fn as_dictionary(&self) -> Option<&IndexMap<String, Value>> {
237        match self {
238            Value::Dictionary(dict) => Some(dict),
239            _ => None,
240        }
241    }
242}