Skip to main content

aver/services/
console.rs

1/// Console service — terminal I/O.
2///
3/// Methods:
4///   Console.print(msg)    — print to stdout (same as top-level `print`)
5///   Console.error(msg)    — print to stderr (no colour, raw message)
6///   Console.warn(msg)     — print to stderr prefixed with "[warn] "
7///   Console.readLine()    — read one line from stdin; Ok(line) or Err("EOF")
8///
9/// All methods require `! [Console]`.
10use std::collections::HashMap;
11
12use crate::value::{RuntimeError, Value, aver_display};
13
14pub fn register(global: &mut HashMap<String, Value>) {
15    let mut members = HashMap::new();
16    for method in &["print", "error", "warn", "readLine"] {
17        members.insert(
18            method.to_string(),
19            Value::Builtin(format!("Console.{}", method)),
20        );
21    }
22    global.insert(
23        "Console".to_string(),
24        Value::Namespace {
25            name: "Console".to_string(),
26            members,
27        },
28    );
29}
30
31pub fn effects(name: &str) -> &'static [&'static str] {
32    match name {
33        "Console.print" | "Console.error" | "Console.warn" | "Console.readLine" => &["Console"],
34        _ => &[],
35    }
36}
37
38/// Returns `Some(result)` when `name` is owned by this service, `None` otherwise.
39pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
40    match name {
41        "Console.print" => Some(one_msg(name, &args, |s| {
42            println!("{}", s);
43        })),
44        "Console.error" => Some(one_msg(name, &args, |s| {
45            eprintln!("{}", s);
46        })),
47        "Console.warn" => Some(one_msg(name, &args, |s| {
48            eprintln!("[warn] {}", s);
49        })),
50        "Console.readLine" => Some(read_line(&args)),
51        _ => None,
52    }
53}
54
55// ─── Private helpers ──────────────────────────────────────────────────────────
56
57fn one_msg(name: &str, args: &[Value], emit: impl Fn(&str)) -> Result<Value, RuntimeError> {
58    if args.len() != 1 {
59        return Err(RuntimeError::Error(format!(
60            "{}() takes 1 argument, got {}",
61            name,
62            args.len()
63        )));
64    }
65    if let Some(s) = aver_display(&args[0]) {
66        emit(&s);
67    }
68    Ok(Value::Unit)
69}
70
71fn read_line(args: &[Value]) -> Result<Value, RuntimeError> {
72    if !args.is_empty() {
73        return Err(RuntimeError::Error(format!(
74            "Console.readLine() takes 0 arguments, got {}",
75            args.len()
76        )));
77    }
78    let mut line = String::new();
79    match std::io::stdin().read_line(&mut line) {
80        Ok(0) => Ok(Value::Err(Box::new(Value::Str("EOF".to_string())))),
81        Ok(_) => {
82            // Strip trailing newline
83            if line.ends_with('\n') {
84                line.pop();
85            }
86            if line.ends_with('\r') {
87                line.pop();
88            }
89            Ok(Value::Ok(Box::new(Value::Str(line))))
90        }
91        Err(e) => Ok(Value::Err(Box::new(Value::Str(e.to_string())))),
92    }
93}