1use std::fmt::Write;
2use std::{ops::Range, path::Path};
3
4pub mod compiler;
5pub mod globals;
6pub mod keycodes;
7pub mod vendor_coms;
8pub mod builder;
9
10#[derive(Debug)]
11pub struct ConfigError {
12 pub message: String,
13 pub span: Option<Range<usize>>,
14}
15
16impl std::fmt::Display for ConfigError {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 write!(f, "{}\n at: ({:?})", &self.message, &self.span)
19 }
20}
21
22impl std::error::Error for ConfigError {}
23
24impl From<std::io::Error> for ConfigError {
25 fn from(err: std::io::Error) -> Self {
26 Self {
27 message: err.to_string(),
28 span: None,
29 }
30 }
31}
32
33impl From<&str> for ConfigError {
34 fn from(err: &str) -> Self {
35 Self {
36 message: err.to_string(),
37 span: None,
38 }
39 }
40}
41
42impl ConfigError {
43 pub fn new(message: String, span: Range<usize>) -> Self {
44 Self {
45 message,
46 span: Some(span),
47 }
48 }
49
50 fn char_span(&self, source: &str) -> Option<Range<usize>> {
51 self.span.clone().map(|b: Range<usize>| {
52 let mut s = usize::MAX;
53 let mut j = 0;
54 for (i, c) in source.chars().enumerate() {
55 if j >= b.start {
56 if s == usize::MAX {
57 s = i;
58 }
59 if j >= b.end {
60 return Some(s..i);
61 }
62 }
63 j += c.len_utf8();
64 }
65 Some(s..j - 1)
66 })?
67 }
68
69 pub fn long_format(&self, source_file: &Path, source: &str) -> String {
70 let (line, col, slice) = self.line_col_slice(source);
71 let width = format!("{}", line + 10).len();
72 format!(
73 "error: {} \n --> {}:{}:{}\n{}",
74 self.message,
75 source_file.display(),
76 line,
77 col,
78 source[slice.0..slice.1]
79 .split('\n')
80 .skip(1)
81 .enumerate()
82 .fold(String::new(), |mut output, l| {
83 let _ = writeln!(output, " {:>width$} | {}", line + l.0, l.1);
84 output
85 })
86 )
87 }
88
89 pub fn line_col_slice(&self, source: &str) -> (usize, usize, (usize, usize)) {
90 let mut line = 1;
91 let mut col = 0;
92 let mut sol = 0;
93 let Some(span) = self.span.clone() else {
94 return (0, 0, (0, 0));
95 };
96
97 for (i, c) in source.char_indices() {
98 match c {
99 '\n' => {
100 if i <= span.start {
101 line += 1;
102 col = 0;
103 sol = i;
104 } else {
105 return (line, col, (sol, i));
106 }
107 }
108 _ if i <= span.start => {
109 col += 1;
110 }
111 _ => {}
112 }
113 }
114 (line, col, (sol, source.len()))
115 }
116}
117
118pub fn pretty_compile<'s>(
119 file: &Path,
120 src: &'s str,
121) -> Result<compiler::KeyboardConfig<'s>, ConfigError> {
122 match compiler::compile(src) {
123 Ok(config) => Ok(config),
124 Err(err) => {
125 use ariadne::{ColorGenerator, Label, Report, ReportKind, Source};
126 let filename = file.to_str().unwrap_or("<unknown>");
127 let mut colors = ColorGenerator::new();
128
129 let a = colors.next();
130 if let Some(span) = err.char_span(src) {
131 Report::build(ReportKind::Error, filename, 12)
132 .with_message("Invalid config".to_string())
133 .with_label(
134 Label::new((filename, span))
135 .with_message(&err.message)
136 .with_color(a),
137 )
138 .finish()
139 .eprint((filename, Source::from(src)))
140 .unwrap();
141 }
142 Err(err)
143 }
144 }
145}
146
147pub fn text_to_binary(source: &str) -> Result<Vec<u16>, ConfigError> {
148 let file = Path::new("<unknown>");
149 let config = pretty_compile(file, source)?;
150 Ok(config.serialize())
151}
152
153pub fn f32_to_u16(n: f32) -> ByteToU16IntoIter<4> {
154 bytes_to_u16(n.to_le_bytes())
155}
156pub fn bytes_to_u16<const N: usize>(bytes: [u8; N]) -> ByteToU16IntoIter<N> {
157 ByteToU16IntoIter::new(bytes)
158}
159pub struct ByteToU16IntoIter<const N: usize>([u8; N], usize);
160impl<const N: usize> ByteToU16IntoIter<N> {
161 pub fn new(bytes: [u8; N]) -> Self {
162 Self(bytes, 0)
163 }
164}
165impl<const N: usize> Iterator for ByteToU16IntoIter<N> {
166 type Item = u16;
167
168 fn next(&mut self) -> Option<Self::Item> {
169 let i = self.1;
170 if i + 1 < self.0.len() {
171 self.1 += 2;
172 Some(self.0[i] as u16 | ((self.0[i + 1] as u16) << 8))
173 } else {
174 None
175 }
176 }
177}
178
179#[macro_export]
180macro_rules! fixme {
181 ($a:expr) => {{
182 extern crate std;
183 std::eprintln!(
184 "FIXME\
186 ! at ./{}:{}:{}\n{:?}",
187 file!(),
188 line!(),
189 column!(),
190 $a,
191 )
192 }};
193}
194
195#[cfg(test)]
196#[path = "lib_test.rs"]
197mod test;