rust 将文件内容加载到静态字节数组中

v09wglhw  于 2022-12-19  发布在  其他
关注(0)|答案(3)|浏览(122)

我有一个静态数组初始化了一些常数值:

static PROG_ROM: [u8; 850] = [0x12, 0x1d, ...];

我想在编译时加载文件的内容,听起来像是std::include_bytes!的工作,但是我有两个问题:

  1. include_bytes!("foo.dat")的类型是&[u8; 850],即它是一个引用。我需要这是一个真正的静态数组。
    1.即使有一个include_bytes_static!宏类型为[u8;850],我也必须像这样使用它:
static PROG_ROM: [u8; 850] = include_bytes_static!("foo.dat");

也就是说,我必须硬编码文件的长度。相反,我想从文件内容的长度中提取长度。
因此,我的代码的理想替代品应该是一个宏来替换整个定义,即类似于以下内容:

define_included_bytes!(PROG_ROM, "foo.dat")

它会扩展到

static PROG_ROM: [u8; 850] = [0x12, 0x1d, ...];

我该怎么做呢?

p8h8hvxi

p8h8hvxi1#

使用*include_bytes!(..)来获取[u8; _]而不是&[u8; _](因为数组实现了Copy),并使用include_bytes!(..).len()(这是一个常量方法)来指定类型中数组的长度:

static PROG_ROM: [u8; include_bytes!("foo.dat").len()] = *include_bytes!("foo.dat");
v7pvogib

v7pvogib2#

正如Chayim Friedman指出的,你可以很容易地自己定义proc宏:

#[proc_macro]
pub fn define_included_bytes(token_stream: TokenStream) -> TokenStream {
    let [ident, _comma, file] = &token_stream.into_iter().collect::<Vec<_>>()[..] else {
        panic!("expected invocation: `define_included_bytes!(IDENTIFIER, \"file_name\");");
    };
    let file = file.to_string().trim_matches('\"').to_string();
    let data: Vec<u8> = std::fs::read(&file).expect(&format!("File {:?} could not be read", file));
    format!("const {ident}: [u8; {}] = {:?};", data.len(), data).parse().unwrap()
}

显然,这只是一个拼凑在一起的概念证明,你应该仔细检查令牌,而不是仅仅假设它们是正确的。

yyyllmsg

yyyllmsg3#

基于cafce25's answer,我最终使用synquote编写了以下版本:

extern crate proc_macro;
extern crate syn;
extern crate quote;

use proc_macro::TokenStream;
use syn::parse::{Parse, ParseStream, Result};
use syn::{parse_macro_input, Ident, Token, LitStr};
use quote::quote;

struct StaticInclude {
    name: Ident,
    filepath: String,
}

impl Parse for StaticInclude {
    fn parse(input: ParseStream) -> Result<Self> {
        let name: Ident = input.parse()?;
        input.parse::<Token![=]>()?;
        let filepath: String = input.parse::<LitStr>()?.value();
        Ok(StaticInclude{ name, filepath })
    }
}

#[proc_macro]
pub fn progmem_include_bytes(tokens: TokenStream) -> TokenStream {
    let StaticInclude{ name, filepath } = parse_macro_input!(tokens as StaticInclude);

    let data: Vec<u8> = std::fs::read(&filepath).expect(&format!("File {:?} could not be read", filepath));
    let len = data.len();
    TokenStream::from(quote! {
        #[link_section = ".progmem.data"]
        static #name: [u8; #len] = [#(#data),*];
    })
}

(别管link_section属性,它是AVR-ism)。
这样做效果很好,只是Cargo不跟踪外部文件的依赖性,因此如果其内容发生变化,使用progmem_include_bytes!的程序不会被cargo build重新编译。

相关问题