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/// Each method requires its own exact effect (`Console.print`, `Console.error`, etc.).
10use std::collections::HashMap;
11use std::sync::Arc as Rc;
12
13use crate::nan_value::{Arena, NanValue};
14use crate::value::{RuntimeError, Value, aver_display};
15
16pub fn register(global: &mut HashMap<String, Value>) {
17    let mut members = HashMap::new();
18    for method in &["print", "error", "warn", "readLine"] {
19        members.insert(
20            method.to_string(),
21            Value::Builtin(format!("Console.{}", method)),
22        );
23    }
24    global.insert(
25        "Console".to_string(),
26        Value::Namespace {
27            name: "Console".to_string(),
28            members,
29        },
30    );
31}
32
33pub const DECLARED_EFFECTS: &[&str] = &[
34    "Console.print",
35    "Console.error",
36    "Console.warn",
37    "Console.readLine",
38];
39
40pub fn effects(name: &str) -> &'static [&'static str] {
41    match name {
42        "Console.print" => &["Console.print"],
43        "Console.error" => &["Console.error"],
44        "Console.warn" => &["Console.warn"],
45        "Console.readLine" => &["Console.readLine"],
46        _ => &[],
47    }
48}
49
50/// Returns `Some(result)` when `name` is owned by this service, `None` otherwise.
51pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
52    match name {
53        "Console.print" => Some(one_msg(name, args, |s| {
54            println!("{}", s);
55        })),
56        "Console.error" => Some(one_msg(name, args, |s| {
57            eprintln!("{}", s);
58        })),
59        "Console.warn" => Some(one_msg(name, args, |s| {
60            eprintln!("[warn] {}", s);
61        })),
62        "Console.readLine" => Some(read_line(args)),
63        _ => None,
64    }
65}
66
67// ─── Private helpers ──────────────────────────────────────────────────────────
68
69fn one_msg(name: &str, args: &[Value], emit: impl Fn(&str)) -> Result<Value, RuntimeError> {
70    if args.len() != 1 {
71        return Err(RuntimeError::Error(format!(
72            "{}() takes 1 argument, got {}",
73            name,
74            args.len()
75        )));
76    }
77    if let Some(s) = aver_display(&args[0]) {
78        emit(&s);
79    }
80    Ok(Value::Unit)
81}
82
83fn read_line(args: &[Value]) -> Result<Value, RuntimeError> {
84    if !args.is_empty() {
85        return Err(RuntimeError::Error(format!(
86            "Console.readLine() takes 0 arguments, got {}",
87            args.len()
88        )));
89    }
90    match aver_rt::read_line() {
91        Ok(line) => Ok(Value::Ok(Box::new(Value::Str(line)))),
92        Err(e) => Ok(Value::Err(Box::new(Value::Str(e)))),
93    }
94}
95
96// ─── NanValue-native API ─────────────────────────────────────────────────────
97
98pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
99    let methods = &["print", "error", "warn", "readLine"];
100    let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
101    for method in methods {
102        let idx = arena.push_builtin(&format!("Console.{}", method));
103        members.push((Rc::from(*method), NanValue::new_builtin(idx)));
104    }
105    let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
106        name: Rc::from("Console"),
107        members,
108    });
109    global.insert("Console".to_string(), NanValue::new_namespace(ns_idx));
110}
111
112pub fn call_nv(
113    name: &str,
114    args: &[NanValue],
115    arena: &mut Arena,
116) -> Option<Result<NanValue, RuntimeError>> {
117    match name {
118        "Console.print" => Some(one_msg_nv(name, args, arena, |s| {
119            println!("{}", s);
120        })),
121        "Console.error" => Some(one_msg_nv(name, args, arena, |s| {
122            eprintln!("{}", s);
123        })),
124        "Console.warn" => Some(one_msg_nv(name, args, arena, |s| {
125            eprintln!("[warn] {}", s);
126        })),
127        "Console.readLine" => Some(read_line_nv(args, arena)),
128        _ => None,
129    }
130}
131
132fn one_msg_nv(
133    name: &str,
134    args: &[NanValue],
135    arena: &mut Arena,
136    emit: impl Fn(&str),
137) -> Result<NanValue, RuntimeError> {
138    if args.len() != 1 {
139        return Err(RuntimeError::Error(format!(
140            "{}() takes 1 argument, got {}",
141            name,
142            args.len()
143        )));
144    }
145    if let Some(s) = args[0].display(arena) {
146        emit(&s);
147    }
148    Ok(NanValue::UNIT)
149}
150
151fn read_line_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
152    if !args.is_empty() {
153        return Err(RuntimeError::Error(format!(
154            "Console.readLine() takes 0 arguments, got {}",
155            args.len()
156        )));
157    }
158    match aver_rt::read_line() {
159        Ok(line) => {
160            let inner = NanValue::new_string_value(&line, arena);
161            Ok(NanValue::new_ok_value(inner, arena))
162        }
163        Err(e) => {
164            let inner = NanValue::new_string_value(&e, arena);
165            Ok(NanValue::new_err_value(inner, arena))
166        }
167    }
168}