qbd/build.rs

243 lines
7.6 KiB
Rust
Raw Normal View History

2024-01-03 17:19:45 +01:00
use std::collections::{HashMap, HashSet};
use std::env;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
println!("cargo:rustc-link-arg-bins=-Tlink-rp.x");
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
let client = reqwest::blocking::Client::new();
let res = if Path::new("./target/keycodes.h").exists() {
std::fs::read_to_string("./target/keycodes.h").unwrap()
} else {
let res = client
.get("https://raw.githubusercontent.com/qmk/qmk_firmware/master/quantum/keycodes.h")
.send()
.unwrap()
.text()
.unwrap();
std::fs::write("./target/keycodes.h", res.as_bytes()).unwrap();
res
};
let mut log_file = std::fs::OpenOptions::new()
.create(true)
.append(false)
.write(true)
.open("./target/tokens.log")
.unwrap();
let mut global_register = HashMap::with_capacity(9086);
let mut enums = Vec::with_capacity(64);
let iter = res.lines();
let mut is_block_comment = false;
for line in iter {
parse_line(
line,
&mut enums,
&mut is_block_comment,
&mut global_register,
&mut log_file,
);
}
let mut mod_file = std::fs::OpenOptions::new()
.create(true)
.append(false)
.write(true)
.open("./src/keycodes/mod.rs")
.unwrap();
for info in enums {
mod_file.write_fmt(format_args!("{info}\n")).unwrap();
mod_file.flush().unwrap();
}
}
fn parse_line(
line: &str,
enums: &mut Vec<EnumInfo>,
is_block_comment: &mut bool,
global_register: &mut HashMap<String, String>,
log_file: &mut File,
) {
log_file.write_fmt(format_args!("Line {line:?}\n")).unwrap();
log_file.flush().unwrap();
let line = line.trim();
let mut it = line
.split_ascii_whitespace()
.filter(|s| !s.is_empty())
.peekable();
while let Some(token) = it.next().map(ToOwned::to_owned) {
log_file
.write_fmt(format_args!("token {token:?}\n"))
.unwrap();
log_file.flush().unwrap();
println!("token {token:?}");
match token.as_str() {
_ if token.starts_with("#") => {
return;
}
"//" => {
log_file
.write_fmt(format_args!("skip {token:?}\n"))
.unwrap();
log_file.flush().unwrap();
return;
}
"enum" => {
log_file
.write_fmt(format_args!("new enum {token:?}\n"))
.unwrap();
log_file.flush().unwrap();
enums.push(EnumInfo::new(
it.next().expect("enum must have name").to_owned(),
));
}
"{" => {}
"," => {}
"}" | "};" => {}
_ if token.starts_with("/*") && token.ends_with("*/") => {
println!("closed comment");
}
_ if token.ends_with("*/") => {
*is_block_comment = false;
}
_ if *is_block_comment || token.starts_with("/*") => {
*is_block_comment = true;
println!("comment {token:?}");
log_file
.write_fmt(format_args!("comment {token:?}\n"))
.unwrap();
log_file.flush().unwrap();
while let Some(t) = it.next() {
println!("looking for \"*/\" : {t:?}");
log_file
.write_fmt(format_args!("looking for \"*/\" : {t:?}\n"))
.unwrap();
log_file.flush().unwrap();
if t.ends_with("*/") {
println!("ends looking for closing comment with {t:?}");
*is_block_comment = false;
break;
}
}
}
_ => {
if enums.is_empty() {
return;
}
let name = token.as_str();
println!("new variant name is {name:?}");
it.next().expect("variant must be followed by =");
let val = it.next().expect("variant must have value").trim_end_matches(",");
enums
.last_mut()
.expect("must have enum token before here")
.add_variant(name, val, global_register);
}
}
}
}
pub type VariantName = String;
pub type VariantValue = String;
pub type EnumName = String;
#[derive(Debug)]
pub struct EnumInfo {
pub name: String,
pub variants: HashMap<VariantName, VariantValue>,
pub values_cache: HashSet<VariantValue>,
}
impl Display for EnumInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("#[derive(Debug)]\n")?;
f.write_str("#[repr(C)]\n")?;
f.write_str("pub enum ")?;
f.write_str(Self::rust_enum_name(&self.name).as_str())?;
f.write_str(" {\n")?;
for (var_name, var_val) in &self.variants {
f.write_fmt(format_args!(
" {} = {},\n",
Self::rust_enum_name(var_name),
var_val
))?;
}
f.write_str("}")?;
Ok(())
}
}
impl EnumInfo {
const CACHE_SIZE: usize = 1024 * 5;
pub fn new(name: String) -> EnumInfo {
Self {
name,
variants: HashMap::with_capacity(Self::CACHE_SIZE),
values_cache: HashSet::with_capacity(Self::CACHE_SIZE),
}
}
pub fn add_variant<'global>(
&mut self,
name: &str,
val: &str,
global_register: &'global mut HashMap<VariantName, EnumName>,
) {
println!("adding variant {name:?} {val:?}");
if self.values_cache.contains(val) {
return;
}
self.variants.insert(
name.to_owned(),
global_register
.get(val)
.map(|enum_name| format!("{enum_name}::{val} as usize"))
.unwrap_or_else(|| val.to_owned()),
);
global_register.insert(name.to_owned(), self.name.clone());
}
fn rust_enum_name(name: &str) -> String {
let mut prev_underscore = false;
name.chars()
.enumerate()
.filter_map(|(idx, c)| {
if idx == 0 {
Some(c.to_ascii_uppercase())
} else if c == '_' {
prev_underscore = true;
None
} else if prev_underscore {
prev_underscore = false;
Some(c.to_ascii_uppercase())
} else {
Some(c)
}
})
.collect()
}
}