Files
aster/aster-core/src/runtime/builtins/string.rs
T
0264408 fa6ea512b3 refactor: VM-only architecture — remove tree-walking interpreter
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>
2026-06-26 10:31:32 +08:00

204 lines
7.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 标准库:stringsplit, 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,
}),
}
}