1use std::{
2 convert::TryInto, env, error::Error, ffi::OsString, fmt, fs, process::Command, time::Instant,
3};
4
5use crate::{Class, Instance, NativeFun, Value, VM};
6
7type StaticNativeFun = fn(&[Value]) -> Result<Value, Box<dyn Error>>;
8
9impl VM {
10 pub fn add_std_globals(&mut self) {
11 self.define_closure_value("clock", create_clock_fn());
13 self.define_fn_value("readFile", read_file);
14 self.define_fn_value("writeFile", write_file);
15 self.define_fn_value("shell", shell);
16 self.define_fn_value("env", environment_var);
17 self.define_fn_value("setEnv", set_environment_var);
18
19 self.add_arguments_instance();
21 }
22
23 fn define_fn_value(&mut self, name: &'static str, f: StaticNativeFun) {
24 let fn_value = Value::NativeFun(self.alloc(Box::new(f)));
25 self.define_global(name, fn_value);
26 }
27
28 fn define_closure_value(&mut self, name: &'static str, c: NativeFun) {
29 let fn_value = Value::NativeFun(self.alloc(c));
30 self.define_global(name, fn_value);
31 }
32
33 pub fn add_arguments_instance(&mut self) {
34 let args = env::args_os().collect::<Vec<_>>();
35 let count = args.len();
36
37 let get_arg = Box::new(move |a: &[Value]| match a {
38 [Value::Number(n)] if n.is_finite() && n.is_sign_positive() => Ok(args
39 .get(n.round().abs() as usize)
40 .map_or(Value::Nil, |val: &OsString| {
41 val.to_string_lossy().as_ref().into()
42 })),
43 [Value::Number(_)] => Err(Box::new(LoxStdErr::InvalidArgument {
44 expected: "a positive, finite number as argument index",
45 }) as Box<dyn Error>),
46 _ if a.len() != 1 => Err(Box::new(LoxStdErr::ArityMismatch {
47 expected: 1,
48 got: a.len().try_into().unwrap_or(255),
49 }) as Box<dyn Error>),
50 _ => Err(Box::new(LoxStdErr::ArgumentTypes {
51 expected: "a number (command line argument index)",
52 }) as Box<dyn Error>),
53 });
54
55 let mut arg_class = Class::new(&"Arguments");
56 arg_class
57 .methods
58 .insert("get".into(), Value::NativeFun(self.alloc(get_arg)));
59
60 let mut arg_inst = Instance::new(self.alloc(arg_class));
61 arg_inst
62 .fields
63 .insert("count".into(), Value::Number(count as f64));
64 let arg_value = Value::Instance(self.alloc(arg_inst));
65 self.define_global("arguments", arg_value);
66 }
67}
68
69#[derive(Debug)]
70enum LoxStdErr {
71 ArityMismatch { expected: u8, got: u8 },
72 ArgumentTypes { expected: &'static str },
73 InvalidArgument { expected: &'static str },
74}
75
76impl fmt::Display for LoxStdErr {
77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78 match self {
79 Self::ArityMismatch { expected, got } => {
80 write!(f, "expected {} arguments, got {}", expected, got)
81 }
82 Self::ArgumentTypes { expected } => {
83 write!(f, "wrong argument type(s) supplied: expected {}", expected)
84 }
85 Self::InvalidArgument { expected } => write!(
86 f,
87 "invalid argument value(s) supplied: expected {}",
88 expected
89 ),
90 }
91 }
92}
93
94impl Error for LoxStdErr {}
95
96#[must_use]
101pub fn create_clock_fn() -> NativeFun {
102 let start_time = Instant::now();
103 let clock_cls = move |_: &[Value]| Ok(start_time.elapsed().as_secs_f64().into());
104 Box::new(clock_cls) as NativeFun
105}
106
107pub fn read_file(args: &[Value]) -> Result<Value, Box<dyn Error>> {
116 match args {
117 [Value::r#String(s)] => Ok(fs::read_to_string(s.as_ref())?.into()),
118 _ if args.len() != 1 => Err(Box::new(LoxStdErr::ArityMismatch {
119 expected: 1,
120 got: args.len().try_into().unwrap_or(255),
121 })),
122 _ => Err(Box::new(LoxStdErr::ArgumentTypes {
123 expected: "string for filename",
124 })),
125 }
126}
127
128pub fn write_file(args: &[Value]) -> Result<Value, Box<dyn Error>> {
140 match args {
141 [Value::r#String(file_name), Value::r#String(contents)] => {
142 fs::write(file_name.as_ref(), contents.as_ref())?;
143 Ok(Value::Nil)
144 }
145 _ if args.len() != 2 => Err(Box::new(LoxStdErr::ArityMismatch {
146 expected: 2,
147 got: args.len().try_into().unwrap_or(255),
148 })),
149 _ => Err(Box::new(LoxStdErr::ArgumentTypes {
150 expected: "string for filename",
151 })),
152 }
153}
154
155pub fn shell(args: &[Value]) -> Result<Value, Box<dyn Error>> {
161 match args {
162 [Value::r#String(cmd)] => {
163 let output = if cfg!(target_os = "windows") {
164 Command::new("cmd").args(&["/C", &cmd]).output()?
165 } else {
166 Command::new("sh").args(&["-c", &cmd]).output()?
167 };
168
169 Ok(String::from_utf8_lossy(&output.stdout).as_ref().into())
170 }
171 _ if args.len() != 1 => Err(Box::new(LoxStdErr::ArityMismatch {
172 expected: 1,
173 got: args.len().try_into().unwrap_or(255),
174 })),
175 _ => Err(Box::new(LoxStdErr::ArgumentTypes {
176 expected: "string for shell command",
177 })),
178 }
179}
180
181pub fn environment_var(args: &[Value]) -> Result<Value, Box<dyn Error>> {
187 match args {
188 [Value::r#String(name)] => {
189 Ok(env::var_os(name.as_ref())
190 .map_or(Value::Nil, |s| s.to_string_lossy().as_ref().into()))
191 }
192 _ if args.len() != 1 => Err(Box::new(LoxStdErr::ArityMismatch {
193 expected: 1,
194 got: args.len().try_into().unwrap_or(255),
195 })),
196 _ => Err(Box::new(LoxStdErr::ArgumentTypes {
197 expected: "name of environment variable",
198 })),
199 }
200}
201
202pub fn set_environment_var(args: &[Value]) -> Result<Value, Box<dyn Error>> {
204 match args {
205 [Value::r#String(name), Value::r#String(value)] => {
206 env::set_var(name.as_ref(), value.as_ref());
207 Ok(Value::Nil)
208 }
209 _ if args.len() != 2 => Err(Box::new(LoxStdErr::ArityMismatch {
210 expected: 2,
211 got: args.len().try_into().unwrap_or(255),
212 })),
213 _ => Err(Box::new(LoxStdErr::ArgumentTypes {
214 expected: "name of environment variable",
215 })),
216 }
217}