1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
use crate::prelude::*;
use crate::state::Directory;
functions! {
/// Evaluates all given arguments and returns the atom the last argument evaluated to.
/// If no arguments are given, `null` is returned.
///
/// Every program is implicitly wrapped in a call to this function.
///
/// This function has an alias: `run`.
"_"(_) => |state, args| {
if args.is_empty() {
Ok(Atom::Null)
} else {
for arg in &args[0..args.len() - 1] {
arg.eval(state)?;
}
args[args.len() - 1].eval(state)
}
}
/// Assigns the second argument to a variable named like the first argument.
///
/// This function has an alias: `assign`.
"="(2) => |state, args| {
let var = args[0].variable("invalid assignment: tried to assign to a non-variable", state)?;
let value = args[1].eval(state)?;
state.storage.insert(var, value);
Ok(Atom::Null)
}
/// Evaluates the first argument as a boolean.
/// If it evaluates to true, the second argument is evaluated and returned.
/// If it evaluates to false, the third argument is evaluated and returned instead.
"ifelse"(3) => |state, args| {
Ok(if args[0].eval_bool(state)? {
args[1].eval(state)?
} else {
args[2].eval(state)?
})
}
/// Repeatedly evaluates the first argument as a boolean.
/// If it evaluates to true, the second argument is evaluated and the same steps begin again.
/// If it evaluates to false, the loop ends and `null` is returned.
"while"(2) => |state, args| {
while args[0].eval_bool(state)? {
args[1].eval(state)?;
}
Ok(Atom::Null)
}
/// Raises an exception.
/// The first argument is a string that describes the error kind.
/// The second argument is a string error message.
///
/// The error kind should be a captialized word.
/// When displaying the error kind, `Error` will be appended implicitly, so the error kind given
/// here should not end in `Error`, `Exception` or similar.
"error"(2) => |state, args| {
let kind = args[0].eval_as_string(state)?;
let msg = args[1].eval_as_string(state)?;
Err(state.raise(kind, msg))
}
/// Evaluates the given argument and terminates the program directly.
/// The program will return the given value as its final result.
///
/// If the argument causes an exception, it is not returned directly
/// and the exception may still be caught with `try_except`.
///
/// If `exit` is reached via an `import`-ed module, it will stop the main program too.
"exit"(1) => |state, args| {
state.exit_unwind_value = Some(args[0].eval(state)?);
Ok(Atom::Null)
}
/// Evaluates the given argument as a string, then treats this string as Regulus code and executes it.
/// Returns the result of that program.
///
/// Variables defined inside the evaluated code are not visible outside of the `eval` invocation.
///
/// TODO: think about imports, test them
"eval"(1) => |state, args| {
let code = args[0].eval_as_string(state)?;
let mut eval_state = State::new().with_code(code);
eval_state.file_directory = Directory::FromEval;
eval_state.run()
}
/// Defines a new variable as global and assigns it the given value.
"global"(2) => |state, args| {
let var = args[0].variable("`global(2)` expects a variable argument", state)?;
let atom = args[1].eval(state)?;
state.storage.add_global(var, atom);
Ok(Atom::Null)
}
/// Executes the first argument. If it raises an uncaught exception, runs the second argument.
/// This optionally takes an identifier as a third argument. If it is passed, it will be
/// assigned the stringified exception message.
///
/// If the second argument also throws an exception, it will not be caught by this call and
/// propagate further.
///
/// This returns what the first argument evaluates to (if successful),
/// otherwise it returns the eval of the second arg.
"try_except"(_) => |state, args| {
match args {
[body, fallback] => {
match body.eval(state) {
Ok(val) => Ok(val),
Err(_) => fallback.eval(state),
}
}
[body, fallback, exception_var] => {
let exc_var = exception_var.variable("invalid exception variable given to `try_except`", state)?;
match body.eval(state) {
Ok(val) => Ok(val),
Err(e) => {
state.storage.insert(exc_var, Atom::new_string(&e.to_string()));
fallback.eval(state)
},
}
}
_ => raise!(
state,
"Argument",
"invalid number of args for `try_except`: should be 2 or 3, was {}",
args.len()
)
}
}
// TODO: invent some way for objects to define how they want to be printed.
// TODO: try then moving this to the STL
/// Evaluates the given arg and returns a string representation of it.
/// See the documentation of `string(1)` for a comparison of these two methods.
/// Note that the exact output format is not yet stable and may change, especially regarding
/// objects.
///
/// This is identical to the output of `write`.
"printable"(1) => |state, args| {
Ok(Atom::new_string(&args[0].eval(state)?.to_string()))
}
/// Iterates over the given list elements.
/// The first argument is the list, the second the loop variable name for each element and the
/// third is the body that will be run for each of these elements.
/// Afterwards, `null` is returned.
// TODO: argument order of seq and loop var is confusing
"for_in"(3) => |state, args| {
let v = args[0].eval_list(state)?;
let loop_var = args[1].variable("invalid loop variable given to `for_in`", state)?;
let loop_body = &args[2];
for el in v.iter() {
state.storage.insert(loop_var, el.clone());
loop_body.eval(state)?;
}
Ok(Atom::Null)
}
/// Returns the documentation string for a function.
"doc"(1) => |state, args| {
Ok(Atom::new_string(args[0].eval_function(state)?.doc()))
}
/// Returns the argument count for a function, or `null` if it has none.
"argc"(1) => |state, args| {
Ok(if let Some(argc) = args[0].eval_function(state)?.argc() {
Atom::int_from_rust_int(argc, state)?
} else {
Atom::Null
})
}
/// Takes no arguments and reads from stdin until a newline is entered.
/// Returns the read input, excluding the newline, as a string.
"input"(0) => |state, _| {
let mut input = String::new();
match state.stdin.read_line(&mut input) {
Ok(_) => Ok(Atom::new_string(
input.strip_suffix('\n').unwrap_or(&input)
)),
Err(error) => {
raise!(state, "Io", "error while reading input: {error}")
}
}
}
/// Evaluates the given argument and prints it to stdout, without any additional spaces or newline.
"write"(1) => |state, args| {
let s = args[0].eval(state)?.to_string();
state.write_to_stdout(&s);
Ok(Atom::Null)
}
/// Evaluates both arguments as booleans and performs short-circuiting OR on them.
"||"(2) => |state, args| Ok(Atom::Bool(
args[0].eval_bool(state)? || args[1].eval_bool(state)?
))
/// Evaluates both arguments as booleans and performs short-circuiting AND on them.
"&&"(2) => |state, args| Ok(Atom::Bool(
args[0].eval_bool(state)? && args[1].eval_bool(state)?
))
}