Skip to main content

crypt_configs/
lib.rs

1//! Crypt Configuration
2//!
3//! Created 7/25/2026 - Nyx
4//!
5//! A modern readable file format.
6//!
7//! Crypt files are similar to json with additional support for enums
8//! special tags, identifiers, and includes. Crypt is essentially a superset of json
9//! meaning any json file can parsed as if it was a crypt file.
10//!
11//! Using the beauty of Rust crypt files can be parsed directly into rust
12//! native structs and enums using the [Cryptic] derivation. To parse a crypt
13//! file a [CryptParserServer] is used which automatically caches opened files and
14//! reuses them when requested.
15
16pub use crypt_macro::Cryptic;
17
18use std::{
19    cell::LazyCell,
20    collections::HashMap,
21    ffi::OsString,
22    iter::Peekable,
23    path::{Path, PathBuf},
24    vec::IntoIter
25};
26
27use indexmap::IndexMap;
28use oaken::{
29    Lexer, Registry,
30    TokenPosition, TracedToken,
31    TracedTokenStream,
32    util::{
33        numeric_state::{DecimalState, numeric_entry_rule},
34        string_state::StringState
35    }
36};
37
38////////////
39// TOKENS //
40////////////
41
42#[derive(Debug, Clone)]
43/// Tokens collected by the crypt parser.
44enum Token {
45    Identifier(String),
46    String(String),
47    Integer(i64),
48    Float(f64),
49    Boolean(bool), // true | false
50    Null, // null
51
52    Import, // !import
53
54    Assign, // = or :
55    SingleTag, // #
56    TagOpen, // (
57    TagClose, // )
58    BodyOpen, // {
59    BodyClose, // }
60    ListOpen, // [
61    ListClose, // ]
62    Break, // ,
63}
64
65impl std::fmt::Display for Token {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        match self {
68            Self::Identifier(ident) => write!(f, "'{}'", ident),
69            Self::String(str) => write!(f, "\"{}\"", str),
70            Self::Integer(int) => write!(f, "{}", int),
71            Self::Float(fl) => write!(f, "{}", fl),
72            Self::Boolean(bool) => write!(f, "{}", bool),
73            Self::Null => write!(f, "null"),
74            Self::Import => write!(f, "!import"),
75            Self::Assign => write!(f, ": | ="),
76            Self::SingleTag => write!(f, "#"),
77            Self::TagOpen => write!(f, "("),
78            Self::TagClose => write!(f, ")"),
79            Self::ListOpen => write!(f, "["),
80            Self::ListClose => write!(f, "]"),
81            Self::BodyOpen => write!(f, "{{"),
82            Self::BodyClose => write!(f, "}}"),
83            Self::Break => write!(f, ","),
84        }
85    }
86}
87
88impl Token {
89    fn get_binding_power(&self) -> isize {
90        match self {
91            Self::Break => 1,
92            Self::Assign => 2,
93
94            _ => 0,
95        }
96    }
97}
98
99impl From<String> for Token {
100    fn from(value: String) -> Self {
101        Self::String(value)
102    }
103}
104
105macro_rules! from_int {
106    ($($ty:ty)+) => {
107        $(
108            impl From<$ty> for Token {
109                fn from(value: $ty) -> Self {
110                    Self::Integer(value as _)
111                }
112            }
113        )+
114    };
115}
116from_int!(i8 u8 i16 u16 i32 u32 i64 u64);
117
118impl From<f32> for Token {
119    fn from(value: f32) -> Self {
120        Self::Float(value as _)
121    }
122}
123impl From<f64> for Token {
124    fn from(value: f64) -> Self {
125        Self::Float(value)
126    }
127}
128
129const TOKENS: LazyCell<Vec<(&'static str, Token, bool)>> =
130LazyCell::new(||vec![
131    ("true", Token::Boolean(true), false),
132    ("false", Token::Boolean(false), false),
133    ("null", Token::Null, false),
134    ("!import", Token::Import, false),
135    ("#", Token::SingleTag, true),
136    ("(", Token::TagOpen, true),
137    (")", Token::TagClose, true),
138    ("{", Token::BodyOpen, true),
139    ("}", Token::BodyClose, true),
140    ("[", Token::ListOpen, true),
141    ("]", Token::ListClose, true),
142    (",", Token::Break, true),
143    ("=", Token::Assign, true),
144    (":", Token::Assign, true),
145]);
146
147fn identifier_fallback(str: &String) -> Token {
148    Token::Identifier(str.clone())
149}
150
151
152///////////////////
153// PARSER SERVER //
154///////////////////
155
156
157/// Sets up a parsing server for parsing crypt files.
158///
159/// # Example
160///
161/// ```no_run
162/// use crypt_config::{CryptParserServer, Cryptic};
163///
164/// #[derive(Cryptic)]
165/// struct MyStruct {
166///     str: String
167/// }
168///
169/// let mut server = CryptParserServer::new();
170/// let my_struct: MyStruct = server.open("path/to/my/file.crypt").expect("Failed to parse file");
171/// ```
172///
173/// The parser will automatically convert the resulting crypt file into the
174/// desired [Cryptic] type.
175pub struct CryptParserServer {
176    /// The [oaken] registry used to parse files
177    register: Registry<Token>,
178    /// Already opened files are cached and reused if requested.
179    opened_files: HashMap<OsString, TracedObject>,
180}
181
182impl CryptParserServer {
183    /// Creates a new parser server that can be reused while opening crypt files.
184    pub fn new() -> Self {
185        let mut reg= Registry::new(identifier_fallback);
186
187        for (kw, tk, br) in &*TOKENS {
188            reg = reg.new_keyword(kw, tk.clone(), *br).unwrap();
189        }
190
191        Self {
192            register: reg
193                .new_special_state('"', StringState::new('"'))
194                .add_new_entry_rule(numeric_entry_rule, DecimalState::default()),
195            opened_files: HashMap::new(),
196        }
197    }
198
199    /// Opens a crypt file from a path and automatically converts it to
200    /// the desired [Cryptic] type.
201    pub fn open<P, T>(&mut self, path: P) -> Result<T, CryptError>
202    where
203        P: AsRef<Path>,
204        T: Cryptic,
205    {
206        let path_str = path.as_ref().canonicalize()?.as_os_str().to_os_string();
207
208        if let Some(built) = self.opened_files.get(&path_str) {
209            return T::cryptic(built.clone())
210        }
211
212        let tokens = self.tokenize(path)?;
213        let mut iter = tokens.into_iter().peekable();
214        let ast = parse_body(&mut iter)?;
215        let object = resolve(ast, self)?;
216
217        self.opened_files.insert(path_str, object.clone());
218
219        // Nuance for parsing json files that have the main body
220        if let CryptObject::ObjectList(body) = &object.object
221            && body.len() == 1 && matches!(&body.iter().next().unwrap().object, &CryptObject::ObjectBody(_))
222        {
223            let CryptObject::ObjectList(body) = object.object else { unreachable!() };
224            let object = body.into_iter().next().unwrap();
225            T::cryptic(object)
226        } else {
227            T::cryptic(object)
228        }
229    }
230
231    /// Opens a tokenizes a crypt file.
232    fn tokenize<P>(&self, path: P) -> std::io::Result<TracedTokenStream<Token>>
233    where
234        P: AsRef<Path>,
235    {
236        let mut lexer = Lexer::from(&self.register);
237        lexer.traced();
238        lexer.lex_file(path, None)?;
239        Ok(lexer.finish_as_trace())
240    }
241}
242
243
244/////////////
245// PARSING //
246/////////////
247
248#[derive(Debug)]
249/// A parsed node containing information regarding the source of the node.
250/// This information is used in the case of an error.
251struct TracedNode {
252    position: TokenPosition,
253    node: Node,
254}
255
256#[derive(Debug)]
257/// A node parsed from a crypt file.
258enum Node {
259    Identifier(String),
260    String(String),
261    Integer(i64),
262    Float(f64),
263    Boolean(bool),
264    Null,
265
266    Import(String),
267    Assignment(String, Box<TracedNode>),
268    Tag(Vec<TracedNode>, Box<TracedNode>),
269    Body(Vec<TracedNode>),
270    List(Vec<TracedNode>),
271}
272
273type TokenIterator = Peekable<IntoIter<TracedToken<Token>>>;
274
275/// Parses a stream of tokens into a tree of nodes representing the crypt file.
276fn parse_body(iter: &mut TokenIterator) -> Result<TracedNode, CryptError> {
277    let mut items = Vec::new();
278    while let Some(_) = iter.peek() {
279        let item = parse(iter, 1)?;
280        items.push(item);
281
282        if let Some(t) = iter.peek() && matches!(t.get_type(), Token::Break) {
283            iter.next();
284        } else {
285            break;
286        }
287    }
288    if let Some(t) = iter.next() {
289        return Err(CryptError(Some(t.get_position().clone()), CryptErrorType::UnexpectedToken(t.into_type())))
290    }
291
292    Ok(TracedNode { position: TokenPosition { line: 0, column_start: 0, column_end: 0, path: None }, node: Node::List(items) })
293}
294
295fn parse(iter: &mut TokenIterator, current_binding_power: isize, ) -> Result<TracedNode, CryptError> {
296    let Some(current) = iter.next() else {
297        return Err(CryptError(None, CryptErrorType::UnexpectedEOF))
298    };
299
300    let mut left = parse_nud(iter, current)?;
301
302    while let Some(t) = iter.peek() && t.get_type().get_binding_power() > current_binding_power {
303        let current = iter.next().unwrap();
304        left = parse_led(iter, left, current)?;
305    }
306
307    Ok(left)
308}
309
310fn parse_nud(iter: &mut TokenIterator, current: TracedToken<Token>) -> Result<TracedNode, CryptError> {
311    let pos = current.get_position().clone();
312    let token = current.into_type();
313    let node = match token.clone() {
314        Token::Identifier(ident) => Node::Identifier(ident),
315        Token::String(str) => Node::String(str),
316        Token::Integer(int) => Node::Integer(int),
317        Token::Float(fl) => Node::Float(fl),
318        Token::Boolean(bool) => Node::Boolean(bool),
319        Token::Null => Node::Null,
320
321        Token::Import => {
322            let Some(t) = iter.next() else {
323                return Err(CryptError(None, CryptErrorType::UnexpectedEOF))
324            };
325
326            let path_token_pos = t.get_position().clone();
327            let t = t.into_type();
328            let Token::String(str) = t.clone() else {
329                return Err(CryptError(Some(path_token_pos), CryptErrorType::ExpectedOther("path string", t)))
330            };
331
332            Node::Import(str)
333        },
334
335        Token::SingleTag => {
336            if let Some(t) = iter.peek() && matches!(t.get_type(), Token::TagOpen) {
337                iter.next();
338                let mut tags = Vec::new();
339                while let Some(t) = iter.peek() && !matches!(t.get_type(), Token::TagClose) {
340                    let tag = parse(iter, 1)?;
341                    tags.push(tag);
342
343                    if let Some(t) = iter.peek() && matches!(t.get_type(), Token::Break) {
344                        iter.next();
345                    } else {
346                        break;
347                    }
348                }
349                if let Some(t) = iter.next() && !matches!(t.get_type(), Token::TagClose) {
350                    return Err(CryptError(Some(t.get_position().clone()), CryptErrorType::ExpectedOther("closing ')'", t.into_type())))
351                }
352                Node::Tag(tags, Box::new(parse(iter, 1)?))
353            } else {
354                let tag = parse(iter, 1)?;
355                Node::Tag(vec![tag], Box::new(parse(iter, 1)?))
356            }
357        }
358
359        Token::BodyOpen => {
360            let mut items = Vec::new();
361            while let Some(t) = iter.peek() && !matches!(t.get_type(), Token::BodyClose) {
362                let item = parse(iter, 1)?;
363                items.push(item);
364
365                if let Some(t) = iter.peek() && matches!(t.get_type(), Token::Break) {
366                    iter.next();
367                } else {
368                    break;
369                }
370            }
371            if let Some(t) = iter.next() && !matches!(t.get_type(), Token::BodyClose) {
372                return Err(CryptError(Some(t.get_position().clone()), CryptErrorType::ExpectedOther("closing '}'", t.into_type())))
373            }
374            Node::Body(items)
375        }
376
377        Token::ListOpen => {
378            let mut items = Vec::new();
379            while let Some(t) = iter.peek() && !matches!(t.get_type(), Token::ListClose) {
380                let item = parse(iter, 1)?;
381                items.push(item);
382
383                if let Some(t) = iter.peek() && matches!(t.get_type(), Token::Break) {
384                    iter.next();
385                } else {
386                    break;
387                }
388            }
389            if let Some(t) = iter.next() && !matches!(t.get_type(), Token::ListClose) {
390                return Err(CryptError(Some(t.get_position().clone()), CryptErrorType::ExpectedOther("closing ']'", t.into_type())))
391            }
392            Node::List(items)
393        }
394
395        _ => return Err(CryptError(Some(pos), CryptErrorType::UnexpectedToken(token)))
396    };
397
398    Ok(TracedNode { position: pos, node, })
399}
400
401fn parse_led(iter: &mut TokenIterator, left: TracedNode, current: TracedToken<Token>) -> Result<TracedNode, CryptError> {
402    let pos = current.get_position().clone();
403    let token = current.into_type();
404    let node = match token.clone() {
405        Token::Assign => {
406            let ident = match left.node {
407                Node::Identifier(ident) => ident,
408                Node::String(str) => str,
409                _ => return Err(CryptError(Some(left.position), CryptErrorType::ExpectedIdentifier))
410            };
411
412            let object = parse(iter, 1)?;
413            Node::Assignment(ident, Box::new(object))
414        }
415
416        _ => return Err(CryptError(Some(pos), CryptErrorType::UnexpectedToken(token)))
417    };
418
419    Ok(TracedNode { position: pos, node, })
420}
421
422
423
424
425fn resolve(node: TracedNode, server: &mut CryptParserServer) -> Result<TracedObject, CryptError> {
426    let pos = node.position;
427
428    let obj = match node.node {
429        Node::Identifier(ident) => CryptObject::FloatingIdentifier(ident),
430        Node::String(str) => CryptObject::ConstantString(str),
431        Node::Integer(int) => CryptObject::ConstantInteger(int),
432        Node::Float(fl) => CryptObject::ConstantFloat(fl),
433        Node::Boolean(bool) => CryptObject::ConstantBoolean(bool),
434        Node::Null => CryptObject::Null,
435        Node::Assignment(ident, item) => CryptObject::IdentifiedObject(ident, Box::new(resolve(*item, server)?)),
436        Node::Body(items) => {
437            let mut body = IndexMap::new();
438            for item in items {
439                let item = resolve(item, server)?;
440                match item.object {
441                    CryptObject::IdentifiedObject(ident, item) => {
442                        body.insert(ident, *item);
443                    }
444                    CryptObject::TaggedObject(tags, tagged_item) =>
445                        if let CryptObject::IdentifiedObject(ident, tagged_item) = tagged_item.object {
446                            body.insert(ident, TracedObject { position: item.position, object: CryptObject::TaggedObject(tags, tagged_item) });
447                        } else {
448                            return Err(CryptError(Some(item.position), CryptErrorType::ExpectedIdentifier))
449                        },
450                    _ => return Err(CryptError(Some(item.position), CryptErrorType::ExpectedIdentifier))
451                }
452            }
453            CryptObject::ObjectBody(body)
454        }
455        Node::List(items) => {
456            let mut resolved_items = Vec::new();
457            for item in items {
458                let item = resolve(item, server)?;
459                resolved_items.push(item);
460            }
461            CryptObject::ObjectList(resolved_items)
462        }
463        Node::Tag(tags, object) => {
464            let mut resolved_tags = Vec::new();
465            for tag in tags {
466                let tag = resolve(tag, server)?;
467                resolved_tags.push(tag);
468            }
469            CryptObject::TaggedObject(resolved_tags, Box::new(resolve(*object, server)?))
470        }
471        Node::Import(path) => {
472            let Some(current_path) = pos.path else {
473                return Err(CryptError(Some(pos), CryptErrorType::CannotImport))
474            };
475
476            let path_buf =
477                PathBuf::from(current_path.as_ref())
478                .canonicalize().unwrap(); // TODO propagate
479            let final_path = path_buf.parent().unwrap().join(path);
480
481            return server.open(final_path)
482        }
483    };
484
485    Ok(TracedObject { position: pos, object: obj })
486}
487
488
489
490
491/////////////
492// OBJECTS //
493/////////////
494
495#[derive(Debug, Clone)]
496/// An object parsed from a crypt file that also contains
497/// debug information in the case of an error.
498///
499/// This is the main interface type for building [Cryptic] objects.
500/// Traced objects can be easily cast to other types while also providing
501/// debug information. If you need to handle ambiguous types use [CrypticObject]
502/// which strips away the added debug information.
503pub struct TracedObject {
504    position: TokenPosition,
505    object: CryptObject,
506}
507
508#[derive(Debug, Clone)]
509/// An object taken from a crypt file.
510enum CryptObject {
511    ConstantString(String),
512    ConstantInteger(i64),
513    ConstantFloat(f64),
514    ConstantBoolean(bool),
515    Null,
516    FloatingIdentifier(String),
517    IdentifiedObject(String, Box<TracedObject>),
518
519    TaggedObject (Vec<TracedObject>, Box<TracedObject>),
520
521    ObjectBody(IndexMap<String, TracedObject>),
522    ObjectList(Vec<TracedObject>),
523}
524
525#[derive(Debug, Clone)]
526/// An ambiguous object parsed from a crypt file.
527///
528/// This should only be used if the object you want parsed is expected
529/// to be multiple different types and should be handled manually. In any other
530/// case simply using that type is preferred.
531pub enum CrypticObject {
532    ConstantString(String),
533    ConstantInteger(i64),
534    ConstantFloat(f64),
535    ConstantBoolean(bool),
536    Null,
537    FloatingIdentifier(String),
538    IdentifiedObject(String, Box<CrypticObject>),
539
540    TaggedObject (Vec<CrypticObject>, Box<CrypticObject>),
541
542    ObjectBody(HashMap<String, CrypticObject>),
543    ObjectList(Vec<CrypticObject>),
544}
545
546#[derive(Debug, Clone)]
547/// A custom tagged object parsed from a crypt file.
548pub struct Tagged<T, O> {
549    pub tag: T,
550    pub object: O
551}
552
553#[derive(Debug, Clone)]
554/// A custom tagged object parsed from a crypt file.
555pub struct HashTagged<T, O> {
556    pub tags: HashMap<String, T>,
557    pub object: O
558}
559
560#[derive(Debug, Clone)]
561/// A custom tagged object parsed from a crypt file.
562pub struct ListTagged<T, O> {
563    pub tags: Vec<T>,
564    pub object: O
565}
566
567
568
569//////////////////
570// CONSTRUCTING //
571//////////////////
572
573/// An object that can be parsed from a crypt file.
574///
575/// A derive macro exists for this type and is expected to be used over manually
576/// implementing it.
577pub trait Cryptic: Sized {
578    /// Attempts to convert an AST from a crypt file into a desired type.
579    ///
580    /// In most cases it is recommended to use [CryptParserServer] which will
581    /// automatically call this method an opened file.
582    fn cryptic(object: TracedObject) -> Result<Self, CryptError>;
583}
584
585impl TryInto<String> for TracedObject {
586    type Error = CryptError;
587
588    fn try_into(self) -> Result<String, Self::Error> {
589        match self.object {
590            CryptObject::ConstantString(str) => Ok(str),
591            CryptObject::FloatingIdentifier(ident) => Ok(ident),
592            CryptObject::ConstantInteger(int) => Ok(int.to_string()),
593            CryptObject::ConstantFloat(fl) => Ok(fl.to_string()),
594            CryptObject::ConstantBoolean(b) => Ok(b.to_string()),
595            CryptObject::Null => Ok(String::new()),
596            CryptObject::IdentifiedObject(_, obj) => (*obj).try_into(),
597            CryptObject::TaggedObject(_, obj) => (*obj).try_into(),
598            _ => Err(CryptError(Some(self.position), CryptErrorType::UnexpectedType))
599        }
600    }
601}
602
603impl TryInto<bool> for TracedObject {
604    type Error = CryptError;
605
606    fn try_into(self) -> Result<bool, Self::Error> {
607        match self.object {
608            CryptObject::ConstantString(str) => Ok(str != ""),
609            CryptObject::FloatingIdentifier(_) => Ok(true),
610            CryptObject::ConstantInteger(int) => Ok(int != 0),
611            CryptObject::ConstantFloat(fl) => Ok(fl != 0.0),
612            CryptObject::ConstantBoolean(b) => Ok(b),
613            CryptObject::Null => Ok(false),
614            CryptObject::IdentifiedObject(_, obj) => (*obj).try_into(),
615            CryptObject::TaggedObject(_, obj) => (*obj).try_into(),
616            _ => Err(CryptError(Some(self.position), CryptErrorType::UnexpectedType))
617        }
618    }
619}
620
621macro_rules! try_into_int {
622    ($($ty:ty)+) => {
623        $(
624            impl TryInto<$ty> for TracedObject {
625                type Error = CryptError;
626
627                fn try_into(self) -> Result<$ty, Self::Error> {
628                    match self.object {
629                        CryptObject::ConstantInteger(int) => Ok(int as _),
630                        CryptObject::ConstantFloat(fl) => Ok(fl as _),
631                        CryptObject::ConstantBoolean(b) => Ok(if b {1 as _} else {0 as _}),
632                        CryptObject::ConstantString(str) => match str.parse() {
633                            Ok(i) => Ok(i),
634                            Err(_) => Err(CryptError(Some(self.position), CryptErrorType::ParseError))
635                        },
636                        CryptObject::IdentifiedObject(_, item) => (*item).try_into(),
637                        CryptObject::TaggedObject(_, obj) => (*obj).try_into(),
638                        _ => Err(CryptError(Some(self.position), CryptErrorType::UnexpectedType))
639                    }
640                }
641            }
642        )+
643    };
644}
645try_into_int!(i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64);
646
647impl<T> TryInto<Vec<T>> for TracedObject
648where
649    T: Cryptic
650{
651    type Error = CryptError;
652
653    fn try_into(self) -> Result<Vec<T>, Self::Error> {
654        match self.object {
655            CryptObject::ObjectList(list) =>
656                list
657                    .into_iter()
658                    .map(|i| T::cryptic(i))
659                    .collect(),
660            CryptObject::ObjectBody(body) =>
661                body
662                    .into_values()
663                    .into_iter()
664                    .map(|i| T::cryptic(i))
665                    .collect(),
666
667            CryptObject::IdentifiedObject(_, obj) => (*obj).try_into(),
668            CryptObject::TaggedObject(_, obj) => (*obj).try_into(),
669
670            _ => Ok(vec![T::cryptic(self)?])
671        }
672    }
673}
674
675impl<T> TryInto<HashMap<String, T>> for TracedObject
676where
677    T: Cryptic
678{
679    type Error = CryptError;
680
681    fn try_into(self) -> Result<HashMap<String, T>, Self::Error> {
682        match self.object {
683            CryptObject::ObjectBody(body) =>
684                body
685                    .into_iter()
686                    .map(|(k, i)| T::cryptic(i).map(|i| (k, i)))
687                    .collect(),
688            CryptObject::ObjectList(list) => {
689                let mut body = HashMap::new();
690                let mut idx: usize = 0;
691                for item in list {
692                    if let CryptObject::IdentifiedObject(ident, item) = item.object {
693                        body.insert(ident, T::cryptic(*item)?);
694                    } else {
695                        body.insert(idx.to_string(), T::cryptic(item)?);
696                        idx += 1;
697                    }
698                }
699                Ok(body)
700            },
701
702            CryptObject::IdentifiedObject(ident, item) => Ok(vec![(ident, T::cryptic(*item)?)].into_iter().collect()),
703            CryptObject::TaggedObject(_, item) => (*item).try_into(),
704
705            _ => Err(CryptError(Some(self.position), CryptErrorType::UnexpectedType))
706        }
707    }
708}
709
710
711impl<T, O> TryInto<Tagged<T, O>> for TracedObject
712where
713    T: Cryptic,
714    O: Cryptic
715{
716    type Error = CryptError;
717
718    fn try_into(self) -> Result<Tagged<T, O>, Self::Error> {
719        match self.object {
720            CryptObject::TaggedObject(tags, obj) => {
721                let Some(tag) = tags.into_iter().next() else {
722                    return Err(CryptError(Some(self.position), CryptErrorType::ExpectedTag))
723                };
724                let tag = T::cryptic(tag)?;
725
726                Ok(Tagged { tag: tag, object: O::cryptic(*obj)? })
727            }
728
729            _ => Err(CryptError(Some(self.position), CryptErrorType::UnexpectedType))
730
731        }
732    }
733}
734
735impl<T, O> TryInto<ListTagged<T, O>> for TracedObject
736where
737    T: Cryptic,
738    O: Cryptic
739{
740    type Error = CryptError;
741
742    fn try_into(self) -> Result<ListTagged<T, O>, Self::Error> {
743        match self.object {
744            CryptObject::TaggedObject(tags, obj) => {
745                let tags = tags.into_iter().map(|i| T::cryptic(i)).collect::<Result<Vec<T>, CryptError>>()?;
746                Ok(ListTagged { tags, object: O::cryptic(*obj)? })
747            }
748
749            _ => Ok(ListTagged { tags: Vec::new(), object: O::cryptic(self)? })
750        }
751    }
752}
753
754impl<T, O> TryInto<HashTagged<T, O>> for TracedObject
755where
756    T: Cryptic,
757    O: Cryptic
758{
759    type Error = CryptError;
760
761    fn try_into(self) -> Result<HashTagged<T, O>, Self::Error> {
762        match self.object {
763            CryptObject::TaggedObject(tags, item) => {
764                let mut built_tags = HashMap::new();
765                let mut idx: usize = 0;
766                for tag in tags {
767                    if let CryptObject::IdentifiedObject(ident, item) = tag.object {
768                        built_tags.insert(ident, T::cryptic(*item)?);
769                    } else {
770                        built_tags.insert(idx.to_string(), T::cryptic(tag)?);
771                        idx += 1;
772                    }
773                }
774                Ok(HashTagged { tags: built_tags, object: O::cryptic(*item)? })
775            }
776
777            _ => Ok(HashTagged { tags: HashMap::new(), object: O::cryptic(self)? })
778        }
779    }
780}
781
782impl<T> Cryptic for T
783where
784    TracedObject: TryInto<T, Error = CryptError>,
785{
786    fn cryptic(object: TracedObject) -> Result<Self, CryptError> {
787        object.try_into()
788    }
789}
790
791impl Cryptic for CryptObject {
792    fn cryptic(object: TracedObject) -> Result<Self, CryptError> {
793        Ok(object.object)
794    }
795}
796
797impl Cryptic for CrypticObject {
798    fn cryptic(object: TracedObject) -> Result<Self, CryptError> {
799        Ok(match object.object {
800            CryptObject::ConstantString(str) => CrypticObject::ConstantString(str),
801            CryptObject::ConstantInteger(int) => CrypticObject::ConstantInteger(int),
802            CryptObject::ConstantFloat(fl) => CrypticObject::ConstantFloat(fl),
803            CryptObject::ConstantBoolean(b) => CrypticObject::ConstantBoolean(b),
804            CryptObject::Null => CrypticObject::Null,
805            CryptObject::FloatingIdentifier(ident) => CrypticObject::FloatingIdentifier(ident),
806            CryptObject::IdentifiedObject(ident, item) => CrypticObject::IdentifiedObject(ident, Box::new(CrypticObject::cryptic(*item).unwrap())),
807            CryptObject::TaggedObject (tags, item) => CrypticObject::TaggedObject(tags.into_iter().map(|i| CrypticObject::cryptic(i).unwrap()).collect(), Box::new(CrypticObject::cryptic(*item).unwrap())),
808            CryptObject::ObjectBody(body) => CrypticObject::ObjectBody(body.into_iter().map(|(ident, item)| (ident, CrypticObject::cryptic(item).unwrap())).collect()),
809            CryptObject::ObjectList(list) => CrypticObject::ObjectList(list.into_iter().map(|i| CrypticObject::cryptic(i).unwrap()).collect()),
810        })
811    }
812}
813
814impl<T> Cryptic for Option<T>
815where
816    T: Cryptic
817{
818    fn cryptic(object: TracedObject) -> Result<Self, CryptError> {
819        match object.object {
820            CryptObject::Null => Ok(None),
821            _ => Ok(Some(Cryptic::cryptic(object)?))
822        }
823    }
824}
825
826impl Cryptic for TracedObject {
827    fn cryptic(object: TracedObject) -> Result<Self, CryptError> {
828        Ok(object)
829    }
830}
831
832#[derive(Debug)]
833/// An error that can happen during the parsing and building of crypt files.
834pub struct CryptError(Option<TokenPosition>, CryptErrorType);
835
836impl CryptError {
837    pub const fn cannot_find_ident(name: &'static str) -> Self {
838        Self(None, CryptErrorType::NoItemOfIdentifier(name))
839    }
840    pub const fn not_enough_items() -> Self {
841        Self(None, CryptErrorType::NotEnoughItems)
842    }
843    pub fn invalid_enum_variant(variant: String) -> Self {
844        Self(None, CryptErrorType::InvalidEnumVariant(variant))
845    }
846}
847
848#[derive(Debug)]
849enum CryptErrorType {
850    UnexpectedToken(Token),
851    ExpectedOther(&'static str, Token),
852    UnexpectedEOF,
853    ExpectedIdentifier,
854    CannotImport,
855    UnexpectedType,
856    ParseError,
857    ExpectedTag,
858
859    NoItemOfIdentifier(&'static str),
860    NotEnoughItems,
861    InvalidEnumVariant(String),
862
863    OsError(std::io::Error),
864}
865
866impl std::fmt::Display for CryptError {
867    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
868        match &self.1 {
869            CryptErrorType::UnexpectedToken(t) => writeln!(f, "Unexpected token: {}", t),
870            CryptErrorType::ExpectedOther(desc, t) => writeln!(f, "Unexpected token: {}, expected {}", t, desc),
871            CryptErrorType::UnexpectedEOF => writeln!(f, "Unexpected end of file"),
872            CryptErrorType::ExpectedIdentifier => writeln!(f, "Expected an identifier"),
873            CryptErrorType::CannotImport => writeln!(f, "Cannot import here"),
874            CryptErrorType::UnexpectedType => writeln!(f, "Unexpected type"),
875            CryptErrorType::ParseError => writeln!(f, "Failed to parse"),
876            CryptErrorType::ExpectedTag => writeln!(f, "Expected a tag"),
877            CryptErrorType::OsError(e) => writeln!(f, "{}", e),
878            CryptErrorType::NoItemOfIdentifier(ident) => write!(f, "Couldn't find item: {}", ident),
879            CryptErrorType::NotEnoughItems => write!(f, "Not enough items found"),
880            CryptErrorType::InvalidEnumVariant(v) => write!(f, "Invalid enum variant: {}", v),
881        }?;
882
883        if let Some(pos) = &self.0 {
884            writeln!(f, "at: {:?}", pos)?;
885        }
886
887        Ok(())
888    }
889}
890
891impl std::error::Error for CryptError {}
892
893impl From<std::io::Error> for CryptError {
894    fn from(value: std::io::Error) -> Self {
895        CryptError(None, CryptErrorType::OsError(value))
896    }
897}