Skip to main content

castep_cell_fmt/
parse.rs

1use crate::{
2    Cell, CellValue,
3    error::{CResult, Error},
4    parser::parse_cell_file,
5    query::{find_block, find_keyvalue, value_as_bool, value_as_f64, value_as_i32,
6            value_as_string, value_as_u32},
7};
8
9// ── Core traits ──────────────────────────────────────────────────────────────
10
11/// Leaf-level: parse a single `CellValue` into a Rust type.
12///
13/// Implemented by scalars, unit enums, keyword enums, and row structs.
14pub trait FromCellValue: Sized {
15    fn from_cell_value(value: &CellValue<'_>) -> CResult<Self>;
16}
17
18/// Block-level: parse a block's rows into a struct.
19///
20/// `BLOCK_NAME` is the case-insensitive block name used for lookup.
21/// `BLOCK_ALIASES` provides alternative block names also accepted during deserialization.
22/// The primary `BLOCK_NAME` is tried first; aliases are checked only if it is not found.
23pub trait FromBlock: Sized {
24    const BLOCK_NAME: &'static str;
25
26    /// Alternative block names accepted during deserialization.
27    /// Checked in order after `BLOCK_NAME` is not found.
28    /// Defaults to `&[]` (no aliases) for backward compatibility.
29    const BLOCK_ALIASES: &'static [&'static str] = &[];
30
31    fn from_block_rows(rows: &[CellValue<'_>]) -> CResult<Self>;
32
33    /// Provided: find the block in the token slice and parse it.
34    fn from_cells(tokens: &[Cell<'_>]) -> CResult<Self> {
35        match find_block(tokens, Self::BLOCK_NAME) {
36            Ok(rows) => Self::from_block_rows(rows),
37            Err(Error::KeyNotFound(_)) => {
38                for alias in Self::BLOCK_ALIASES {
39                    if let Ok(rows) = find_block(tokens, alias) {
40                        return Self::from_block_rows(rows);
41                    }
42                }
43                Err(Error::KeyNotFound(Self::BLOCK_NAME.to_string()))
44            }
45            Err(e) => Err(e),
46        }
47    }
48}
49
50/// KeyValue-level: parse the value at a known key into a type.
51///
52/// `KEY_NAME` is the case-insensitive key used for lookup.
53/// `KEY_ALIASES` provides alternative key names also accepted during deserialization.
54pub trait FromKeyValue: Sized {
55    const KEY_NAME: &'static str;
56
57    /// Alternative key names accepted during deserialization.
58    /// Defaults to `&[]` (no aliases) for backward compatibility.
59    const KEY_ALIASES: &'static [&'static str] = &[];
60
61    fn from_cell_value_kv(value: &CellValue<'_>) -> CResult<Self>;
62
63    /// Provided: returns `None` if the key and all aliases are absent (optional fields).
64    fn from_cells(tokens: &[Cell<'_>]) -> CResult<Option<Self>> {
65        match find_keyvalue(tokens, Self::KEY_NAME) {
66            Ok(v) => Self::from_cell_value_kv(v).map(Some),
67            Err(Error::KeyNotFound(_)) => {
68                for alias in Self::KEY_ALIASES {
69                    if let Ok(v) = find_keyvalue(tokens, alias) {
70                        return Self::from_cell_value_kv(v).map(Some);
71                    }
72                }
73                Ok(None)
74            }
75            Err(e) => Err(e),
76        }
77    }
78}
79
80/// File-level: assemble a top-level struct from the full token slice.
81pub trait FromCellFile: Sized {
82    fn from_cell_file(tokens: &[Cell<'_>]) -> CResult<Self>;
83}
84
85// ── Entry point ──────────────────────────────────────────────────────────────
86
87/// Parse a `.cell` / `.param` file string and deserialize into `T`.
88pub fn parse<T: FromCellFile>(input: &str) -> CResult<T> {
89    let tokens =
90        parse_cell_file(input).map_err(|errors| Error::Message(format!("{errors:?}")))?;
91    T::from_cell_file(&tokens)
92}
93
94// ── Primitive FromCellValue impls ─────────────────────────────────────────────
95
96impl FromCellValue for f64 {
97    fn from_cell_value(value: &CellValue<'_>) -> CResult<Self> {
98        value_as_f64(value)
99    }
100}
101
102impl FromCellValue for u32 {
103    fn from_cell_value(value: &CellValue<'_>) -> CResult<Self> {
104        value_as_u32(value)
105    }
106}
107
108impl FromCellValue for i32 {
109    fn from_cell_value(value: &CellValue<'_>) -> CResult<Self> {
110        value_as_i32(value)
111    }
112}
113
114impl FromCellValue for bool {
115    fn from_cell_value(value: &CellValue<'_>) -> CResult<Self> {
116        value_as_bool(value)
117    }
118}
119
120impl FromCellValue for String {
121    fn from_cell_value(value: &CellValue<'_>) -> CResult<Self> {
122        value_as_string(value)
123    }
124}
125
126/// `FromCellValue` for borrowed `&str` is intentionally not provided here
127/// because the lifetime dependency makes it impractical as a blanket impl.
128/// Use `value_as_str` directly when a `&str` is needed.
129impl<T: FromCellValue, const N: usize> FromCellValue for [T; N] {
130    fn from_cell_value(value: &CellValue<'_>) -> CResult<Self> {
131        match value {
132            CellValue::Array(arr) => {
133                if arr.len() < N {
134                    return Err(Error::Message(format!(
135                        "expected array of length {N}, got {}",
136                        arr.len()
137                    )));
138                }
139                // Collect into a Vec then convert to array
140                let items: Vec<T> = arr
141                    .iter()
142                    .take(N)
143                    .map(T::from_cell_value)
144                    .collect::<CResult<Vec<T>>>()?;
145                items.try_into().map_err(|_| {
146                    Error::Message(format!("failed to convert Vec to [{N}] array"))
147                })
148            }
149            other => Err(Error::UnexpectedType(
150                format!("Array[{N}]"),
151                format!("{other:?}"),
152            )),
153        }
154    }
155}