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