1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Tools for handling the tokens that make up strings in the L-system ([`crate::strings::ProductionString`]).
//!
//! Tokens can be of various kinds:
//!
//! * [`TokenKind::Terminal`] are the strict endpoints of the L-System. No production rule can target them.
//! * [`TokenKind::Production`] are those that can be handled by a production rule.
//!
//! Production rules ([`crate::productions::Production`]) will enforce that the target of a
//! production is a token of kind [`TokenKind::Production`].

use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};

use crate::DisplaySystem;
use crate::error::Error;

/// The various kinds of tokens that can make up a [`crate::strings::ProductionString`].
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd)]
pub enum TokenKind {
    /// Terminals are never transformed by production rules. 
    Terminal,
    /// Production tokens may be targets for [`crate::productions::Production`] rules.
    Production
}

impl TokenKind {
    pub fn is_terminal(&self) -> bool {
        matches!(self, TokenKind::Terminal)
    }
    pub fn is_production(&self) -> bool {
        matches!(self, TokenKind::Production)
    }
}

impl Display for TokenKind {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            TokenKind::Terminal => f.write_str("Terminal"),
            TokenKind::Production => f.write_str("Production")
        }
    }
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd)]
pub struct Token {
    kind: TokenKind,
    code: u32
}

impl Token {

    pub fn new(kind: TokenKind, code: u32) -> Self {
        Token {
            kind, code
        }
    }

    pub fn kind(&self) -> TokenKind {
        self.kind
    }

    /// A unique identifier for the token.
    ///
    /// This identifier is set when the token is created (see [`Token::new`]).
    /// The value may not be the same when generated by different instances of [`crate::prelude::System`],
    /// and the value here should not be relied on.
    pub fn code(&self) -> u32 {
        self.code
    }

    #[inline]
    pub fn is_terminal(&self) -> bool {
        self.kind.is_terminal()
    }

    #[inline]
    pub fn is_production(&self) -> bool {
        self.kind.is_production()
    }
}

impl Display for Token {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}[{}]", self.kind, self.code)
    }
}

pub trait TokenStore {
    fn add_token(&self, name: &str, kind: TokenKind) -> crate::Result<Token>;
    fn get_token(&self, name: &str) -> Option<Token>;
}

impl TokenStore for RefCell<HashMap<String, Token>> {
    fn add_token(&self, name: &str, kind: TokenKind) -> crate::Result<Token> {
        let mut map = self.borrow_mut();
        
        // If it already exists, return it.
        if let Some(value) = map.get(name) {
            return Ok(*value);
        }
        
        let max = map.values().map(|t| t.code).max().unwrap_or(0);
        let token = Token::new(kind, max + 1);

        map.insert(name.to_string(), token);
        Ok(token)
    }

    fn get_token(&self, name: &str) -> Option<Token> {
        let map = self.borrow();
        map.get(name).cloned()
    }
}

impl DisplaySystem for Token {
    fn format(&self, names: &HashMap<Token, String>) -> crate::Result<String> {
        let name = names.get(self)
            .ok_or_else(|| Error::general(format!("No name supplied for token {self:?}")))?;
        
        Ok(name.clone())
    }
}