1use std::fmt::Write;
2use std::panic::{RefUnwindSafe, UnwindSafe};
3use std::{collections::HashMap, sync::Arc};
4
5use super::{EvalContext, Module, ModuleData, ModuleDataMap, StaticValue, Type, Value};
6
7pub struct Console {
40 callback: Arc<LogCallback>,
41}
42
43pub type LogCallback = dyn Fn(String) + Send + Sync + UnwindSafe + RefUnwindSafe;
45
46impl Module for Console {
47 fn get_name(&self) -> &'static str {
48 "console"
49 }
50
51 fn get_static_values(&self) -> HashMap<&'static str, StaticValue> {
52 [
53 (
54 "log",
55 StaticValue::function(
56 Self::log,
57 vec![
58 vec![Type::Bytes],
59 vec![Type::Bytes, Type::Bytes],
60 vec![Type::Integer],
61 vec![Type::Bytes, Type::Integer],
62 vec![Type::Float],
63 vec![Type::Bytes, Type::Float],
64 ],
65 Type::Integer,
66 ),
67 ),
68 (
69 "hex",
70 StaticValue::function(
71 Self::hex,
72 vec![vec![Type::Integer], vec![Type::Bytes, Type::Integer]],
73 Type::Integer,
74 ),
75 ),
76 ]
77 .into()
78 }
79
80 fn setup_new_scan(&self, data_map: &mut ModuleDataMap) {
81 data_map.insert::<Self>(PrivateData {
82 callback: Arc::clone(&self.callback),
83 });
84 }
85}
86
87pub struct PrivateData {
88 callback: Arc<LogCallback>,
89}
90
91impl ModuleData for Console {
92 type PrivateData = PrivateData;
93 type UserData = ConsoleData;
94}
95
96pub struct ConsoleData {
102 callback: Box<LogCallback>,
103}
104
105impl ConsoleData {
106 pub fn new<T>(callback: T) -> Self
108 where
109 T: Fn(String) + Send + Sync + UnwindSafe + RefUnwindSafe + 'static,
110 {
111 Self {
112 callback: Box::new(callback),
113 }
114 }
115}
116
117impl Console {
118 #[must_use]
123 pub fn with_callback<T>(callback: T) -> Self
124 where
125 T: Fn(String) + Send + Sync + UnwindSafe + RefUnwindSafe + 'static,
126 {
127 Self {
128 callback: Arc::new(callback),
129 }
130 }
131
132 fn log(ctx: &mut EvalContext, args: Vec<Value>) -> Option<Value> {
133 let mut args = args.into_iter();
134 let mut res = String::new();
135 add_value(args.next()?, &mut res)?;
136 if let Some(arg) = args.next() {
137 add_value(arg, &mut res)?;
138 }
139
140 call_callback(ctx, res)?;
141
142 Some(Value::Integer(1))
143 }
144
145 fn hex(ctx: &mut EvalContext, args: Vec<Value>) -> Option<Value> {
146 let mut args = args.into_iter();
147 let res = match args.next()? {
148 Value::Integer(v) => format!("0x{v:x}"),
149 value => {
150 let mut res = String::new();
151 add_value(value, &mut res)?;
152 let v: i64 = args.next()?.try_into().ok()?;
153 write!(&mut res, "0x{v:x}").ok()?;
154 res
155 }
156 };
157
158 call_callback(ctx, res)?;
159
160 Some(Value::Integer(1))
161 }
162}
163
164fn call_callback(ctx: &EvalContext, log: String) -> Option<()> {
165 if let Some(data) = ctx.module_data.get_user_data::<Console>() {
167 (data.callback)(log);
168 } else {
169 let data = ctx.module_data.get::<Console>()?;
171 (data.callback)(log);
172 }
173 Some(())
174}
175
176fn add_value(value: Value, out: &mut String) -> Option<()> {
177 match value {
178 Value::Integer(v) => write!(out, "{v}").ok(),
179 Value::Float(v) => write!(out, "{v}").ok(),
180 Value::Bytes(v) => {
181 for byte in v {
182 for b in std::ascii::escape_default(byte) {
183 out.push(char::from(b));
184 }
185 }
186 Some(())
187 }
188 _ => None,
189 }
190}