1use std::cell::RefCell;
21use std::collections::HashMap;
22use std::sync::Arc as Rc;
23
24use crate::nan_value::{Arena, NanValue};
25use crate::value::{RuntimeError, Value, aver_display};
26
27thread_local! {
28 static CAPTURE: RefCell<Option<(Vec<u8>, Vec<u8>)>> = const { RefCell::new(None) };
33}
34
35pub fn capture_output<F, R>(f: F) -> (R, Vec<u8>, Vec<u8>)
42where
43 F: FnOnce() -> R,
44{
45 CAPTURE.with(|c| {
46 let mut slot = c.borrow_mut();
47 if slot.is_some() {
48 panic!("capture_output: nested capture is not supported");
49 }
50 *slot = Some((Vec::new(), Vec::new()));
51 });
52 let result = f();
53 let (out, err) = CAPTURE.with(|c| c.borrow_mut().take()).unwrap_or_default();
54 (result, out, err)
55}
56
57pub fn write_stdout_str(s: &str) {
63 write_stdout(s);
64}
65
66pub fn write_stderr_plain_str(s: &str) {
69 write_stderr_plain(s);
70}
71
72fn write_stdout(s: &str) {
73 CAPTURE.with(|c| {
74 if let Some((out, _)) = c.borrow_mut().as_mut() {
75 out.extend_from_slice(s.as_bytes());
76 out.push(b'\n');
77 } else {
78 println!("{}", s);
79 }
80 });
81}
82
83fn write_stderr_plain(s: &str) {
84 CAPTURE.with(|c| {
85 if let Some((_, err)) = c.borrow_mut().as_mut() {
86 err.extend_from_slice(s.as_bytes());
87 err.push(b'\n');
88 } else {
89 eprintln!("{}", s);
90 }
91 });
92}
93
94fn write_stderr_warn(s: &str) {
95 CAPTURE.with(|c| {
96 if let Some((_, err)) = c.borrow_mut().as_mut() {
97 err.extend_from_slice(b"[warn] ");
98 err.extend_from_slice(s.as_bytes());
99 err.push(b'\n');
100 } else {
101 eprintln!("[warn] {}", s);
102 }
103 });
104}
105
106pub fn register(global: &mut HashMap<String, Value>) {
107 let mut members = HashMap::new();
108 for method in &["print", "error", "warn", "readLine"] {
109 members.insert(
110 method.to_string(),
111 Value::Builtin(format!("Console.{}", method)),
112 );
113 }
114 global.insert(
115 "Console".to_string(),
116 Value::Namespace {
117 name: "Console".to_string(),
118 members,
119 },
120 );
121}
122
123pub const DECLARED_EFFECTS: &[&str] = &[
124 "Console.print",
125 "Console.error",
126 "Console.warn",
127 "Console.readLine",
128];
129
130pub fn effects(name: &str) -> &'static [&'static str] {
131 match name {
132 "Console.print" => &["Console.print"],
133 "Console.error" => &["Console.error"],
134 "Console.warn" => &["Console.warn"],
135 "Console.readLine" => &["Console.readLine"],
136 _ => &[],
137 }
138}
139
140pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
142 match name {
143 "Console.print" => Some(one_msg(name, args, write_stdout)),
144 "Console.error" => Some(one_msg(name, args, write_stderr_plain)),
145 "Console.warn" => Some(one_msg(name, args, write_stderr_warn)),
146 "Console.readLine" => Some(read_line(args)),
147 _ => None,
148 }
149}
150
151fn one_msg(name: &str, args: &[Value], emit: impl Fn(&str)) -> Result<Value, RuntimeError> {
154 if args.len() != 1 {
155 return Err(RuntimeError::Error(format!(
156 "{}() takes 1 argument, got {}",
157 name,
158 args.len()
159 )));
160 }
161 if let Some(s) = aver_display(&args[0]) {
162 emit(&s);
163 }
164 Ok(Value::Unit)
165}
166
167fn read_line(args: &[Value]) -> Result<Value, RuntimeError> {
168 if !args.is_empty() {
169 return Err(RuntimeError::Error(format!(
170 "Console.readLine() takes 0 arguments, got {}",
171 args.len()
172 )));
173 }
174 match aver_rt::read_line() {
175 Ok(line) => Ok(Value::Ok(Box::new(Value::Str(line)))),
176 Err(e) => Ok(Value::Err(Box::new(Value::Str(e)))),
177 }
178}
179
180pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
183 let methods = &["print", "error", "warn", "readLine"];
184 let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
185 for method in methods {
186 let idx = arena.push_builtin(&format!("Console.{}", method));
187 members.push((Rc::from(*method), NanValue::new_builtin(idx)));
188 }
189 let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
190 name: Rc::from("Console"),
191 members,
192 });
193 global.insert("Console".to_string(), NanValue::new_namespace(ns_idx));
194}
195
196pub fn call_nv(
197 name: &str,
198 args: &[NanValue],
199 arena: &mut Arena,
200) -> Option<Result<NanValue, RuntimeError>> {
201 match name {
202 "Console.print" => Some(one_msg_nv(name, args, arena, write_stdout)),
203 "Console.error" => Some(one_msg_nv(name, args, arena, write_stderr_plain)),
204 "Console.warn" => Some(one_msg_nv(name, args, arena, write_stderr_warn)),
205 "Console.readLine" => Some(read_line_nv(args, arena)),
206 _ => None,
207 }
208}
209
210fn one_msg_nv(
211 name: &str,
212 args: &[NanValue],
213 arena: &mut Arena,
214 emit: impl Fn(&str),
215) -> Result<NanValue, RuntimeError> {
216 if args.len() != 1 {
217 return Err(RuntimeError::Error(format!(
218 "{}() takes 1 argument, got {}",
219 name,
220 args.len()
221 )));
222 }
223 if let Some(s) = args[0].display(arena) {
224 emit(&s);
225 }
226 Ok(NanValue::UNIT)
227}
228
229fn read_line_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
230 if !args.is_empty() {
231 return Err(RuntimeError::Error(format!(
232 "Console.readLine() takes 0 arguments, got {}",
233 args.len()
234 )));
235 }
236 match aver_rt::read_line() {
237 Ok(line) => {
238 let inner = NanValue::new_string_value(&line, arena);
239 Ok(NanValue::new_ok_value(inner, arena))
240 }
241 Err(e) => {
242 let inner = NanValue::new_string_value(&e, arena);
243 Ok(NanValue::new_err_value(inner, arena))
244 }
245 }
246}