Skip to main content

conlang/
builtin.rs

1use crate::inline_modules;
2use cl_ast::{At, Expr};
3use cl_interpret::{
4    builtin::builtins, convalue::ConValue, env::Environment, error::Error, interpret::Interpret,
5};
6use cl_lexer::Lexer;
7use cl_parser::Parser;
8use std::{
9    fs,
10    io::{Write, stdout},
11};
12
13const PREAMBLE: &str = include_str!("preamble.cl");
14
15pub fn get_env() -> Environment {
16    let mut env = Environment::new();
17    env.add_builtins(&builtins! {
18        /// Lexes, parses, and evaluates an expression in the current env
19        fn eval(string) @env {
20            let string = match string.dereference_in(env)? {
21                ConValue::Str(string) => string.to_ref(),
22                ConValue::String(string) => string.as_str(),
23                _ => Err(Error::TypeError("string", string.typename()))?
24            };
25
26            match Parser::new(Lexer::new("eval".into(), string)).parse::<At<Expr>>(0).map(inline_modules) {
27                Err(e) => Ok(ConValue::String(format!("{e}"))),
28                Ok(v) => v.interpret(env),
29            }
30        }
31
32        /// Puts a single character, flushing stdout
33        fn putchar(ConValue::Char(c)) {
34            let mut stdout = stdout().lock();
35            let _ = write!(stdout, "{c}");
36            let _ = stdout.flush();
37            Ok(ConValue::Empty)
38        }
39
40        /// Gets a line of input from stdin
41        fn get_line(prompt) @env {
42            let prompt = match prompt.dereference_in(env)? {
43                ConValue::Str(prompt) => prompt.to_ref(),
44                ConValue::String(prompt) => prompt.as_str(),
45                _ => Err(Error::TypeError("string", prompt.typename()))?,
46            };
47            match repline::Repline::new("", prompt, "").read() {
48                Ok(line) => Ok(ConValue::String(line)),
49                Err(repline::Error::CtrlD(line)) => Ok(ConValue::String(line)),
50                Err(repline::Error::CtrlC(_)) => Err(Error::Break(ConValue::Empty)),
51                Err(e) => Ok(ConValue::String(e.to_string())),
52            }
53        }
54
55        // TODO: low level file abstraction
56        fn read_file(path) @env {
57            let path = match path.dereference_in(env)? {
58                ConValue::Str(path) => path.to_ref(),
59                ConValue::String(path) => path.as_str(),
60                _ => Err(Error::TypeError("string", path.typename()))?,
61            };
62            fs::read_to_string(path).map_err(Error::BuiltinError)
63        }
64
65        fn write_file(path, data) @env {
66            let path = match path.dereference_in(env)? {
67                ConValue::Str(v) => v.to_ref(),
68                ConValue::String(v) => v.as_str(),
69                v => Err(Error::TypeError("string", v.typename()))?,
70            };
71            let data = match data.dereference_in(env)? {
72                ConValue::Str(v) => v.to_ref(),
73                ConValue::String(v) => v.as_str(),
74                v => Err(Error::TypeError("string", v.typename()))?,
75            };
76            fs::write(path, data).map_err(Error::BuiltinError)
77        }
78    });
79
80    if let Ok(code) = Parser::new(Lexer::new("".into(), PREAMBLE)).parse::<At<Expr>>(0) {
81        code.interpret(&mut env).expect("PREAMBLE should not fail");
82    }
83    env
84}