Skip to content

Commit bd92165

Browse files
committed
Add math module
1 parent ecd8d09 commit bd92165

3 files changed

Lines changed: 263 additions & 0 deletions

File tree

docs/reference.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ Explore the standard library functions.
7575

7676
---
7777

78+
## Module `math`
79+
80+
[fn abs(number: Int | Float): Int | Float](#fn-absnumber-int--float-int--float)<br>
81+
[fn ceil(number: Float): Float](#fn-ceilnumber-float-float)<br>
82+
[fn clamp(value: Int | Float, min: Int | Float, max: Int | Float): Int | Float](#fn-clampvalue-int--float-min-int--float-max-int--float-int--float)<br>
83+
[fn floor(number: Float): Float](#fn-floornumber-float-float)<br>
84+
[fn round(number: Float): Float](#fn-roundnumber-float-float)
85+
86+
---
87+
7888
## Module `strings`
7989

8090
[fn contains(string: String, substr: String): Bool](#fn-containsstring-string-substr-string-bool)<br>
@@ -247,6 +257,44 @@ Parses the given JSON string and returns a result record. The `error` field indi
247257

248258
---
249259

260+
## Module: `math`
261+
262+
## `fn abs(number: Int | Float): Int | Float`
263+
264+
Returns the absolute value of the given number. If the input is an integer, an integer is returned. If the input is a floating-point number, a floating-point number is returned.
265+
266+
---
267+
268+
## `fn ceil(number: Float): Float`
269+
270+
Returns the smallest integer value greater than or equal to the given floating-point number, as a floating-point value.
271+
272+
---
273+
274+
## `fn clamp(value: Int | Float, min: Int | Float, max: Int | Float): Int | Float`
275+
276+
Restricts the given value to be within the inclusive range defined by `min` and `max`.
277+
278+
If the value is less than `min`, `min` is returned.
279+
If the value is greater than `max`, `max` is returned.
280+
Otherwise, the original value is returned.
281+
282+
All arguments must be of the same type (either all integers or all floating-point numbers).
283+
284+
---
285+
286+
## `fn floor(number: Float): Float`
287+
288+
Returns the largest integer value less than or equal to the given floating-point number, as a floating-point value.
289+
290+
---
291+
292+
## `fn round(number: Float): Float`
293+
294+
Returns the nearest integer value to the given floating-point number, as a floating-point value. Halfway cases are rounded away from zero.
295+
296+
---
297+
250298
## Module: `strings`
251299

252300
## `fn contains(string: String, substr: String): Bool`

src/builtin.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod fs;
44
mod http;
55
mod io;
66
mod json;
7+
mod math;
78
mod strings;
89

910
use std::cell::RefCell;
@@ -78,6 +79,7 @@ pub fn import(args: Vec<Value>) -> Result<Value, RuntimeError> {
7879
"std/http" => http::module(),
7980
"std/io" => io::module(),
8081
"std/json" => json::module(),
82+
"std/math" => math::module(),
8183
"std/strings" => strings::module(),
8284

8385
// Handle cases where the imported module is a file stored on the file system.

src/builtin/math.rs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
use std::collections::HashMap;
2+
3+
use crate::runtime::{RuntimeError, Value};
4+
5+
pub fn module() -> Result<Value, RuntimeError> {
6+
let mut map = HashMap::new();
7+
8+
map.insert("abs".to_string(), Value::BuiltinFunction(abs));
9+
map.insert("ceil".to_string(), Value::BuiltinFunction(ceil));
10+
map.insert("clamp".to_string(), Value::BuiltinFunction(clamp));
11+
map.insert("floor".to_string(), Value::BuiltinFunction(floor));
12+
map.insert("round".to_string(), Value::BuiltinFunction(round));
13+
14+
Ok(Value::Record(map))
15+
}
16+
17+
pub fn abs(args: Vec<Value>) -> Result<Value, RuntimeError> {
18+
if args.len() != 1 {
19+
return Err(RuntimeError::Arity {
20+
expected: 1,
21+
got: args.len(),
22+
});
23+
}
24+
25+
match args.first() {
26+
Some(Value::Float(number)) => Ok(Value::Float(number.abs())),
27+
Some(Value::Int(number)) => Ok(Value::Int(number.abs())),
28+
29+
None => Err(RuntimeError::Arity {
30+
expected: 1,
31+
got: 0,
32+
}),
33+
34+
other => Err(RuntimeError::TypeError {
35+
expected: "float/int",
36+
got: format!("{:?}", other),
37+
}),
38+
}
39+
}
40+
41+
pub fn ceil(args: Vec<Value>) -> Result<Value, RuntimeError> {
42+
if args.len() != 1 {
43+
return Err(RuntimeError::Arity {
44+
expected: 1,
45+
got: args.len(),
46+
});
47+
}
48+
49+
match args.first() {
50+
Some(Value::Float(number)) => Ok(Value::Float(number.ceil())),
51+
52+
None => Err(RuntimeError::Arity {
53+
expected: 1,
54+
got: 0,
55+
}),
56+
57+
other => Err(RuntimeError::TypeError {
58+
expected: "float",
59+
got: format!("{:?}", other),
60+
}),
61+
}
62+
}
63+
64+
pub fn clamp(args: Vec<Value>) -> Result<Value, RuntimeError> {
65+
if args.len() != 3 {
66+
return Err(RuntimeError::Arity {
67+
expected: 3,
68+
got: args.len(),
69+
});
70+
}
71+
72+
match args.first() {
73+
Some(Value::Float(v)) => {
74+
let min = match args.iter().nth(1) {
75+
Some(Value::Float(min)) => min,
76+
77+
Some(other) => {
78+
return Err(RuntimeError::TypeError {
79+
expected: "float",
80+
got: format!("{:?}", other),
81+
});
82+
}
83+
84+
None => {
85+
return Err(RuntimeError::Arity {
86+
expected: 3,
87+
got: 0,
88+
});
89+
}
90+
};
91+
92+
let max = match args.iter().nth(2) {
93+
Some(Value::Float(max)) => max,
94+
95+
Some(other) => {
96+
return Err(RuntimeError::TypeError {
97+
expected: "float",
98+
got: format!("{:?}", other),
99+
});
100+
}
101+
102+
None => {
103+
return Err(RuntimeError::Arity {
104+
expected: 3,
105+
got: 0,
106+
});
107+
}
108+
};
109+
110+
return Ok(Value::Float(v.clamp(*min, *max)));
111+
}
112+
113+
Some(Value::Int(v)) => {
114+
let min = match args.iter().nth(1) {
115+
Some(Value::Int(min)) => min,
116+
117+
Some(other) => {
118+
return Err(RuntimeError::TypeError {
119+
expected: "int",
120+
got: format!("{:?}", other),
121+
});
122+
}
123+
124+
None => {
125+
return Err(RuntimeError::Arity {
126+
expected: 3,
127+
got: 0,
128+
});
129+
}
130+
};
131+
132+
let max = match args.iter().nth(2) {
133+
Some(Value::Int(max)) => max,
134+
135+
Some(other) => {
136+
return Err(RuntimeError::TypeError {
137+
expected: "int",
138+
got: format!("{:?}", other),
139+
});
140+
}
141+
142+
None => {
143+
return Err(RuntimeError::Arity {
144+
expected: 3,
145+
got: 0,
146+
});
147+
}
148+
};
149+
150+
return Ok(Value::Int(*v.clamp(min, max)));
151+
}
152+
153+
Some(other) => {
154+
return Err(RuntimeError::TypeError {
155+
expected: "float/int",
156+
got: format!("{:?}", other),
157+
});
158+
}
159+
160+
None => {
161+
return Err(RuntimeError::Arity {
162+
expected: 3,
163+
got: 0,
164+
});
165+
}
166+
}
167+
}
168+
169+
pub fn floor(args: Vec<Value>) -> Result<Value, RuntimeError> {
170+
if args.len() != 1 {
171+
return Err(RuntimeError::Arity {
172+
expected: 1,
173+
got: args.len(),
174+
});
175+
}
176+
177+
match args.first() {
178+
Some(Value::Float(number)) => Ok(Value::Float(number.floor())),
179+
180+
None => Err(RuntimeError::Arity {
181+
expected: 1,
182+
got: 0,
183+
}),
184+
185+
other => Err(RuntimeError::TypeError {
186+
expected: "float",
187+
got: format!("{:?}", other),
188+
}),
189+
}
190+
}
191+
192+
pub fn round(args: Vec<Value>) -> Result<Value, RuntimeError> {
193+
if args.len() != 1 {
194+
return Err(RuntimeError::Arity {
195+
expected: 1,
196+
got: args.len(),
197+
});
198+
}
199+
200+
match args.first() {
201+
Some(Value::Float(number)) => Ok(Value::Float(number.round())),
202+
203+
None => Err(RuntimeError::Arity {
204+
expected: 1,
205+
got: 0,
206+
}),
207+
208+
other => Err(RuntimeError::TypeError {
209+
expected: "float",
210+
got: format!("{:?}", other),
211+
}),
212+
}
213+
}

0 commit comments

Comments
 (0)