1pub const CARRIED_C_FUNCTIONS: &[u8; 8] = b"WRPCcfun";
7pub const HAND_LINKED_LIBC: [&str; 8] = ["abs", "strlen", "atoi", "atol", "atof", "strcmp", "strncmp", "rand"];
9const FIELD_SEPARATOR: char = '\t';
10const LIST_SEPARATOR: char = ',';
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub enum CPointer {
15 Text,
17 Memory,
19 Handle,
21 Out,
24 Struct,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30pub enum ParamType {
31 I32,
32 I64,
33 F32,
34 F64,
35 Ptr,
37 Handle,
39 Out,
41 Bytes,
44 Length,
46 StructIn,
48 StructsIn,
50 StructOut,
52 ErrorOut,
54 StructsOut,
57}
58
59impl ParamType {
60 pub fn is_passed(self) -> bool {
62 !matches!(
63 self,
64 ParamType::Out | ParamType::ErrorOut | ParamType::Length | ParamType::StructOut | ParamType::StructsOut
65 )
66 }
67
68 pub fn is_struct_output(self) -> bool {
69 matches!(self, ParamType::StructOut | ParamType::StructsOut)
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
75pub enum RetType {
76 Void,
77 I32,
78 I64,
79 F32,
80 F64,
81 Bool,
82}
83
84const PARAM_NAMES: [(ParamType, &str); 14] = [
85 (ParamType::I32, "i32"),
86 (ParamType::I64, "i64"),
87 (ParamType::F32, "f32"),
88 (ParamType::F64, "f64"),
89 (ParamType::Ptr, "ptr"),
90 (ParamType::Handle, "handle"),
91 (ParamType::Out, "out"),
92 (ParamType::Bytes, "bytes"),
93 (ParamType::Length, "length"),
94 (ParamType::StructIn, "struct_in"),
95 (ParamType::StructsIn, "structs_in"),
96 (ParamType::StructOut, "struct_out"),
97 (ParamType::StructsOut, "structs_out"),
98 (ParamType::ErrorOut, "error_out"),
99];
100const RET_NAMES: [(RetType, &str); 6] = [
101 (RetType::Void, "void"),
102 (RetType::I32, "i32"),
103 (RetType::I64, "i64"),
104 (RetType::F32, "f32"),
105 (RetType::F64, "f64"),
106 (RetType::Bool, "bool"),
107];
108const RESULT_NAMES: [(Option<CPointer>, &str); 4] = [
110 (None, "number"),
111 (Some(CPointer::Text), "text"),
112 (Some(CPointer::Handle), "handle"),
113 (Some(CPointer::Struct), "struct"),
114];
115const LAYOUT_SEPARATOR: char = ';';
116
117fn name_of<T: PartialEq + Copy>(names: &[(T, &'static str)], value: T) -> &'static str {
118 names.iter().find(|(known, _)| *known == value).map_or("?", |(_, name)| name)
119}
120
121fn value_of<T: Copy>(names: &[(T, &str)], name: &str) -> Option<T> {
122 names.iter().find(|(_, known)| *known == name).map(|(value, _)| *value)
123}
124
125#[derive(Clone, Debug, PartialEq, Eq)]
128pub struct CFunction {
129 pub module: String,
130 pub library: String,
131 pub name: String,
132 pub param_types: Vec<ParamType>,
133 pub result: Option<CPointer>,
135 pub ret_type: RetType,
136 pub out_pointee: Option<String>,
138 pub layouts: Vec<String>,
140 pub failure: Failure,
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
147pub enum Failure {
148 None,
149 NotNull,
150 False,
151}
152const FAILURE_NAMES: [(Failure, &str); 3] =
153 [(Failure::None, ""), (Failure::NotNull, "not_null"), (Failure::False, "false")];
154
155pub fn is_error_type(c_type: &str) -> bool {
157 let lower = c_type.to_lowercase();
158 ["error", "trap", "status", "exception"].iter().any(|word| lower.contains(word))
159}
160
161impl CFunction {
162 pub fn line(&self) -> String {
165 let params: Vec<&str> = self.param_types.iter().map(|param| name_of(&PARAM_NAMES, *param)).collect();
166 [
167 self.module.as_str(),
168 &self.library,
169 &self.name,
170 ¶ms.join(&LIST_SEPARATOR.to_string()),
171 name_of(&RET_NAMES, self.ret_type),
172 name_of(&RESULT_NAMES, self.result),
173 self.out_pointee.as_deref().unwrap_or_default(),
174 &self.layouts.join(&LAYOUT_SEPARATOR.to_string()),
175 name_of(&FAILURE_NAMES, self.failure),
176 ]
177 .join(&FIELD_SEPARATOR.to_string())
178 }
179
180 pub fn of_line(line: &str) -> Option<CFunction> {
181 let [module, library, name, params, ret_type, result, out_pointee, layouts, failure] =
182 line.split(FIELD_SEPARATOR).collect::<Vec<_>>().try_into().ok()?;
183 let param_types = params
184 .split(LIST_SEPARATOR)
185 .filter(|param| !param.is_empty())
186 .map(|param| value_of(&PARAM_NAMES, param))
187 .collect::<Option<_>>()?;
188 Some(CFunction {
189 module: module.to_string(),
190 library: library.to_string(),
191 name: name.to_string(),
192 param_types,
193 result: value_of(&RESULT_NAMES, result)?,
194 ret_type: value_of(&RET_NAMES, ret_type)?,
195 out_pointee: (!out_pointee.is_empty()).then(|| out_pointee.to_string()),
196 layouts: layouts.split(LAYOUT_SEPARATOR).filter(|_| !layouts.is_empty()).map(str::to_string).collect(),
197 failure: value_of(&FAILURE_NAMES, failure)?,
198 })
199 }
200}
201
202pub fn with_c_functions(functions: &[CFunction], machine_code: &[u8]) -> Vec<u8> {
205 if functions.is_empty() {
206 return machine_code.to_vec();
207 }
208 let table = functions.iter().map(CFunction::line).collect::<Vec<_>>().join("\n");
209 [CARRIED_C_FUNCTIONS.as_slice(), &(table.len() as u64).to_le_bytes(), table.as_bytes(), machine_code].concat()
210}
211
212pub fn split_c_functions(carried: &[u8]) -> Result<(Vec<CFunction>, &[u8]), String> {
214 let Some(rest) = carried.strip_prefix(CARRIED_C_FUNCTIONS.as_slice()) else {
215 return Ok((vec![], carried));
216 };
217 let damaged = || "the executable's table of C functions is damaged".to_string();
218 let (length, rest) = rest.split_first_chunk::<8>().ok_or_else(damaged)?;
219 let (table, machine_code) = rest.split_at_checked(u64::from_le_bytes(*length) as usize).ok_or_else(damaged)?;
220 let table = std::str::from_utf8(table).map_err(|_| damaged())?;
221 let functions = table.lines().map(CFunction::of_line).collect::<Option<_>>().ok_or_else(damaged)?;
222 Ok((functions, machine_code))
223}