feat: expand standard library with Math module and new functions
- Add Math module: abs, min, max, sqrt, pow, floor, ceil, round - Add List functions: isEmpty, find, any, all, take, drop - Add String functions: startsWith, endsWith, toUpper, toLower, substring - Add 12 tests for new standard library functions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,31 @@ pub enum BuiltinFn {
|
|||||||
Versioned, // Create versioned value: versioned("TypeName", 1, value)
|
Versioned, // Create versioned value: versioned("TypeName", 1, value)
|
||||||
Migrate, // Migrate to version: migrate(versionedValue, targetVersion)
|
Migrate, // Migrate to version: migrate(versionedValue, targetVersion)
|
||||||
GetVersion, // Get version number: getVersion(versionedValue)
|
GetVersion, // Get version number: getVersion(versionedValue)
|
||||||
|
|
||||||
|
// Math operations
|
||||||
|
MathAbs,
|
||||||
|
MathMin,
|
||||||
|
MathMax,
|
||||||
|
MathSqrt,
|
||||||
|
MathPow,
|
||||||
|
MathFloor,
|
||||||
|
MathCeil,
|
||||||
|
MathRound,
|
||||||
|
|
||||||
|
// Additional List operations
|
||||||
|
ListIsEmpty,
|
||||||
|
ListFind,
|
||||||
|
ListAny,
|
||||||
|
ListAll,
|
||||||
|
ListTake,
|
||||||
|
ListDrop,
|
||||||
|
|
||||||
|
// Additional String operations
|
||||||
|
StringStartsWith,
|
||||||
|
StringEndsWith,
|
||||||
|
StringToUpper,
|
||||||
|
StringToLower,
|
||||||
|
StringSubstring,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runtime value
|
/// Runtime value
|
||||||
@@ -708,6 +733,15 @@ impl Interpreter {
|
|||||||
("length".to_string(), Value::Builtin(BuiltinFn::ListLength)),
|
("length".to_string(), Value::Builtin(BuiltinFn::ListLength)),
|
||||||
("get".to_string(), Value::Builtin(BuiltinFn::ListGet)),
|
("get".to_string(), Value::Builtin(BuiltinFn::ListGet)),
|
||||||
("range".to_string(), Value::Builtin(BuiltinFn::ListRange)),
|
("range".to_string(), Value::Builtin(BuiltinFn::ListRange)),
|
||||||
|
(
|
||||||
|
"isEmpty".to_string(),
|
||||||
|
Value::Builtin(BuiltinFn::ListIsEmpty),
|
||||||
|
),
|
||||||
|
("find".to_string(), Value::Builtin(BuiltinFn::ListFind)),
|
||||||
|
("any".to_string(), Value::Builtin(BuiltinFn::ListAny)),
|
||||||
|
("all".to_string(), Value::Builtin(BuiltinFn::ListAll)),
|
||||||
|
("take".to_string(), Value::Builtin(BuiltinFn::ListTake)),
|
||||||
|
("drop".to_string(), Value::Builtin(BuiltinFn::ListDrop)),
|
||||||
]));
|
]));
|
||||||
env.define("List", list_module);
|
env.define("List", list_module);
|
||||||
|
|
||||||
@@ -730,6 +764,26 @@ impl Interpreter {
|
|||||||
),
|
),
|
||||||
("chars".to_string(), Value::Builtin(BuiltinFn::StringChars)),
|
("chars".to_string(), Value::Builtin(BuiltinFn::StringChars)),
|
||||||
("lines".to_string(), Value::Builtin(BuiltinFn::StringLines)),
|
("lines".to_string(), Value::Builtin(BuiltinFn::StringLines)),
|
||||||
|
(
|
||||||
|
"startsWith".to_string(),
|
||||||
|
Value::Builtin(BuiltinFn::StringStartsWith),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"endsWith".to_string(),
|
||||||
|
Value::Builtin(BuiltinFn::StringEndsWith),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"toUpper".to_string(),
|
||||||
|
Value::Builtin(BuiltinFn::StringToUpper),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"toLower".to_string(),
|
||||||
|
Value::Builtin(BuiltinFn::StringToLower),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"substring".to_string(),
|
||||||
|
Value::Builtin(BuiltinFn::StringSubstring),
|
||||||
|
),
|
||||||
]));
|
]));
|
||||||
env.define("String", string_module);
|
env.define("String", string_module);
|
||||||
|
|
||||||
@@ -789,6 +843,19 @@ impl Interpreter {
|
|||||||
),
|
),
|
||||||
]));
|
]));
|
||||||
env.define("Schema", schema_module);
|
env.define("Schema", schema_module);
|
||||||
|
|
||||||
|
// Math module
|
||||||
|
let math_module = Value::Record(HashMap::from([
|
||||||
|
("abs".to_string(), Value::Builtin(BuiltinFn::MathAbs)),
|
||||||
|
("min".to_string(), Value::Builtin(BuiltinFn::MathMin)),
|
||||||
|
("max".to_string(), Value::Builtin(BuiltinFn::MathMax)),
|
||||||
|
("sqrt".to_string(), Value::Builtin(BuiltinFn::MathSqrt)),
|
||||||
|
("pow".to_string(), Value::Builtin(BuiltinFn::MathPow)),
|
||||||
|
("floor".to_string(), Value::Builtin(BuiltinFn::MathFloor)),
|
||||||
|
("ceil".to_string(), Value::Builtin(BuiltinFn::MathCeil)),
|
||||||
|
("round".to_string(), Value::Builtin(BuiltinFn::MathRound)),
|
||||||
|
]));
|
||||||
|
env.define("Math", math_module);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a program
|
/// Execute a program
|
||||||
@@ -1945,6 +2012,237 @@ impl Interpreter {
|
|||||||
_ => Err(err("Schema.getVersion: argument must be a Versioned value")),
|
_ => Err(err("Schema.getVersion: argument must be a Versioned value")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Math operations
|
||||||
|
BuiltinFn::MathAbs => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(err("Math.abs requires 1 argument"));
|
||||||
|
}
|
||||||
|
match &args[0] {
|
||||||
|
Value::Int(n) => Ok(EvalResult::Value(Value::Int(n.abs()))),
|
||||||
|
Value::Float(n) => Ok(EvalResult::Value(Value::Float(n.abs()))),
|
||||||
|
v => Err(err(&format!("Math.abs expects number, got {}", v.type_name()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::MathMin => {
|
||||||
|
let (a, b) = Self::expect_args_2::<i64, i64>(&args, "Math.min", span)
|
||||||
|
.or_else(|_| {
|
||||||
|
// Try floats
|
||||||
|
if args.len() == 2 {
|
||||||
|
match (&args[0], &args[1]) {
|
||||||
|
(Value::Float(a), Value::Float(b)) => {
|
||||||
|
return Ok((0i64, 0i64)); // Placeholder - we'll handle below
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err("Math.min requires 2 number arguments"))
|
||||||
|
})?;
|
||||||
|
// Check if they were floats
|
||||||
|
match (&args[0], &args[1]) {
|
||||||
|
(Value::Float(a), Value::Float(b)) => {
|
||||||
|
Ok(EvalResult::Value(Value::Float(a.min(*b))))
|
||||||
|
}
|
||||||
|
(Value::Int(a), Value::Int(b)) => {
|
||||||
|
Ok(EvalResult::Value(Value::Int((*a).min(*b))))
|
||||||
|
}
|
||||||
|
_ => Err(err("Math.min requires 2 number arguments")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::MathMax => {
|
||||||
|
match (&args[0], &args[1]) {
|
||||||
|
(Value::Float(a), Value::Float(b)) => {
|
||||||
|
Ok(EvalResult::Value(Value::Float(a.max(*b))))
|
||||||
|
}
|
||||||
|
(Value::Int(a), Value::Int(b)) => {
|
||||||
|
Ok(EvalResult::Value(Value::Int((*a).max(*b))))
|
||||||
|
}
|
||||||
|
_ => Err(err("Math.max requires 2 number arguments")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::MathSqrt => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(err("Math.sqrt requires 1 argument"));
|
||||||
|
}
|
||||||
|
match &args[0] {
|
||||||
|
Value::Int(n) => Ok(EvalResult::Value(Value::Float((*n as f64).sqrt()))),
|
||||||
|
Value::Float(n) => Ok(EvalResult::Value(Value::Float(n.sqrt()))),
|
||||||
|
v => Err(err(&format!("Math.sqrt expects number, got {}", v.type_name()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::MathPow => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(err("Math.pow requires 2 arguments: base, exponent"));
|
||||||
|
}
|
||||||
|
match (&args[0], &args[1]) {
|
||||||
|
(Value::Int(base), Value::Int(exp)) => {
|
||||||
|
if *exp >= 0 {
|
||||||
|
Ok(EvalResult::Value(Value::Int(base.pow(*exp as u32))))
|
||||||
|
} else {
|
||||||
|
Ok(EvalResult::Value(Value::Float((*base as f64).powi(*exp as i32))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Float(base), Value::Int(exp)) => {
|
||||||
|
Ok(EvalResult::Value(Value::Float(base.powi(*exp as i32))))
|
||||||
|
}
|
||||||
|
(Value::Float(base), Value::Float(exp)) => {
|
||||||
|
Ok(EvalResult::Value(Value::Float(base.powf(*exp))))
|
||||||
|
}
|
||||||
|
(Value::Int(base), Value::Float(exp)) => {
|
||||||
|
Ok(EvalResult::Value(Value::Float((*base as f64).powf(*exp))))
|
||||||
|
}
|
||||||
|
_ => Err(err("Math.pow requires number arguments")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::MathFloor => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(err("Math.floor requires 1 argument"));
|
||||||
|
}
|
||||||
|
match &args[0] {
|
||||||
|
Value::Float(n) => Ok(EvalResult::Value(Value::Int(n.floor() as i64))),
|
||||||
|
Value::Int(n) => Ok(EvalResult::Value(Value::Int(*n))),
|
||||||
|
v => Err(err(&format!("Math.floor expects number, got {}", v.type_name()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::MathCeil => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(err("Math.ceil requires 1 argument"));
|
||||||
|
}
|
||||||
|
match &args[0] {
|
||||||
|
Value::Float(n) => Ok(EvalResult::Value(Value::Int(n.ceil() as i64))),
|
||||||
|
Value::Int(n) => Ok(EvalResult::Value(Value::Int(*n))),
|
||||||
|
v => Err(err(&format!("Math.ceil expects number, got {}", v.type_name()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::MathRound => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(err("Math.round requires 1 argument"));
|
||||||
|
}
|
||||||
|
match &args[0] {
|
||||||
|
Value::Float(n) => Ok(EvalResult::Value(Value::Int(n.round() as i64))),
|
||||||
|
Value::Int(n) => Ok(EvalResult::Value(Value::Int(*n))),
|
||||||
|
v => Err(err(&format!("Math.round expects number, got {}", v.type_name()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional List operations
|
||||||
|
BuiltinFn::ListIsEmpty => {
|
||||||
|
let list = Self::expect_arg_1::<Vec<Value>>(&args, "List.isEmpty", span)?;
|
||||||
|
Ok(EvalResult::Value(Value::Bool(list.is_empty())))
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::ListFind => {
|
||||||
|
let (list, func) = Self::expect_args_2::<Vec<Value>, Value>(&args, "List.find", span)?;
|
||||||
|
for item in list {
|
||||||
|
let v = self.eval_call_to_value(func.clone(), vec![item.clone()], span)?;
|
||||||
|
match v {
|
||||||
|
Value::Bool(true) => {
|
||||||
|
return Ok(EvalResult::Value(Value::Constructor {
|
||||||
|
name: "Some".to_string(),
|
||||||
|
fields: vec![item],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
Value::Bool(false) => {}
|
||||||
|
_ => return Err(err("List.find predicate must return Bool")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(EvalResult::Value(Value::Constructor {
|
||||||
|
name: "None".to_string(),
|
||||||
|
fields: vec![],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::ListAny => {
|
||||||
|
let (list, func) = Self::expect_args_2::<Vec<Value>, Value>(&args, "List.any", span)?;
|
||||||
|
for item in list {
|
||||||
|
let v = self.eval_call_to_value(func.clone(), vec![item], span)?;
|
||||||
|
match v {
|
||||||
|
Value::Bool(true) => return Ok(EvalResult::Value(Value::Bool(true))),
|
||||||
|
Value::Bool(false) => {}
|
||||||
|
_ => return Err(err("List.any predicate must return Bool")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(EvalResult::Value(Value::Bool(false)))
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::ListAll => {
|
||||||
|
let (list, func) = Self::expect_args_2::<Vec<Value>, Value>(&args, "List.all", span)?;
|
||||||
|
for item in list {
|
||||||
|
let v = self.eval_call_to_value(func.clone(), vec![item], span)?;
|
||||||
|
match v {
|
||||||
|
Value::Bool(false) => return Ok(EvalResult::Value(Value::Bool(false))),
|
||||||
|
Value::Bool(true) => {}
|
||||||
|
_ => return Err(err("List.all predicate must return Bool")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(EvalResult::Value(Value::Bool(true)))
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::ListTake => {
|
||||||
|
let (list, n) = Self::expect_args_2::<Vec<Value>, i64>(&args, "List.take", span)?;
|
||||||
|
let n = n.max(0) as usize;
|
||||||
|
let result: Vec<Value> = list.into_iter().take(n).collect();
|
||||||
|
Ok(EvalResult::Value(Value::List(result)))
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::ListDrop => {
|
||||||
|
let (list, n) = Self::expect_args_2::<Vec<Value>, i64>(&args, "List.drop", span)?;
|
||||||
|
let n = n.max(0) as usize;
|
||||||
|
let result: Vec<Value> = list.into_iter().skip(n).collect();
|
||||||
|
Ok(EvalResult::Value(Value::List(result)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional String operations
|
||||||
|
BuiltinFn::StringStartsWith => {
|
||||||
|
let (s, prefix) = Self::expect_args_2::<String, String>(&args, "String.startsWith", span)?;
|
||||||
|
Ok(EvalResult::Value(Value::Bool(s.starts_with(&prefix))))
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::StringEndsWith => {
|
||||||
|
let (s, suffix) = Self::expect_args_2::<String, String>(&args, "String.endsWith", span)?;
|
||||||
|
Ok(EvalResult::Value(Value::Bool(s.ends_with(&suffix))))
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::StringToUpper => {
|
||||||
|
let s = Self::expect_arg_1::<String>(&args, "String.toUpper", span)?;
|
||||||
|
Ok(EvalResult::Value(Value::String(s.to_uppercase())))
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::StringToLower => {
|
||||||
|
let s = Self::expect_arg_1::<String>(&args, "String.toLower", span)?;
|
||||||
|
Ok(EvalResult::Value(Value::String(s.to_lowercase())))
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFn::StringSubstring => {
|
||||||
|
// String.substring(s, start, end) - end is exclusive
|
||||||
|
if args.len() != 3 {
|
||||||
|
return Err(err("String.substring requires 3 arguments: string, start, end"));
|
||||||
|
}
|
||||||
|
let s = match &args[0] {
|
||||||
|
Value::String(s) => s.clone(),
|
||||||
|
v => return Err(err(&format!("String.substring expects String, got {}", v.type_name()))),
|
||||||
|
};
|
||||||
|
let start = match &args[1] {
|
||||||
|
Value::Int(n) => (*n).max(0) as usize,
|
||||||
|
v => return Err(err(&format!("String.substring expects Int for start, got {}", v.type_name()))),
|
||||||
|
};
|
||||||
|
let end = match &args[2] {
|
||||||
|
Value::Int(n) => (*n).max(0) as usize,
|
||||||
|
v => return Err(err(&format!("String.substring expects Int for end, got {}", v.type_name()))),
|
||||||
|
};
|
||||||
|
let chars: Vec<char> = s.chars().collect();
|
||||||
|
let end = end.min(chars.len());
|
||||||
|
let start = start.min(end);
|
||||||
|
let result: String = chars[start..end].iter().collect();
|
||||||
|
Ok(EvalResult::Value(Value::String(result)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
104
src/main.rs
104
src/main.rs
@@ -1377,6 +1377,110 @@ c")"#;
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(result.unwrap_err().contains("downgrade"));
|
assert!(result.unwrap_err().contains("downgrade"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Math module tests
|
||||||
|
#[test]
|
||||||
|
fn test_math_abs() {
|
||||||
|
let (result, _) = run_with_effects("let x = Math.abs(-42)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "42");
|
||||||
|
let (result, _) = run_with_effects("let x = Math.abs(42)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "42");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_math_min_max() {
|
||||||
|
let (result, _) = run_with_effects("let x = Math.min(3, 7)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "3");
|
||||||
|
let (result, _) = run_with_effects("let x = Math.max(3, 7)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "7");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_math_sqrt() {
|
||||||
|
let (result, _) = run_with_effects("let x = Math.sqrt(16)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_math_pow() {
|
||||||
|
let (result, _) = run_with_effects("let x = Math.pow(2, 10)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "1024");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_math_floor_ceil_round() {
|
||||||
|
let (result, _) = run_with_effects("let x = Math.floor(3.7)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "3");
|
||||||
|
let (result, _) = run_with_effects("let x = Math.ceil(3.2)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "4");
|
||||||
|
let (result, _) = run_with_effects("let x = Math.round(3.5)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "4");
|
||||||
|
}
|
||||||
|
|
||||||
|
// List module additional functions
|
||||||
|
#[test]
|
||||||
|
fn test_list_is_empty() {
|
||||||
|
let (result, _) = run_with_effects("let x = List.isEmpty([])", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "true");
|
||||||
|
let (result, _) = run_with_effects("let x = List.isEmpty([1, 2])", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_find() {
|
||||||
|
let source = "let x = List.find([1, 2, 3, 4, 5], fn(x: Int): Bool => x > 3)";
|
||||||
|
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "Some(4)");
|
||||||
|
let source = "let x = List.find([1, 2, 3], fn(x: Int): Bool => x > 10)";
|
||||||
|
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "None");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_any_all() {
|
||||||
|
let source = "let x = List.any([1, 2, 3], fn(x: Int): Bool => x > 2)";
|
||||||
|
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "true");
|
||||||
|
let source = "let x = List.all([1, 2, 3], fn(x: Int): Bool => x > 0)";
|
||||||
|
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "true");
|
||||||
|
let source = "let x = List.all([1, 2, 3], fn(x: Int): Bool => x > 2)";
|
||||||
|
let (result, _) = run_with_effects(source, Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_take_drop() {
|
||||||
|
let (result, _) = run_with_effects("let x = List.take([1, 2, 3, 4, 5], 3)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "[1, 2, 3]");
|
||||||
|
let (result, _) = run_with_effects("let x = List.drop([1, 2, 3, 4, 5], 2)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "[3, 4, 5]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// String module additional functions
|
||||||
|
#[test]
|
||||||
|
fn test_string_starts_ends_with() {
|
||||||
|
let (result, _) = run_with_effects("let x = String.startsWith(\"hello\", \"he\")", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "true");
|
||||||
|
let (result, _) = run_with_effects("let x = String.endsWith(\"hello\", \"lo\")", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_string_case_conversion() {
|
||||||
|
let (result, _) = run_with_effects("let x = String.toUpper(\"hello\")", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "\"HELLO\"");
|
||||||
|
let (result, _) = run_with_effects("let x = String.toLower(\"HELLO\")", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "\"hello\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_string_substring() {
|
||||||
|
let (result, _) = run_with_effects("let x = String.substring(\"hello world\", 0, 5)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "\"hello\"");
|
||||||
|
let (result, _) = run_with_effects("let x = String.substring(\"hello\", 2, 4)", Value::Unit, Value::Unit).unwrap();
|
||||||
|
assert_eq!(result, "\"ll\"");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diagnostic rendering tests
|
// Diagnostic rendering tests
|
||||||
|
|||||||
105
src/types.rs
105
src/types.rs
@@ -974,6 +974,54 @@ impl TypeEnv {
|
|||||||
"range".to_string(),
|
"range".to_string(),
|
||||||
Type::function(vec![Type::Int, Type::Int], Type::List(Box::new(Type::Int))),
|
Type::function(vec![Type::Int, Type::Int], Type::List(Box::new(Type::Int))),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"isEmpty".to_string(),
|
||||||
|
Type::function(vec![Type::List(Box::new(Type::var()))], Type::Bool),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"find".to_string(),
|
||||||
|
Type::function(
|
||||||
|
vec![
|
||||||
|
Type::List(Box::new(Type::var())),
|
||||||
|
Type::function(vec![Type::var()], Type::Bool),
|
||||||
|
],
|
||||||
|
Type::Option(Box::new(Type::var())),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"any".to_string(),
|
||||||
|
Type::function(
|
||||||
|
vec![
|
||||||
|
Type::List(Box::new(Type::var())),
|
||||||
|
Type::function(vec![Type::var()], Type::Bool),
|
||||||
|
],
|
||||||
|
Type::Bool,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"all".to_string(),
|
||||||
|
Type::function(
|
||||||
|
vec![
|
||||||
|
Type::List(Box::new(Type::var())),
|
||||||
|
Type::function(vec![Type::var()], Type::Bool),
|
||||||
|
],
|
||||||
|
Type::Bool,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"take".to_string(),
|
||||||
|
Type::function(
|
||||||
|
vec![Type::List(Box::new(Type::var())), Type::Int],
|
||||||
|
Type::List(Box::new(Type::var())),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"drop".to_string(),
|
||||||
|
Type::function(
|
||||||
|
vec![Type::List(Box::new(Type::var())), Type::Int],
|
||||||
|
Type::List(Box::new(Type::var())),
|
||||||
|
),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
env.bind("List", TypeScheme::mono(list_module_type));
|
env.bind("List", TypeScheme::mono(list_module_type));
|
||||||
|
|
||||||
@@ -1017,6 +1065,26 @@ impl TypeEnv {
|
|||||||
"lines".to_string(),
|
"lines".to_string(),
|
||||||
Type::function(vec![Type::String], Type::List(Box::new(Type::String))),
|
Type::function(vec![Type::String], Type::List(Box::new(Type::String))),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"startsWith".to_string(),
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::Bool),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"endsWith".to_string(),
|
||||||
|
Type::function(vec![Type::String, Type::String], Type::Bool),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"toUpper".to_string(),
|
||||||
|
Type::function(vec![Type::String], Type::String),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"toLower".to_string(),
|
||||||
|
Type::function(vec![Type::String], Type::String),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"substring".to_string(),
|
||||||
|
Type::function(vec![Type::String, Type::Int, Type::Int], Type::String),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
env.bind("String", TypeScheme::mono(string_module_type));
|
env.bind("String", TypeScheme::mono(string_module_type));
|
||||||
|
|
||||||
@@ -1138,6 +1206,43 @@ impl TypeEnv {
|
|||||||
]);
|
]);
|
||||||
env.bind("Schema", TypeScheme::mono(schema_module_type));
|
env.bind("Schema", TypeScheme::mono(schema_module_type));
|
||||||
|
|
||||||
|
// Math module
|
||||||
|
let math_module_type = Type::Record(vec![
|
||||||
|
(
|
||||||
|
"abs".to_string(),
|
||||||
|
Type::function(vec![Type::var()], Type::var()), // Works on Int or Float
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"min".to_string(),
|
||||||
|
Type::function(vec![Type::var(), Type::var()], Type::var()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"max".to_string(),
|
||||||
|
Type::function(vec![Type::var(), Type::var()], Type::var()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"sqrt".to_string(),
|
||||||
|
Type::function(vec![Type::var()], Type::Float),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pow".to_string(),
|
||||||
|
Type::function(vec![Type::var(), Type::var()], Type::var()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"floor".to_string(),
|
||||||
|
Type::function(vec![Type::var()], Type::Int),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ceil".to_string(),
|
||||||
|
Type::function(vec![Type::var()], Type::Int),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"round".to_string(),
|
||||||
|
Type::function(vec![Type::var()], Type::Int),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
env.bind("Math", TypeScheme::mono(math_module_type));
|
||||||
|
|
||||||
env
|
env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user