fa6ea512b3
Eliminate dual-engine architecture (Interpreter + Vm) in favor of a single bytecode VM executor. - Extract shared types (Value, FunctionProto, Closure, UpvalueObj, Runtime trait, NativeFn) into new module - Consolidate builtin registration in — single used by VM, eliminates 40-line duplicate - Delete tree-walker: exec.rs, eval.rs, interpreter.rs, env.rs, module.rs - Change Value::Function(Rc<Function>) → Value::Function(Rc<Closure>) eliminating the closures HashMap pointer-key hack - Fix VM semantic gaps found during migration: * Structural equality for Object/Array in is_equal * CompoundAssignGlobal opcode (global compound assigns were broken) * 9 string methods added to VM get_property * ForInInit error on non-iterable values - Switch run_file/run_repl to VM; remove --vm CLI flag - Move 327 tests from interpreter/ to vm/ — all pass Co-Authored-By: Claude <noreply@anthropic.com>
204 lines
7.6 KiB
Rust
204 lines
7.6 KiB
Rust
//! 标准库:string(split, trim, substring, replace, contains, upper, lower, starts_with, ends_with)
|
||
|
||
use crate::error::RuntimeError;
|
||
use crate::runtime::{Runtime, Value};
|
||
use std::cell::RefCell;
|
||
use std::rc::Rc;
|
||
|
||
fn require_string(val: &Value, arg_name: &str) -> Result<String, RuntimeError> {
|
||
match val {
|
||
Value::String(s) => Ok(s.clone()),
|
||
_ => Err(RuntimeError::RuntimeError {
|
||
message: format!("{} must be a string", arg_name),
|
||
token: None,
|
||
}),
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// split(str, delim) → array of substrings
|
||
// ============================================================================
|
||
|
||
pub fn split(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||
if args.len() < 2 {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "split() expects 2 arguments (string, delimiter)".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
let s = require_string(&args[0], "First argument")?;
|
||
let delim = require_string(&args[1], "Second argument (delimiter)")?;
|
||
|
||
let parts: Vec<Value> = if delim.is_empty() {
|
||
// Split by empty string → characters
|
||
s.chars().map(|c| Value::String(c.to_string())).collect()
|
||
} else {
|
||
s.split(&delim).map(|p| Value::String(p.to_string())).collect()
|
||
};
|
||
|
||
Ok(Value::Array(Rc::new(RefCell::new(parts))))
|
||
}
|
||
|
||
// ============================================================================
|
||
// trim(str) → string with leading/trailing whitespace removed
|
||
// ============================================================================
|
||
|
||
pub fn trim(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||
if args.is_empty() {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "trim() expects 1 argument (string)".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
let s = require_string(&args[0], "Argument")?;
|
||
Ok(Value::String(s.trim().to_string()))
|
||
}
|
||
|
||
// ============================================================================
|
||
// substring(str, start, len?) → substring
|
||
// ============================================================================
|
||
|
||
pub fn substring(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||
if args.is_empty() {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "substring() expects at least 2 arguments (string, start, len?)".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
let s = require_string(&args[0], "First argument")?;
|
||
let start = as_usize(&args[1], "Second argument (start)")?;
|
||
let chars: Vec<char> = s.chars().collect();
|
||
let start = start.min(chars.len());
|
||
let end = if args.len() >= 3 {
|
||
(start + as_usize(&args[2], "Third argument (length)")?).min(chars.len())
|
||
} else {
|
||
chars.len()
|
||
};
|
||
Ok(Value::String(chars[start..end].iter().collect()))
|
||
}
|
||
|
||
// ============================================================================
|
||
// replace(str, old, new) → string with all occurrences replaced
|
||
// ============================================================================
|
||
|
||
pub fn replace(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||
if args.len() < 3 {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "replace() expects 3 arguments (string, old, new)".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
let s = require_string(&args[0], "First argument")?;
|
||
let old = require_string(&args[1], "Second argument (old)")?;
|
||
let new = require_string(&args[2], "Third argument (new)")?;
|
||
|
||
if old.is_empty() {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "replace(): 'old' must not be empty".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
|
||
Ok(Value::String(s.replace(&old, &new)))
|
||
}
|
||
|
||
// ============================================================================
|
||
// contains(str, needle) → bool
|
||
// ============================================================================
|
||
|
||
pub fn contains(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||
if args.len() < 2 {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "contains() expects 2 arguments (string, needle)".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
let s = require_string(&args[0], "First argument")?;
|
||
let needle = require_string(&args[1], "Second argument (needle)")?;
|
||
Ok(Value::Bool(s.contains(&needle)))
|
||
}
|
||
|
||
// ============================================================================
|
||
// upper(str) → uppercase (ASCII)
|
||
// ============================================================================
|
||
|
||
pub fn upper(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||
if args.is_empty() {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "upper() expects 1 argument (string)".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
let s = require_string(&args[0], "Argument")?;
|
||
Ok(Value::String(s.to_ascii_uppercase()))
|
||
}
|
||
|
||
// ============================================================================
|
||
// lower(str) → lowercase (ASCII)
|
||
// ============================================================================
|
||
|
||
pub fn lower(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||
if args.is_empty() {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "lower() expects 1 argument (string)".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
let s = require_string(&args[0], "Argument")?;
|
||
Ok(Value::String(s.to_ascii_lowercase()))
|
||
}
|
||
|
||
// ============================================================================
|
||
// starts_with(str, prefix) → bool
|
||
// ============================================================================
|
||
|
||
pub fn starts_with(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||
if args.len() < 2 {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "starts_with() expects 2 arguments (string, prefix)".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
let s = require_string(&args[0], "First argument")?;
|
||
let prefix = require_string(&args[1], "Second argument (prefix)")?;
|
||
Ok(Value::Bool(s.starts_with(&prefix)))
|
||
}
|
||
|
||
// ============================================================================
|
||
// ends_with(str, suffix) → bool
|
||
// ============================================================================
|
||
|
||
pub fn ends_with(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||
if args.len() < 2 {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: "ends_with() expects 2 arguments (string, suffix)".into(),
|
||
token: None,
|
||
});
|
||
}
|
||
let s = require_string(&args[0], "First argument")?;
|
||
let suffix = require_string(&args[1], "Second argument (suffix)")?;
|
||
Ok(Value::Bool(s.ends_with(&suffix)))
|
||
}
|
||
|
||
// ============================================================================
|
||
// Helper
|
||
// ============================================================================
|
||
|
||
fn as_usize(val: &Value, arg_name: &str) -> Result<usize, RuntimeError> {
|
||
match val {
|
||
Value::Number(n) => {
|
||
if *n < 0.0 || n.fract() != 0.0 {
|
||
return Err(RuntimeError::RuntimeError {
|
||
message: format!("{} must be a non-negative integer, got {}", arg_name, n),
|
||
token: None,
|
||
});
|
||
}
|
||
Ok(*n as usize)
|
||
}
|
||
_ => Err(RuntimeError::RuntimeError {
|
||
message: format!("{} must be a number", arg_name),
|
||
token: None,
|
||
}),
|
||
}
|
||
}
|