243 lines
7.6 KiB
Rust
243 lines
7.6 KiB
Rust
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()
|
|
}
|
|
}
|