ZRY 1 month ago
parent
commit
05b002dc4a

+ 14 - 0
.gitignore

@@ -11,3 +11,17 @@
 # Generated by Cargo
 /target/
 
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="XMakeProjectSettings">
+    <option name="currentArchitecture" value="x86" />
+    <option name="workingDirectory" value="$PROJECT_DIR$" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/wasi-import-from-yaml-docs-proc-macro.iml" filepath="$PROJECT_DIR$/.idea/wasi-import-from-yaml-docs-proc-macro.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 14 - 0
.idea/wasi-import-from-yaml-docs-proc-macro.iml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/testing/test_proj/wasi-test/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/wasi-import-from-yaml-docs-proc-macro/src" isTestSource="false" />
+      <excludeFolder url="file://$MODULE_DIR$/wasi-import-from-yaml-docs-proc-macro/target" />
+      <excludeFolder url="file://$MODULE_DIR$/testing/test_proj/wasi-test/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 71 - 0
testing/docs.yaml

@@ -0,0 +1,71 @@
+ngvfs_n_init:
+  test_func1:
+    efid: "fXm7si4ffuuvbzxfsckhf32kj4ua999999"
+    desc: "测试函数1"
+    params:
+      - name: ptrMajorVersion
+        type: i32
+        xtyp: "*mut i32"
+        desc: "存放获取到的MajorVersion的i32变量指针"
+      - name: ptrMinorVersion
+        type: i32
+        xtyp: "*mut i32"
+        desc: "存放获取到的MinorVersion的i32变量指针"
+      - name: ptrPatchVersion
+        type: i32
+        xtyp: "*mut i32"
+        desc: "存放获取到的PatchVersion的i32变量指针"
+  test_func2:
+    efid: "fXipq4ewps4rgprf5tlys3qdgoye999999"
+    desc: "测试函数2"
+    params:
+      - name: ptrKey
+        type: i32
+        xtyp: "*const u8"
+        desc: "key的指针"
+      - name: lenKey
+        type: i32
+        xtyp: "usize"
+        desc: "key的长度"
+      - name: ptrBuf
+        type: i32
+        xtyp: "*mut u8"
+        desc: "存放值的缓冲区指针"
+      - name: lenBuf
+        type: i32
+        xtyp: "usize"
+        desc: "缓冲区长度。缓冲区不足则值会被截断"
+    results:
+      - name: result
+        type: i32
+        xtyp: "enum"
+        desc: "结果"
+        enum:
+          "-3": "写入buf时内存越界"
+          "-2": "key不存在"
+          "-1": "读取key时内存越界"
+          "0": "缓冲区长度小于等于0"
+          ">0": "写入缓冲区的值的长度"
+  test_func3:
+    efid: "fX6bo3gexcifdrxdwyhseym5irau999999"
+    desc: "测试函数3"
+    results:
+      - name: result
+        type: i32
+        xtyp: "enum"
+        desc: "结果"
+        enum:
+          "-3": "写入buf时内存越界"
+          "-2": "key不存在"
+          "-1": "读取key时内存越界"
+          "0": "缓冲区长度小于等于0"
+          ">0": "写入缓冲区的值的长度"
+      - name: testRet
+        type: i32
+        xtyp: "*const u8"
+        desc: "测试返回值"
+  test_func4:
+    efid: "fXiz5epbvgrbc5jfmuhucvuekcpq999999"
+    desc: "测试函数4"
+    params:
+    results:

+ 9 - 0
testing/test_proj/wasi-test/Cargo.toml

@@ -0,0 +1,9 @@
+[package]
+name = "wasi-test"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+wasi-import-from-yaml-docs-proc-macro = { path = "../../../wasi-import-from-yaml-docs-proc-macro" }

+ 11 - 0
testing/test_proj/wasi-test/src/main.rs

@@ -0,0 +1,11 @@
+mod raw_fn;
+
+fn main() {
+    println!("Hello, world!");
+    unsafe{
+        let mut a = 0;
+        let mut b = 0;
+        let mut c = 0;
+        raw_fn::ngvfs_n_init::test_func1(&mut a, &mut b, &mut c);
+    }
+}

+ 3 - 0
testing/test_proj/wasi-test/src/raw_fn.rs

@@ -0,0 +1,3 @@
+use wasi_import_from_yaml_docs_proc_macro::generate_wasi_import;
+
+generate_wasi_import!("../../docs.yaml");

+ 17 - 0
wasi-import-from-yaml-docs-proc-macro/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "wasi-import-from-yaml-docs-proc-macro"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = { version="2.0.16", features=["full"] }
+quote = "1.0.27"
+proc-macro2 = "1.0.58"
+proc-macro-error = "1.0.4"
+serde = { version = "1", features = ["derive"] }
+serde_yaml = "^0.9.33"

+ 245 - 0
wasi-import-from-yaml-docs-proc-macro/src/lib.rs

@@ -0,0 +1,245 @@
+mod yaml_def;
+use proc_macro::TokenStream;
+use quote::{quote};
+use syn::{LitStr, Type};
+use proc_macro_error::{proc_macro_error, abort_call_site};
+use crate::yaml_def::{Function};
+
+extern crate syn;
+extern crate std;
+
+#[proc_macro]
+#[proc_macro_error]
+pub fn generate_wasi_import(input: TokenStream) -> TokenStream{
+    let ast = syn::parse_macro_input!(input as LitStr);
+    let yaml_path = ast.value();
+    let yaml_content = match std::fs::read_to_string(&yaml_path) {
+        Ok(content) => content,
+        Err(err) => {
+            abort_call_site! {
+                "Failed to read YAML file: {}", err;
+                note = "check YAML file path: {}", yaml_path;
+            }
+        }
+    };
+    let yaml_root: yaml_def::YamlRoot = match serde_yaml::from_str(&yaml_content) {
+        Ok(root) => root,
+        Err(err) => {
+            abort_call_site! {
+                "Failed to parse YAML file: {}", err;
+                note = "check YAML file syntax";
+            }
+        }
+    };
+    let modules = yaml_root.iter().map(|i| {
+        let mod_name = i.0;
+        let mod_val = i.1;
+        let fns_result = mod_val.iter().map(|j| {
+            let fn_name = j.0;
+            let fn_val = j.1;
+            let fn_efid = &fn_val.efid;
+            let fn_efid_ident = syn::Ident::new(fn_efid, proc_macro2::Span::call_site());
+            let fn_params = process_fn_signature_param(fn_val, mod_name, fn_name);
+            let fn_results = process_fn_signature_results(fn_val, mod_name, fn_name);
+            let fn_retval = match fn_results {
+                Some(s) => {
+                    quote! {
+                        -> #s
+                    }
+                },
+                None => {
+                    proc_macro2::TokenStream::new()
+                }
+            };
+            quote! {
+                #[doc = #fn_name ]
+                fn #fn_efid_ident(#fn_params) #fn_retval;
+            }
+        });
+        quote! {
+            #[link(wasm_import_module = #mod_name)]
+            extern "C" {
+                #(#fns_result)*
+            }
+        }
+    });
+
+
+    let module_wraps = yaml_root.iter().map(|i| {
+        let mod_name = i.0;
+        let mod_name_ident = syn::Ident::new(mod_name, proc_macro2::Span::call_site());
+        let mod_val = i.1;
+        let fns_result = mod_val.iter().map(|j| {
+            let fn_name = j.0;
+            let fn_val = j.1;
+            let fn_efid = &fn_val.efid;
+            let fn_name_ident = syn::Ident::new(fn_name, proc_macro2::Span::call_site());
+            let fn_efid_ident = syn::Ident::new(fn_efid, proc_macro2::Span::call_site());
+            let fn_params = process_fn_signature_param(fn_val, mod_name, fn_name);
+            let fn_call_params = process_fn_call_param(fn_val);
+            let fn_results = process_fn_signature_results(fn_val, mod_name, fn_name);
+            let fn_retval = match fn_results {
+                Some(s) => {
+                    quote! {
+                        -> #s
+                    }
+                },
+                None => {
+                    proc_macro2::TokenStream::new()
+                }
+            };
+            quote! {
+                pub unsafe fn #fn_name_ident(#fn_params) #fn_retval {
+                    crate::raw_fn::#fn_efid_ident(#fn_call_params)
+                }
+            }
+        });
+        quote! {
+            pub mod #mod_name_ident {
+                #(#fns_result)*
+            }
+        }
+    });
+
+    let result = quote! {
+        #(#modules)*
+
+        #(#module_wraps)*
+    };
+
+    result.into()
+}
+
+fn process_fn_signature_param(input: &Function, mod_name: &str, fn_name: &str) -> proc_macro2::TokenStream {
+    match &input.params {
+        Some(s) => {
+            if s.len() == 0 {
+                return proc_macro2::TokenStream::new();
+            }
+            let params_iter = s.iter().map(|i| {
+                let param_name = &i.name;
+                let param_name_ident = syn::Ident::new(&param_name, proc_macro2::Span::call_site());
+                let is_use_xtyp;
+                let param_type = match &i.xtyp {
+                    Some(s) => {
+                        if s.as_str() == "enum" {
+                            is_use_xtyp = false;
+                            &i.rtype
+                        }else{
+                            is_use_xtyp = true;
+                            s
+                        }
+                    },
+                    None => {
+                        is_use_xtyp = false;
+                        &i.rtype
+                    }
+                };
+                let param_type_parse_res = syn::parse_str::<Type>(param_type.as_str());
+                let param_type_ast = match param_type_parse_res {
+                    Ok(ast) => ast,
+                    Err(err) => {
+                        let fn_fullname = format!("{}::{}", mod_name, fn_name);
+                        let p_src = if is_use_xtyp {"xtyp"}else{"type"};
+                        let yaml_field_path = format!("{}.{}.{}.{}", mod_name, fn_name, param_name, p_src);
+                        abort_call_site! {
+                            "Failed to read param {} of {}: '{}': {}", p_src, fn_fullname, param_type, err;
+                            note = "Syntax error of this yaml field: {}", yaml_field_path;
+                        }
+                    }
+                };
+                quote!(#param_name_ident: #param_type_ast)
+            });
+            quote! {
+                #(#params_iter),*
+            }
+        },
+        None => {
+            proc_macro2::TokenStream::new()
+        }
+    }
+}
+
+fn process_fn_call_param(input: &Function) -> proc_macro2::TokenStream {
+    match &input.params {
+        Some(s) => {
+            if s.len() == 0 {
+                return proc_macro2::TokenStream::new();
+            }
+            let params_iter = s.iter().map(|i| {
+                let param_name = &i.name;
+                syn::Ident::new(&param_name, proc_macro2::Span::call_site())
+            });
+            quote! {
+                #(#params_iter),*
+            }
+        },
+        None => {
+            proc_macro2::TokenStream::new()
+        }
+    }
+}
+
+fn process_fn_signature_results(input: &Function, mod_name: &str, fn_name: &str) -> Option<proc_macro2::TokenStream> {
+    match &input.results {
+        Some(s) => {
+            if s.len() == 0 {
+                return None;
+            }
+            let mut results_iter = s.iter().map(|i| {
+                let is_use_xtyp;
+                let result_type = match &i.xtyp {
+                    Some(s) => {
+                        if s.as_str() == "enum" {
+                            is_use_xtyp = false;
+                            &i.rtype
+                        }else{
+                            is_use_xtyp = true;
+                            s
+                        }
+                    },
+                    None => {
+                        is_use_xtyp = false;
+                        &i.rtype
+                    }
+                };
+                let result_type_parse_res = syn::parse_str::<Type>(result_type.as_str());
+                let result_type_ast = match result_type_parse_res {
+                    Ok(ast) => ast,
+                    Err(err) => {
+                        let fn_fullname = format!("{}::{}", mod_name, fn_name);
+                        let p_src = if is_use_xtyp {"xtyp"}else{"type"};
+                        let yaml_field_path = format!("{}.{}.{}.{}", mod_name, fn_name, &i.name, p_src);
+                        abort_call_site! {
+                            "Failed to read result {} of {}: '{}': {}", p_src, fn_fullname, result_type, err;
+                            note = "Syntax error of this yaml field: {}", yaml_field_path;
+                        }
+                    }
+                };
+                result_type_ast
+            });
+            Some(if results_iter.len() == 1 {
+                let result_res = results_iter.next();
+                let result = match result_res {
+                    Some(s) => s,
+                    None => {
+                        abort_call_site! {
+                            "iter error in {}::{}: there should be one result in iter but got none.", mod_name, fn_name;
+                            note = "This is macro bug. Please report it.";
+                        }
+                    }
+                };
+                quote! {
+                    #result
+                }
+            }else{
+                quote! {
+                    (#(#results_iter),*)
+                }
+            })
+        },
+        None => {
+            None
+        }
+    }
+}

+ 25 - 0
wasi-import-from-yaml-docs-proc-macro/src/yaml_def.rs

@@ -0,0 +1,25 @@
+use serde::{Serialize, Deserialize};
+use std::collections::HashMap;
+
+pub type YamlRoot = HashMap<String, Module>;
+
+pub type Module = HashMap<String, Function>;
+
+#[derive(Serialize, Deserialize, PartialEq, Debug)]
+pub struct Function {
+    pub efid: String,
+    pub desc: Option<String>,
+    pub params: Option<Vec<Param>>,
+    pub results: Option<Vec<Param>>,
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Debug)]
+pub struct Param {
+    pub name: String,
+    pub desc: Option<String>,
+    #[serde(rename = "type")]
+    pub rtype: String,
+    pub xtyp: Option<String>,
+    #[serde(rename = "enum")]
+    pub enumeration: Option<HashMap<String, String>>,
+}