Merge branch 'master' of https://gitea.jetlumen.com/admin/aster
This commit is contained in:
@@ -130,7 +130,7 @@ pub enum LogicalOp {
|
|||||||
Or,
|
Or,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AssignOp {
|
pub enum AssignOp {
|
||||||
Equal, // =
|
Equal, // =
|
||||||
PlusEqual, // +=
|
PlusEqual, // +=
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//! 标准库:core(len, typeof, push, pop)
|
//! 标准库:core(len, typeof, push, pop)
|
||||||
|
|
||||||
use crate::error::RuntimeError;
|
use crate::error::RuntimeError;
|
||||||
use crate::interpreter::{Interpreter, Value};
|
use crate::interpreter::{Runtime, Value};
|
||||||
|
|
||||||
pub fn len(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn len(_runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "len() expects 1 argument".into(),
|
message: "len() expects 1 argument".into(),
|
||||||
@@ -21,7 +21,7 @@ pub fn len(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn typeof_fn(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn typeof_fn(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "typeof() expects 1 argument".into(),
|
message: "typeof() expects 1 argument".into(),
|
||||||
@@ -41,7 +41,7 @@ pub fn typeof_fn(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
|
|||||||
Ok(Value::String(s.into()))
|
Ok(Value::String(s.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn push(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "push() expects 2 arguments (array, value)".into(),
|
message: "push() expects 2 arguments (array, value)".into(),
|
||||||
@@ -61,7 +61,7 @@ pub fn push(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runtim
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn pop(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "pop() expects 1 argument (array)".into(),
|
message: "pop() expects 1 argument (array)".into(),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//! 标准库:io(print, input)
|
//! 标准库:io(print, input)
|
||||||
|
|
||||||
use crate::error::RuntimeError;
|
use crate::error::RuntimeError;
|
||||||
use crate::interpreter::{Interpreter, Value};
|
use crate::interpreter::{Runtime, Value};
|
||||||
|
|
||||||
pub fn print(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn print(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
for arg in args.iter() {
|
for arg in args.iter() {
|
||||||
print!("{}", arg);
|
print!("{}", arg);
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ pub fn print(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
|
|||||||
Ok(Value::Nil)
|
Ok(Value::Nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn input(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if let Some(prompt) = args.get(0) {
|
if let Some(prompt) = args.get(0) {
|
||||||
print!("{}", prompt);
|
print!("{}", prompt);
|
||||||
std::io::Write::flush(&mut std::io::stdout()).unwrap();
|
std::io::Write::flush(&mut std::io::stdout()).unwrap();
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
//! 内置函数模块:按功能分组注册,便于扩展和维护。
|
//! 内置函数模块:按功能分组注册,便于扩展和维护。
|
||||||
|
|
||||||
mod core;
|
pub mod core;
|
||||||
mod io;
|
pub mod io;
|
||||||
mod os;
|
pub mod os;
|
||||||
pub mod string;
|
pub mod string;
|
||||||
|
|
||||||
use crate::interpreter::{Env, Value};
|
use crate::interpreter::{Env, Value};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//! 标准库:os(clock)
|
//! 标准库:os(clock)
|
||||||
|
|
||||||
use crate::error::RuntimeError;
|
use crate::error::RuntimeError;
|
||||||
use crate::interpreter::{Interpreter, Value};
|
use crate::interpreter::{Runtime, Value};
|
||||||
|
|
||||||
pub fn clock(_interp: &mut Interpreter, _args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn clock(_interp: &mut dyn Runtime, _args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
Ok(Value::Number(
|
Ok(Value::Number(
|
||||||
std::time::SystemTime::now()
|
std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! 标准库:string(split, trim, substring, replace, contains, upper, lower, starts_with, ends_with)
|
//! 标准库:string(split, trim, substring, replace, contains, upper, lower, starts_with, ends_with)
|
||||||
|
|
||||||
use crate::error::RuntimeError;
|
use crate::error::RuntimeError;
|
||||||
use crate::interpreter::{Interpreter, Value};
|
use crate::interpreter::{Runtime, Value};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ fn require_string(val: &Value, arg_name: &str) -> Result<String, RuntimeError> {
|
|||||||
// split(str, delim) → array of substrings
|
// split(str, delim) → array of substrings
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
pub fn split(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn split(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "split() expects 2 arguments (string, delimiter)".into(),
|
message: "split() expects 2 arguments (string, delimiter)".into(),
|
||||||
@@ -43,7 +43,7 @@ pub fn split(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
|
|||||||
// trim(str) → string with leading/trailing whitespace removed
|
// trim(str) → string with leading/trailing whitespace removed
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
pub fn trim(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn trim(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "trim() expects 1 argument (string)".into(),
|
message: "trim() expects 1 argument (string)".into(),
|
||||||
@@ -58,7 +58,7 @@ pub fn trim(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runtim
|
|||||||
// substring(str, start, len?) → substring
|
// substring(str, start, len?) → substring
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
pub fn substring(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn substring(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "substring() expects at least 2 arguments (string, start, len?)".into(),
|
message: "substring() expects at least 2 arguments (string, start, len?)".into(),
|
||||||
@@ -81,7 +81,7 @@ pub fn substring(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
|
|||||||
// replace(str, old, new) → string with all occurrences replaced
|
// replace(str, old, new) → string with all occurrences replaced
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
pub fn replace(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn replace(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.len() < 3 {
|
if args.len() < 3 {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "replace() expects 3 arguments (string, old, new)".into(),
|
message: "replace() expects 3 arguments (string, old, new)".into(),
|
||||||
@@ -106,7 +106,7 @@ pub fn replace(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Run
|
|||||||
// contains(str, needle) → bool
|
// contains(str, needle) → bool
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
pub fn contains(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn contains(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "contains() expects 2 arguments (string, needle)".into(),
|
message: "contains() expects 2 arguments (string, needle)".into(),
|
||||||
@@ -122,7 +122,7 @@ pub fn contains(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Ru
|
|||||||
// upper(str) → uppercase (ASCII)
|
// upper(str) → uppercase (ASCII)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
pub fn upper(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn upper(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "upper() expects 1 argument (string)".into(),
|
message: "upper() expects 1 argument (string)".into(),
|
||||||
@@ -137,7 +137,7 @@ pub fn upper(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
|
|||||||
// lower(str) → lowercase (ASCII)
|
// lower(str) → lowercase (ASCII)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
pub fn lower(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn lower(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "lower() expects 1 argument (string)".into(),
|
message: "lower() expects 1 argument (string)".into(),
|
||||||
@@ -152,7 +152,7 @@ pub fn lower(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
|
|||||||
// starts_with(str, prefix) → bool
|
// starts_with(str, prefix) → bool
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
pub fn starts_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn starts_with(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "starts_with() expects 2 arguments (string, prefix)".into(),
|
message: "starts_with() expects 2 arguments (string, prefix)".into(),
|
||||||
@@ -168,7 +168,7 @@ pub fn starts_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value,
|
|||||||
// ends_with(str, suffix) → bool
|
// ends_with(str, suffix) → bool
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
pub fn ends_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn ends_with(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
if args.len() < 2 {
|
if args.len() < 2 {
|
||||||
return Err(RuntimeError::RuntimeError {
|
return Err(RuntimeError::RuntimeError {
|
||||||
message: "ends_with() expects 2 arguments (string, suffix)".into(),
|
message: "ends_with() expects 2 arguments (string, suffix)".into(),
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ impl super::Interpreter {
|
|||||||
"length" => Ok(Value::Number(arr.borrow().len() as f64)),
|
"length" => Ok(Value::Number(arr.borrow().len() as f64)),
|
||||||
"push" => {
|
"push" => {
|
||||||
let arr = Rc::clone(&arr);
|
let arr = Rc::clone(&arr);
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, mut args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |_runtime: &mut dyn super::Runtime, mut args: Vec<Value>| {
|
||||||
let val = args.pop().unwrap_or(Value::Nil);
|
let val = args.pop().unwrap_or(Value::Nil);
|
||||||
arr.borrow_mut().push(val.clone());
|
arr.borrow_mut().push(val.clone());
|
||||||
Ok(val)
|
Ok(val)
|
||||||
@@ -111,7 +111,7 @@ impl super::Interpreter {
|
|||||||
}
|
}
|
||||||
"pop" => {
|
"pop" => {
|
||||||
let arr = Rc::clone(&arr);
|
let arr = Rc::clone(&arr);
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, _args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |_runtime: &mut dyn super::Runtime, _args: Vec<Value>| {
|
||||||
arr.borrow_mut()
|
arr.borrow_mut()
|
||||||
.pop()
|
.pop()
|
||||||
.ok_or_else(|| RuntimeError::RuntimeError {
|
.ok_or_else(|| RuntimeError::RuntimeError {
|
||||||
@@ -129,74 +129,74 @@ impl super::Interpreter {
|
|||||||
"length" => Ok(Value::Number(s.chars().count() as f64)),
|
"length" => Ok(Value::Number(s.chars().count() as f64)),
|
||||||
"upper" => {
|
"upper" => {
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
|
||||||
let mut all_args = vec![Value::String(s.clone())];
|
let mut all_args = vec![Value::String(s.clone())];
|
||||||
all_args.extend(args);
|
all_args.extend(args);
|
||||||
super::builtins::string::upper(_interp, all_args)
|
super::builtins::string::upper(runtime, all_args)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
"lower" => {
|
"lower" => {
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
|
||||||
let mut all_args = vec![Value::String(s.clone())];
|
let mut all_args = vec![Value::String(s.clone())];
|
||||||
all_args.extend(args);
|
all_args.extend(args);
|
||||||
super::builtins::string::lower(_interp, all_args)
|
super::builtins::string::lower(runtime, all_args)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
"trim" => {
|
"trim" => {
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
|
||||||
let mut all_args = vec![Value::String(s.clone())];
|
let mut all_args = vec![Value::String(s.clone())];
|
||||||
all_args.extend(args);
|
all_args.extend(args);
|
||||||
super::builtins::string::trim(_interp, all_args)
|
super::builtins::string::trim(runtime, all_args)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
"substring" => {
|
"substring" => {
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
|
||||||
let mut all_args = vec![Value::String(s.clone())];
|
let mut all_args = vec![Value::String(s.clone())];
|
||||||
all_args.extend(args);
|
all_args.extend(args);
|
||||||
super::builtins::string::substring(_interp, all_args)
|
super::builtins::string::substring(runtime, all_args)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
"replace" => {
|
"replace" => {
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
|
||||||
let mut all_args = vec![Value::String(s.clone())];
|
let mut all_args = vec![Value::String(s.clone())];
|
||||||
all_args.extend(args);
|
all_args.extend(args);
|
||||||
super::builtins::string::replace(_interp, all_args)
|
super::builtins::string::replace(runtime, all_args)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
"contains" => {
|
"contains" => {
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
|
||||||
let mut all_args = vec![Value::String(s.clone())];
|
let mut all_args = vec![Value::String(s.clone())];
|
||||||
all_args.extend(args);
|
all_args.extend(args);
|
||||||
super::builtins::string::contains(_interp, all_args)
|
super::builtins::string::contains(runtime, all_args)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
"starts_with" => {
|
"starts_with" => {
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
|
||||||
let mut all_args = vec![Value::String(s.clone())];
|
let mut all_args = vec![Value::String(s.clone())];
|
||||||
all_args.extend(args);
|
all_args.extend(args);
|
||||||
super::builtins::string::starts_with(_interp, all_args)
|
super::builtins::string::starts_with(runtime, all_args)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
"ends_with" => {
|
"ends_with" => {
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
|
||||||
let mut all_args = vec![Value::String(s.clone())];
|
let mut all_args = vec![Value::String(s.clone())];
|
||||||
all_args.extend(args);
|
all_args.extend(args);
|
||||||
super::builtins::string::ends_with(_interp, all_args)
|
super::builtins::string::ends_with(runtime, all_args)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
"split" => {
|
"split" => {
|
||||||
let s = s.clone();
|
let s = s.clone();
|
||||||
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
|
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
|
||||||
let mut all_args = vec![Value::String(s.clone())];
|
let mut all_args = vec![Value::String(s.clone())];
|
||||||
all_args.extend(args);
|
all_args.extend(args);
|
||||||
super::builtins::string::split(_interp, all_args)
|
super::builtins::string::split(runtime, all_args)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
_ => Err(RuntimeError::RuntimeError {
|
_ => Err(RuntimeError::RuntimeError {
|
||||||
@@ -481,7 +481,7 @@ impl super::Interpreter {
|
|||||||
|
|
||||||
pub fn call_function(&mut self, func_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
pub fn call_function(&mut self, func_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
match func_val {
|
match func_val {
|
||||||
Value::NativeFunction(native_fn) => native_fn(self, args),
|
Value::NativeFunction(native_fn) => native_fn(self as &mut dyn super::Runtime, args),
|
||||||
Value::Function(f) => self.call_user_function(f, args),
|
Value::Function(f) => self.call_user_function(f, args),
|
||||||
_ => Err(RuntimeError::RuntimeError {
|
_ => Err(RuntimeError::RuntimeError {
|
||||||
message: "Attempt to call non-function".to_string(),
|
message: "Attempt to call non-function".to_string(),
|
||||||
|
|||||||
@@ -13,7 +13,16 @@ use std::rc::Rc;
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
pub type NativeFn = Rc<dyn Fn(&mut super::Interpreter, Vec<Value>) -> Result<Value, crate::error::RuntimeError>>;
|
/// Trait abstracting runtime services that native functions may need.
|
||||||
|
/// Both the tree-walking `Interpreter` and the bytecode `Vm` implement this.
|
||||||
|
pub trait Runtime {
|
||||||
|
/// Load and execute a module, returning its exports object.
|
||||||
|
/// Implementations differ: tree-walker interprets directly,
|
||||||
|
/// VM compiles to bytecode then executes.
|
||||||
|
fn require(&mut self, path: &str) -> Result<Value, crate::error::RuntimeError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type NativeFn = Rc<dyn Fn(&mut dyn Runtime, Vec<Value>) -> Result<Value, crate::error::RuntimeError>>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
|||||||
@@ -8,15 +8,20 @@
|
|||||||
use crate::error::RuntimeError;
|
use crate::error::RuntimeError;
|
||||||
use crate::lexer::Lexer;
|
use crate::lexer::Lexer;
|
||||||
use crate::parser::Parser;
|
use crate::parser::Parser;
|
||||||
use super::{Interpreter, Env, Value, Signal};
|
use super::{Interpreter, Env, Value, Signal, Runtime};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
/// require() 的内置函数实现
|
impl Runtime for Interpreter {
|
||||||
pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
fn require(&mut self, path: &str) -> Result<Value, RuntimeError> {
|
||||||
// 1. 参数验证
|
require_impl(self, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// require() 的内置函数实现 — 薄包装,委托给 Runtime::require
|
||||||
|
pub fn require_fn(runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||||
let path_str = match args.first() {
|
let path_str = match args.first() {
|
||||||
Some(Value::String(s)) => s.clone(),
|
Some(Value::String(s)) => s.clone(),
|
||||||
Some(other) => return Err(runtime_error(format!(
|
Some(other) => return Err(runtime_error(format!(
|
||||||
@@ -24,28 +29,32 @@ pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
|
|||||||
None => return Err(runtime_error(
|
None => return Err(runtime_error(
|
||||||
"require() expects 1 argument (string path)")),
|
"require() expects 1 argument (string path)")),
|
||||||
};
|
};
|
||||||
|
runtime.require(&path_str)
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 路径解析
|
/// require() 的内部实现 (供 Interpreter::require 使用)
|
||||||
let resolved = resolve_path(&interp.current_dir, &path_str)?;
|
fn require_impl(interp: &mut Interpreter, path_str: &str) -> Result<Value, RuntimeError> {
|
||||||
|
// 1. 路径解析
|
||||||
|
let resolved = resolve_path(&interp.current_dir, path_str)?;
|
||||||
|
|
||||||
// 3. 缓存查找
|
// 2. 缓存查找
|
||||||
if let Some(cached) = interp.module_cache.borrow().get(&resolved) {
|
if let Some(cached) = interp.module_cache.borrow().get(&resolved) {
|
||||||
return Ok(cached.clone());
|
return Ok(cached.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 读取文件
|
// 3. 读取文件
|
||||||
let src = std::fs::read_to_string(&resolved)
|
let src = std::fs::read_to_string(&resolved)
|
||||||
.map_err(|e| runtime_error(format!(
|
.map_err(|e| runtime_error(format!(
|
||||||
"Module '{}' not found: {}", path_str, e)))?;
|
"Module '{}' not found: {}", path_str, e)))?;
|
||||||
|
|
||||||
// 5. 词法分析
|
// 4. 词法分析
|
||||||
let (tokens, lex_errors) = Lexer::new(&src).tokenize();
|
let (tokens, lex_errors) = Lexer::new(&src).tokenize();
|
||||||
if !lex_errors.is_empty() {
|
if !lex_errors.is_empty() {
|
||||||
return Err(runtime_error(format!(
|
return Err(runtime_error(format!(
|
||||||
"Lex error in module '{}': {}", path_str, lex_errors[0])));
|
"Lex error in module '{}': {}", path_str, lex_errors[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 语法分析
|
// 5. 语法分析
|
||||||
let mut parser = Parser::new(tokens);
|
let mut parser = Parser::new(tokens);
|
||||||
let (stmts, parse_errors) = parser.parse();
|
let (stmts, parse_errors) = parser.parse();
|
||||||
if !parse_errors.is_empty() {
|
if !parse_errors.is_empty() {
|
||||||
@@ -53,27 +62,26 @@ pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
|
|||||||
"Parse error in module '{}': {}", path_str, parse_errors[0])));
|
"Parse error in module '{}': {}", path_str, parse_errors[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. 创建隔离的模块 env(父级 = builtins_env,看不到调用者的变量)
|
// 6. 创建隔离的模块 env(父级 = builtins_env,看不到调用者的变量)
|
||||||
let module_env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&interp.builtins_env)))));
|
let module_env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&interp.builtins_env)))));
|
||||||
|
|
||||||
// 8. 在缓存中插入占位符(支持循环 require)
|
// 7. 在缓存中插入占位符(支持循环 require)
|
||||||
let exports_map = Rc::new(RefCell::new(HashMap::new()));
|
let exports_map = Rc::new(RefCell::new(HashMap::new()));
|
||||||
let exports = Value::Object(Rc::clone(&exports_map));
|
let exports = Value::Object(Rc::clone(&exports_map));
|
||||||
interp.module_cache.borrow_mut().insert(resolved.clone(), exports.clone());
|
interp.module_cache.borrow_mut().insert(resolved.clone(), exports.clone());
|
||||||
|
|
||||||
// 9. 保存调用者状态
|
// 8. 保存调用者状态
|
||||||
let previous_env = Rc::clone(&interp.env);
|
let previous_env = Rc::clone(&interp.env);
|
||||||
let previous_dir = interp.current_dir.clone();
|
let previous_dir = interp.current_dir.clone();
|
||||||
|
|
||||||
// 10. 切换到模块上下文
|
// 9. 切换到模块上下文
|
||||||
interp.env = module_env.clone();
|
interp.env = module_env.clone();
|
||||||
interp.current_dir = module_dir(&resolved);
|
interp.current_dir = module_dir(&resolved);
|
||||||
|
|
||||||
// 11. 执行模块
|
// 10. 执行模块
|
||||||
for stmt in &stmts {
|
for stmt in &stmts {
|
||||||
match interp.execute(stmt.clone()) {
|
match interp.execute(stmt.clone()) {
|
||||||
Ok(Signal::Break) | Ok(Signal::Continue) => {
|
Ok(Signal::Break) | Ok(Signal::Continue) => {
|
||||||
// 恢复状态后返回错误
|
|
||||||
interp.env = previous_env;
|
interp.env = previous_env;
|
||||||
interp.current_dir = previous_dir;
|
interp.current_dir = previous_dir;
|
||||||
interp.module_cache.borrow_mut().remove(&resolved);
|
interp.module_cache.borrow_mut().remove(&resolved);
|
||||||
@@ -81,23 +89,20 @@ pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
|
|||||||
"break/continue outside of loop in module"));
|
"break/continue outside of loop in module"));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// 恢复状态后传播错误
|
|
||||||
interp.env = previous_env;
|
interp.env = previous_env;
|
||||||
interp.current_dir = previous_dir;
|
interp.current_dir = previous_dir;
|
||||||
interp.module_cache.borrow_mut().remove(&resolved);
|
interp.module_cache.borrow_mut().remove(&resolved);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
Ok(Signal::None) | Ok(Signal::Return(_)) => {
|
Ok(Signal::None) | Ok(Signal::Return(_)) => {}
|
||||||
// 正常执行或顶层 return,继续
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12. 恢复调用者状态
|
// 11. 恢复调用者状态
|
||||||
interp.env = previous_env;
|
interp.env = previous_env;
|
||||||
interp.current_dir = previous_dir;
|
interp.current_dir = previous_dir;
|
||||||
|
|
||||||
// 13. 收集 exports(模块 env 中的所有直接绑定)
|
// 12. 收集 exports(模块 env 中的所有直接绑定)
|
||||||
for (name, (val, _mutable)) in module_env.borrow().values.clone() {
|
for (name, (val, _mutable)) in module_env.borrow().values.clone() {
|
||||||
exports_map.borrow_mut().insert(name, val);
|
exports_map.borrow_mut().insert(name, val);
|
||||||
}
|
}
|
||||||
@@ -109,7 +114,6 @@ pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
|
|||||||
// Helpers
|
// Helpers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/// 解析模块路径:相对路径基于 current_dir,用 canonicalize 规范化
|
|
||||||
fn resolve_path(current_dir: &str, path_str: &str) -> Result<String, RuntimeError> {
|
fn resolve_path(current_dir: &str, path_str: &str) -> Result<String, RuntimeError> {
|
||||||
let path = Path::new(path_str);
|
let path = Path::new(path_str);
|
||||||
let resolved = if path.is_absolute() {
|
let resolved = if path.is_absolute() {
|
||||||
@@ -124,7 +128,6 @@ fn resolve_path(current_dir: &str, path_str: &str) -> Result<String, RuntimeErro
|
|||||||
path_str, resolved.display())))
|
path_str, resolved.display())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 提取模块文件所在目录
|
|
||||||
fn module_dir(resolved: &str) -> String {
|
fn module_dir(resolved: &str) -> String {
|
||||||
Path::new(resolved)
|
Path::new(resolved)
|
||||||
.parent()
|
.parent()
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ pub mod lexer;
|
|||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod interpreter;
|
pub mod interpreter;
|
||||||
|
pub mod vm;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod analysis;
|
pub mod analysis;
|
||||||
|
|
||||||
use lexer::Lexer;
|
use lexer::Lexer;
|
||||||
use parser::Parser;
|
use parser::Parser;
|
||||||
use interpreter::Interpreter;
|
use interpreter::Interpreter;
|
||||||
|
use vm::compiler::Compiler;
|
||||||
|
use vm::vm::Vm;
|
||||||
use error::RuntimeError;
|
use error::RuntimeError;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@@ -46,6 +49,41 @@ pub fn run_file(filename: &str, src: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run a file using the bytecode VM (for performance comparison).
|
||||||
|
pub fn run_file_vm(filename: &str, src: String) {
|
||||||
|
let (tokens, lex_errors) = Lexer::new(&src).tokenize();
|
||||||
|
if !lex_errors.is_empty() {
|
||||||
|
print_errors(&lex_errors);
|
||||||
|
std::process::exit(65);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parser = Parser::new(tokens);
|
||||||
|
let (stmts, parse_errors) = parser.parse();
|
||||||
|
if !parse_errors.is_empty() {
|
||||||
|
print_errors(&parse_errors);
|
||||||
|
std::process::exit(65);
|
||||||
|
}
|
||||||
|
|
||||||
|
let script_dir = std::path::Path::new(filename)
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_else(|| ".".to_string());
|
||||||
|
|
||||||
|
let proto = match Compiler::compile(&stmts) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Compile Error: {}", e);
|
||||||
|
std::process::exit(70);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut vm = Vm::with_current_dir(script_dir);
|
||||||
|
if let Err(e) = vm.run(std::rc::Rc::new(proto)) {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
std::process::exit(70);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_repl() {
|
pub fn run_repl() {
|
||||||
println!("Welcome to Aster REPL!");
|
println!("Welcome to Aster REPL!");
|
||||||
println!("Type ':exit' to quit.");
|
println!("Type ':exit' to quit.");
|
||||||
|
|||||||
@@ -0,0 +1,988 @@
|
|||||||
|
//! AST → bytecode compiler for the Aster VM.
|
||||||
|
//!
|
||||||
|
//! Walks the AST recursively, emitting stack-based bytecode instructions.
|
||||||
|
//! Handles local variable resolution (slot indices), upvalue capture for
|
||||||
|
//! closures, jump backpatching for control flow, and constant pool management.
|
||||||
|
|
||||||
|
use crate::ast::*;
|
||||||
|
use crate::ast::expr::{Literal, UnaryOp, BinaryOp, LogicalOp, AssignOp};
|
||||||
|
use crate::error::RuntimeError;
|
||||||
|
use crate::interpreter::Value;
|
||||||
|
use super::opcode::*;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FunctionProto — compiled function blueprint
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FunctionProto {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub arity: u8,
|
||||||
|
pub code: Vec<u8>,
|
||||||
|
pub constants: Vec<Value>,
|
||||||
|
/// Nested function protos (for closures and function declarations)
|
||||||
|
pub protos: Vec<Rc<FunctionProto>>,
|
||||||
|
pub upvalue_count: u8,
|
||||||
|
/// Upvalue descriptors for this function (for Closure opcode emission)
|
||||||
|
pub upvalues: Vec<(bool, u8)>, // (is_local, index)
|
||||||
|
pub lines: Vec<(usize, usize)>, // (bytecode_offset, source_line)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionProto {
|
||||||
|
pub fn new(name: Option<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
arity: 0,
|
||||||
|
code: Vec::new(),
|
||||||
|
constants: Vec::new(),
|
||||||
|
protos: Vec::new(),
|
||||||
|
upvalue_count: 0,
|
||||||
|
upvalues: Vec::new(),
|
||||||
|
lines: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_constant(&mut self, val: Value) -> u16 {
|
||||||
|
// Check for existing identical constant
|
||||||
|
for (i, c) in self.constants.iter().enumerate() {
|
||||||
|
if values_eq(c, &val) {
|
||||||
|
return i as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let idx = self.constants.len();
|
||||||
|
self.constants.push(val);
|
||||||
|
idx as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn values_eq(a: &Value, b: &Value) -> bool {
|
||||||
|
match (a, b) {
|
||||||
|
(Value::Number(x), Value::Number(y)) => (x - y).abs() < f64::EPSILON,
|
||||||
|
(Value::String(x), Value::String(y)) => x == y,
|
||||||
|
(Value::Bool(x), Value::Bool(y)) => x == y,
|
||||||
|
(Value::Nil, Value::Nil) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Compiler
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Local {
|
||||||
|
name: String,
|
||||||
|
depth: u8, // scope depth where declared; 0 = uninitialized
|
||||||
|
is_captured: bool,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
is_const: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Upvalue {
|
||||||
|
index: u8,
|
||||||
|
is_local: bool, // true = captured from enclosing fn's local; false = from upvalue
|
||||||
|
name: String, // variable name (for transitive upvalue resolution)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoopContext {
|
||||||
|
break_patches: Vec<usize>,
|
||||||
|
continue_patches: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Compiler {
|
||||||
|
function: FunctionProto,
|
||||||
|
locals: Vec<Local>,
|
||||||
|
/// Upvalues for this function (shared with child for transitive resolution)
|
||||||
|
upvalues: Rc<RefCell<Vec<Upvalue>>>,
|
||||||
|
/// ALL enclosing locals from entire chain (merged, direct parent first)
|
||||||
|
enclosing_locals: Option<Vec<Local>>,
|
||||||
|
/// Direct parent's own locals count (to distinguish parent locals from deeper ones)
|
||||||
|
parent_locals_count: usize,
|
||||||
|
/// Direct parent's upvalues (shared)
|
||||||
|
enclosing_upvalues: Option<Rc<RefCell<Vec<Upvalue>>>>,
|
||||||
|
/// Grandparent's upvalues (shared, for 3-level transitive capture)
|
||||||
|
grandparent_upvalues: Option<Rc<RefCell<Vec<Upvalue>>>>,
|
||||||
|
scope_depth: u8,
|
||||||
|
loop_stack: Vec<LoopContext>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Compiler {
|
||||||
|
pub fn new(name: Option<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
function: FunctionProto::new(name),
|
||||||
|
locals: Vec::new(),
|
||||||
|
upvalues: Rc::new(RefCell::new(Vec::new())),
|
||||||
|
enclosing_locals: None,
|
||||||
|
parent_locals_count: 0,
|
||||||
|
enclosing_upvalues: None,
|
||||||
|
grandparent_upvalues: None,
|
||||||
|
scope_depth: 0,
|
||||||
|
loop_stack: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience: compile a list of statements into a FunctionProto.
|
||||||
|
pub fn compile(stmts: &[Stmt]) -> Result<FunctionProto, RuntimeError> {
|
||||||
|
let mut compiler = Self::new(None);
|
||||||
|
for stmt in stmts {
|
||||||
|
compiler.compile_stmt(stmt)?;
|
||||||
|
}
|
||||||
|
// Implicit return nil at end of function
|
||||||
|
compiler.emit_op(OpCode::LoadNil);
|
||||||
|
compiler.emit_op(OpCode::Return);
|
||||||
|
Ok(compiler.function)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile a list of statements (for use from nested compilers).
|
||||||
|
fn compile_stmts(&mut self, stmts: &[Stmt]) -> Result<(), RuntimeError> {
|
||||||
|
for stmt in stmts {
|
||||||
|
self.compile_stmt(stmt)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Statement compilation
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), RuntimeError> {
|
||||||
|
match stmt {
|
||||||
|
Stmt::Let { name, initializer, mutable } => {
|
||||||
|
self.compile_let(name, initializer, *mutable)?;
|
||||||
|
}
|
||||||
|
Stmt::ExprStmt(expr) => {
|
||||||
|
self.compile_expr(expr)?;
|
||||||
|
self.emit_op(OpCode::Pop);
|
||||||
|
}
|
||||||
|
Stmt::Block(stmts) => {
|
||||||
|
self.begin_scope();
|
||||||
|
self.compile_stmts(stmts)?;
|
||||||
|
self.end_scope();
|
||||||
|
}
|
||||||
|
Stmt::If { condition, then_branch, else_branch } => {
|
||||||
|
self.compile_if(condition, then_branch, else_branch.as_deref())?;
|
||||||
|
}
|
||||||
|
Stmt::While { condition, body } => {
|
||||||
|
self.compile_while(condition, body)?;
|
||||||
|
}
|
||||||
|
Stmt::For { initializer, condition, step, body } => {
|
||||||
|
self.compile_for(initializer.as_deref(), condition.as_ref(), step.as_ref(), body)?;
|
||||||
|
}
|
||||||
|
Stmt::ForIn { var_name, iterable, body } => {
|
||||||
|
self.compile_for_in(var_name, iterable, body)?;
|
||||||
|
}
|
||||||
|
Stmt::Function { name, params, body } => {
|
||||||
|
self.compile_function_decl(name, params, body)?;
|
||||||
|
// DefineGlobal pushes the value back; pop it as this is a statement
|
||||||
|
self.emit_op(OpCode::Pop);
|
||||||
|
}
|
||||||
|
Stmt::Return(expr) => {
|
||||||
|
if let Some(e) = expr {
|
||||||
|
self.compile_expr(e)?;
|
||||||
|
} else {
|
||||||
|
self.emit_op(OpCode::LoadNil);
|
||||||
|
}
|
||||||
|
self.emit_op(OpCode::Return);
|
||||||
|
}
|
||||||
|
Stmt::Break => {
|
||||||
|
self.compile_break()?;
|
||||||
|
}
|
||||||
|
Stmt::Continue => {
|
||||||
|
self.compile_continue()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_let(&mut self, name: &str, initializer: &Expr, mutable: bool) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(initializer)?;
|
||||||
|
if self.scope_depth == 0 {
|
||||||
|
let name_idx = self.add_string_constant(name);
|
||||||
|
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx);
|
||||||
|
// DefineGlobal pushes value back; pop it for statement-level let
|
||||||
|
self.emit_op(OpCode::Pop);
|
||||||
|
} else {
|
||||||
|
let slot = self.locals.len() as u8;
|
||||||
|
self.locals.push(Local {
|
||||||
|
name: name.to_string(),
|
||||||
|
depth: self.scope_depth,
|
||||||
|
is_captured: false,
|
||||||
|
is_const: !mutable,
|
||||||
|
});
|
||||||
|
// StoreLocal PEEKS the value — it stays on stack as the local
|
||||||
|
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_if(&mut self, condition: &Expr, then_branch: &Stmt, else_branch: Option<&Stmt>) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(condition)?;
|
||||||
|
let else_jump = self.emit_jump(OpCode::JumpIfFalse);
|
||||||
|
self.compile_stmt(then_branch)?;
|
||||||
|
if let Some(else_stmt) = else_branch {
|
||||||
|
let end_jump = self.emit_jump(OpCode::Jump);
|
||||||
|
self.patch_jump(else_jump);
|
||||||
|
self.compile_stmt(else_stmt)?;
|
||||||
|
self.patch_jump(end_jump);
|
||||||
|
} else {
|
||||||
|
self.patch_jump(else_jump);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_while(&mut self, condition: &Expr, body: &Stmt) -> Result<(), RuntimeError> {
|
||||||
|
let start_ip = self.function.code.len();
|
||||||
|
self.compile_expr(condition)?;
|
||||||
|
let exit_jump = self.emit_jump(OpCode::JumpIfFalse);
|
||||||
|
|
||||||
|
self.loop_stack.push(LoopContext {
|
||||||
|
break_patches: Vec::new(),
|
||||||
|
continue_patches: Vec::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.compile_stmt(body)?;
|
||||||
|
self.emit_loop_jump(start_ip);
|
||||||
|
|
||||||
|
self.patch_jump(exit_jump);
|
||||||
|
|
||||||
|
let loop_ctx = self.loop_stack.pop().unwrap();
|
||||||
|
for patch in loop_ctx.break_patches {
|
||||||
|
self.patch_jump(patch);
|
||||||
|
}
|
||||||
|
for patch in loop_ctx.continue_patches {
|
||||||
|
self.patch_jump_to(patch, start_ip);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_for(&mut self, initializer: Option<&Stmt>, condition: Option<&Expr>, step: Option<&Expr>, body: &Stmt) -> Result<(), RuntimeError> {
|
||||||
|
// For loop: new scope for init
|
||||||
|
self.begin_scope();
|
||||||
|
|
||||||
|
if let Some(init) = initializer {
|
||||||
|
self.compile_stmt(init)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_ip = self.function.code.len();
|
||||||
|
if let Some(cond) = condition {
|
||||||
|
self.compile_expr(cond)?;
|
||||||
|
} else {
|
||||||
|
self.emit_op(OpCode::LoadTrue);
|
||||||
|
}
|
||||||
|
let exit_jump = self.emit_jump(OpCode::JumpIfFalse);
|
||||||
|
|
||||||
|
self.loop_stack.push(LoopContext {
|
||||||
|
break_patches: Vec::new(),
|
||||||
|
continue_patches: Vec::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.compile_stmt(body)?;
|
||||||
|
|
||||||
|
// continue lands here (after body, before step)
|
||||||
|
let continue_ip = self.function.code.len();
|
||||||
|
let loop_ctx = self.loop_stack.pop().unwrap();
|
||||||
|
for patch in loop_ctx.continue_patches {
|
||||||
|
self.patch_jump_to(patch, continue_ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = step {
|
||||||
|
self.compile_expr(s)?;
|
||||||
|
self.emit_op(OpCode::Pop);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emit_loop_jump(start_ip);
|
||||||
|
self.patch_jump(exit_jump);
|
||||||
|
|
||||||
|
for patch in loop_ctx.break_patches {
|
||||||
|
self.patch_jump(patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.end_scope();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_for_in(&mut self, var_name: &str, iterable: &Expr, body: &Stmt) -> Result<(), RuntimeError> {
|
||||||
|
// 1. New scope for hidden iterator locals (items, idx)
|
||||||
|
self.begin_scope();
|
||||||
|
|
||||||
|
// 2. Evaluate iterable and compute items array
|
||||||
|
self.compile_expr(iterable)?;
|
||||||
|
emit_op(&mut self.function.code, OpCode::ForInInit); // pops iterable, pushes items[]
|
||||||
|
|
||||||
|
// 3. Store items array in a hidden local (peek, no Pop — cleaned by end_scope)
|
||||||
|
let items_slot = self.locals.len() as u8;
|
||||||
|
self.locals.push(Local {
|
||||||
|
name: format!("__iter_items_{}", items_slot),
|
||||||
|
depth: self.scope_depth,
|
||||||
|
is_captured: false,
|
||||||
|
is_const: false,
|
||||||
|
});
|
||||||
|
emit_u8(&mut self.function.code, OpCode::StoreLocal, items_slot);
|
||||||
|
|
||||||
|
// 4. Initialize index = 0 in hidden local
|
||||||
|
let idx_slot = self.locals.len() as u8;
|
||||||
|
self.locals.push(Local {
|
||||||
|
name: format!("__iter_idx_{}", idx_slot),
|
||||||
|
depth: self.scope_depth,
|
||||||
|
is_captured: false,
|
||||||
|
is_const: false,
|
||||||
|
});
|
||||||
|
let zero_idx = self.add_constant(Value::Number(0.0));
|
||||||
|
emit_u16(&mut self.function.code, OpCode::LoadConst, zero_idx);
|
||||||
|
emit_u8(&mut self.function.code, OpCode::StoreLocal, idx_slot);
|
||||||
|
|
||||||
|
// 5. ForInNext: reads items[idx_slot], idx[idx_slot], pushes element
|
||||||
|
let forin_loc = self.emit_forin_next(items_slot, idx_slot);
|
||||||
|
|
||||||
|
// 6. Store loop variable in a local (peek, value stays on stack)
|
||||||
|
let loop_var_slot = self.locals.len() as u8;
|
||||||
|
self.locals.push(Local {
|
||||||
|
name: var_name.to_string(),
|
||||||
|
depth: self.scope_depth,
|
||||||
|
is_captured: false,
|
||||||
|
is_const: false,
|
||||||
|
});
|
||||||
|
emit_u8(&mut self.function.code, OpCode::StoreLocal, loop_var_slot);
|
||||||
|
|
||||||
|
// 7. Loop body (can resolve var_name to loop_var_slot)
|
||||||
|
self.loop_stack.push(LoopContext {
|
||||||
|
break_patches: Vec::new(),
|
||||||
|
continue_patches: Vec::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.compile_stmt(body)?;
|
||||||
|
|
||||||
|
// Patch continue
|
||||||
|
let continue_ip = self.function.code.len();
|
||||||
|
let loop_ctx = self.loop_stack.pop().unwrap();
|
||||||
|
for patch in loop_ctx.continue_patches {
|
||||||
|
self.patch_jump_to(patch, continue_ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Pop loop var element and remove from tracking
|
||||||
|
self.emit_op(OpCode::Pop);
|
||||||
|
self.locals.pop(); // loop_var_slot
|
||||||
|
|
||||||
|
// 9. Jump back to ForInNext (next iteration re-adds loop var via StoreLocal)
|
||||||
|
self.emit_loop_jump(forin_loc);
|
||||||
|
|
||||||
|
// 10. Exit target: patch ForInNext exit and break jumps
|
||||||
|
let exit_ip = self.function.code.len();
|
||||||
|
self.patch_forin_jump(forin_loc, exit_ip);
|
||||||
|
for patch in loop_ctx.break_patches {
|
||||||
|
self.patch_jump_to(patch, exit_ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11. Pop loop var (ForInNext pushes Nil on exit; break leaves element on stack)
|
||||||
|
self.emit_op(OpCode::Pop);
|
||||||
|
|
||||||
|
// 12. End scope: pops items_slot + idx_slot values (left by StoreLocal peek)
|
||||||
|
self.end_scope();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_function_decl(&mut self, name: &str, params: &[String], body: &[Stmt]) -> Result<(), RuntimeError> {
|
||||||
|
let proto = self.compile_nested_function(Some(name.to_string()), params, body)?;
|
||||||
|
let upvalues = proto.upvalues.clone();
|
||||||
|
let proto_idx = self.add_function_proto_constant(proto);
|
||||||
|
self.emit_closure(proto_idx, &upvalues);
|
||||||
|
|
||||||
|
// Bind to name
|
||||||
|
if self.scope_depth == 0 {
|
||||||
|
let name_idx = self.add_string_constant(name);
|
||||||
|
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx);
|
||||||
|
} else {
|
||||||
|
let slot = self.locals.len() as u8;
|
||||||
|
self.locals.push(Local {
|
||||||
|
name: name.to_string(),
|
||||||
|
depth: self.scope_depth,
|
||||||
|
is_captured: false,
|
||||||
|
is_const: false,
|
||||||
|
});
|
||||||
|
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_break(&mut self) -> Result<(), RuntimeError> {
|
||||||
|
let jump_loc = self.emit_jump_placeholder();
|
||||||
|
let loop_ctx = self.loop_stack.last_mut().ok_or_else(|| RuntimeError::RuntimeError {
|
||||||
|
message: "break outside of loop".into(),
|
||||||
|
token: None,
|
||||||
|
})?;
|
||||||
|
loop_ctx.break_patches.push(jump_loc);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_continue(&mut self) -> Result<(), RuntimeError> {
|
||||||
|
let jump_loc = self.emit_jump_placeholder();
|
||||||
|
let loop_ctx = self.loop_stack.last_mut().ok_or_else(|| RuntimeError::RuntimeError {
|
||||||
|
message: "continue outside of loop".into(),
|
||||||
|
token: None,
|
||||||
|
})?;
|
||||||
|
loop_ctx.continue_patches.push(jump_loc);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Expression compilation
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
fn compile_expr(&mut self, expr: &Expr) -> Result<(), RuntimeError> {
|
||||||
|
match expr {
|
||||||
|
Expr::Literal(lit) => self.compile_literal(lit),
|
||||||
|
Expr::Variable(name) => self.compile_variable(name),
|
||||||
|
Expr::Assign { name, op, value } => self.compile_assign(name, op, value),
|
||||||
|
Expr::Get { object, name } => self.compile_get(object, name),
|
||||||
|
Expr::Set { object, name, op, value } => self.compile_set(object, name, op, value),
|
||||||
|
Expr::ObjectLiteral { properties } => self.compile_object(properties),
|
||||||
|
Expr::ArrayLiteral { elements } => self.compile_array(elements),
|
||||||
|
Expr::IndexGet { array, index } => self.compile_index_get(array, index),
|
||||||
|
Expr::IndexSet { array, index, op, value } => self.compile_index_set(array, index, op, value),
|
||||||
|
Expr::Unary { op, right } => self.compile_unary(op, right),
|
||||||
|
Expr::Binary { left, op, right } => self.compile_binary(left, op, right),
|
||||||
|
Expr::Logical { left, op, right } => self.compile_logical(left, op, right),
|
||||||
|
Expr::Ternary { condition, then_branch, else_branch } => {
|
||||||
|
self.compile_ternary(condition, then_branch, else_branch)
|
||||||
|
}
|
||||||
|
Expr::Call { callee, arguments } => self.compile_call(callee, arguments),
|
||||||
|
Expr::Lambda { params, body } => self.compile_lambda(params, body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_literal(&mut self, lit: &Literal) -> Result<(), RuntimeError> {
|
||||||
|
match lit {
|
||||||
|
Literal::Number(n) => {
|
||||||
|
let idx = self.function.add_constant(Value::Number(*n));
|
||||||
|
emit_u16(&mut self.function.code, OpCode::LoadConst, idx);
|
||||||
|
}
|
||||||
|
Literal::String(s) => {
|
||||||
|
let idx = self.function.add_constant(Value::String(s.clone()));
|
||||||
|
emit_u16(&mut self.function.code, OpCode::LoadConst, idx);
|
||||||
|
}
|
||||||
|
Literal::Bool(true) => self.emit_op(OpCode::LoadTrue),
|
||||||
|
Literal::Bool(false) => self.emit_op(OpCode::LoadFalse),
|
||||||
|
Literal::Nil => self.emit_op(OpCode::LoadNil),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_variable(&mut self, name: &str) -> Result<(), RuntimeError> {
|
||||||
|
// Try to resolve as local
|
||||||
|
if let Some(slot) = self.resolve_local(name) {
|
||||||
|
emit_u8(&mut self.function.code, OpCode::LoadLocal, slot);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// Try to resolve as upvalue
|
||||||
|
if let Some(upvalue_idx) = self.resolve_upvalue(name) {
|
||||||
|
emit_u8(&mut self.function.code, OpCode::LoadUpvalue, upvalue_idx);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// Fall back to global
|
||||||
|
let name_idx = self.add_string_constant(name);
|
||||||
|
emit_u16(&mut self.function.code, OpCode::LoadGlobal, name_idx);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_assign(&mut self, name: &str, op: &AssignOp, value: &Expr) -> Result<(), RuntimeError> {
|
||||||
|
if *op == AssignOp::Equal {
|
||||||
|
// Simple assignment
|
||||||
|
self.compile_expr(value)?;
|
||||||
|
if let Some(slot) = self.resolve_local(name) {
|
||||||
|
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
|
||||||
|
} else if let Some(uv_idx) = self.resolve_upvalue(name) {
|
||||||
|
emit_u8(&mut self.function.code, OpCode::StoreUpvalue, uv_idx);
|
||||||
|
} else {
|
||||||
|
let name_idx = self.add_string_constant(name);
|
||||||
|
emit_u16(&mut self.function.code, OpCode::StoreGlobal, name_idx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Compound assignment
|
||||||
|
self.compile_expr(value)?;
|
||||||
|
let compound_op = match op {
|
||||||
|
AssignOp::PlusEqual => CompoundOp::PlusEqual,
|
||||||
|
AssignOp::MinusEqual => CompoundOp::MinusEqual,
|
||||||
|
AssignOp::StarEqual => CompoundOp::StarEqual,
|
||||||
|
AssignOp::SlashEqual => CompoundOp::SlashEqual,
|
||||||
|
AssignOp::PercentEqual => CompoundOp::PercentEqual,
|
||||||
|
AssignOp::Equal => unreachable!(),
|
||||||
|
};
|
||||||
|
if let Some(slot) = self.resolve_local(name) {
|
||||||
|
emit_u8(&mut self.function.code, OpCode::CompoundAssignLocal, slot);
|
||||||
|
self.function.code.push(compound_op as u8);
|
||||||
|
} else if self.resolve_upvalue(name).is_some() {
|
||||||
|
// Compound assign on upvalue: load upvalue, compound op, store
|
||||||
|
// For simplicity, use a workaround: re-emit this as a separate step
|
||||||
|
// TODO: add CompoundAssignUpvalue opcode
|
||||||
|
return Err(RuntimeError::RuntimeError {
|
||||||
|
message: "Compound assignment on upvalue not yet supported".into(),
|
||||||
|
token: None,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let name_idx = self.add_string_constant(name);
|
||||||
|
emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx);
|
||||||
|
self.function.code.push(compound_op as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_get(&mut self, object: &Expr, name: &str) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(object)?;
|
||||||
|
let name_idx = self.add_string_constant(name);
|
||||||
|
emit_u16(&mut self.function.code, OpCode::GetProperty, name_idx);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_set(&mut self, object: &Expr, name: &str, op: &AssignOp, value: &Expr) -> Result<(), RuntimeError> {
|
||||||
|
if *op == AssignOp::Equal {
|
||||||
|
self.compile_expr(object)?;
|
||||||
|
self.compile_expr(value)?;
|
||||||
|
let name_idx = self.add_string_constant(name);
|
||||||
|
emit_u16(&mut self.function.code, OpCode::SetProperty, name_idx);
|
||||||
|
} else {
|
||||||
|
// Compound property set: object.name op= value
|
||||||
|
// Strategy: load object, dup, get property as current value,
|
||||||
|
// load rhs, apply op, set property
|
||||||
|
self.compile_expr(object)?;
|
||||||
|
self.emit_op(OpCode::Dup);
|
||||||
|
let name_idx = self.add_string_constant(name);
|
||||||
|
emit_u16(&mut self.function.code, OpCode::GetProperty, name_idx); // current value
|
||||||
|
self.compile_expr(value)?; // rhs
|
||||||
|
let compound_op = assign_op_to_compound(op);
|
||||||
|
self.function.code.push(compound_op as u8);
|
||||||
|
// Now stack: object, current_val, rhs → set property
|
||||||
|
emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx);
|
||||||
|
self.function.code.push(compound_op as u8);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_object(&mut self, properties: &[(String, Expr)]) -> Result<(), RuntimeError> {
|
||||||
|
emit_op(&mut self.function.code, OpCode::NewObject);
|
||||||
|
for (key, value_expr) in properties {
|
||||||
|
self.emit_op(OpCode::Dup);
|
||||||
|
self.compile_expr(value_expr)?;
|
||||||
|
let name_idx = self.add_string_constant(key);
|
||||||
|
emit_u16(&mut self.function.code, OpCode::SetProperty, name_idx);
|
||||||
|
// SetProperty leaves the value on stack; we need the object, so Pop the value
|
||||||
|
self.emit_op(OpCode::Pop);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_array(&mut self, elements: &[Expr]) -> Result<(), RuntimeError> {
|
||||||
|
for e in elements {
|
||||||
|
self.compile_expr(e)?;
|
||||||
|
}
|
||||||
|
emit_u16(&mut self.function.code, OpCode::NewArray, elements.len() as u16);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_index_get(&mut self, array: &Expr, index: &Expr) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(array)?;
|
||||||
|
self.compile_expr(index)?;
|
||||||
|
emit_op(&mut self.function.code, OpCode::GetIndex);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_index_set(&mut self, array: &Expr, index: &Expr, op: &AssignOp, value: &Expr) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(array)?;
|
||||||
|
self.compile_expr(index)?;
|
||||||
|
self.compile_expr(value)?;
|
||||||
|
if *op == AssignOp::Equal {
|
||||||
|
emit_op(&mut self.function.code, OpCode::SetIndex);
|
||||||
|
} else {
|
||||||
|
let compound_op = assign_op_to_compound(op);
|
||||||
|
emit_u8(&mut self.function.code, OpCode::CompoundAssignIndex, compound_op as u8);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_unary(&mut self, op: &UnaryOp, right: &Expr) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(right)?;
|
||||||
|
match op {
|
||||||
|
UnaryOp::Negate => self.emit_op(OpCode::Negate),
|
||||||
|
UnaryOp::Not => self.emit_op(OpCode::Not),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_binary(&mut self, left: &Expr, op: &BinaryOp, right: &Expr) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(left)?;
|
||||||
|
self.compile_expr(right)?;
|
||||||
|
let opcode = match op {
|
||||||
|
BinaryOp::Add => OpCode::Add,
|
||||||
|
BinaryOp::Sub => OpCode::Sub,
|
||||||
|
BinaryOp::Mul => OpCode::Mul,
|
||||||
|
BinaryOp::Div => OpCode::Div,
|
||||||
|
BinaryOp::Mod => OpCode::Mod,
|
||||||
|
BinaryOp::Greater => OpCode::Greater,
|
||||||
|
BinaryOp::GreaterEqual => OpCode::GreaterEqual,
|
||||||
|
BinaryOp::Less => OpCode::Less,
|
||||||
|
BinaryOp::LessEqual => OpCode::LessEqual,
|
||||||
|
BinaryOp::Equal => OpCode::Equal,
|
||||||
|
BinaryOp::NotEqual => OpCode::NotEqual,
|
||||||
|
};
|
||||||
|
self.emit_op(opcode);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_logical(&mut self, left: &Expr, op: &LogicalOp, right: &Expr) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(left)?;
|
||||||
|
match op {
|
||||||
|
LogicalOp::And => {
|
||||||
|
// Short-circuit: if left is falsy, skip right and return left
|
||||||
|
let end_jump = self.emit_jump(OpCode::PopJumpIfFalse);
|
||||||
|
self.emit_op(OpCode::Pop); // discard left (truthy)
|
||||||
|
self.compile_expr(right)?;
|
||||||
|
self.patch_jump(end_jump);
|
||||||
|
}
|
||||||
|
LogicalOp::Or => {
|
||||||
|
// Short-circuit: if left is truthy, skip right and return left
|
||||||
|
self.emit_op(OpCode::Dup);
|
||||||
|
let end_jump = self.emit_jump(OpCode::JumpIfTrue);
|
||||||
|
self.emit_op(OpCode::Pop); // discard left (falsy)
|
||||||
|
self.compile_expr(right)?;
|
||||||
|
self.patch_jump(end_jump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_ternary(&mut self, condition: &Expr, then_branch: &Expr, else_branch: &Expr) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(condition)?;
|
||||||
|
let else_jump = self.emit_jump(OpCode::JumpIfFalse);
|
||||||
|
self.compile_expr(then_branch)?;
|
||||||
|
let end_jump = self.emit_jump(OpCode::Jump);
|
||||||
|
self.patch_jump(else_jump);
|
||||||
|
self.compile_expr(else_branch)?;
|
||||||
|
self.patch_jump(end_jump);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_call(&mut self, callee: &Expr, arguments: &[Expr]) -> Result<(), RuntimeError> {
|
||||||
|
self.compile_expr(callee)?;
|
||||||
|
for arg in arguments {
|
||||||
|
self.compile_expr(arg)?;
|
||||||
|
}
|
||||||
|
emit_u8(&mut self.function.code, OpCode::Call, arguments.len() as u8);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_lambda(&mut self, params: &[String], body: &[Stmt]) -> Result<(), RuntimeError> {
|
||||||
|
let proto = self.compile_nested_function(None, params, body)?;
|
||||||
|
let upvalues = proto.upvalues.clone();
|
||||||
|
let proto_idx = self.add_function_proto_constant(proto);
|
||||||
|
self.emit_closure(proto_idx, &upvalues);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile a nested function (lambda or function declaration) and return its proto.
|
||||||
|
fn compile_nested_function(&mut self, name: Option<String>, params: &[String], body: &[Stmt]) -> Result<FunctionProto, RuntimeError> {
|
||||||
|
let mut child = Compiler::new(name);
|
||||||
|
// Build merged enclosing locals: our locals + our enclosing chain
|
||||||
|
let mut all_locals = self.locals.clone();
|
||||||
|
if let Some(ref enc) = self.enclosing_locals {
|
||||||
|
all_locals.extend(enc.iter().cloned());
|
||||||
|
}
|
||||||
|
child.enclosing_locals = Some(all_locals);
|
||||||
|
child.parent_locals_count = self.locals.len();
|
||||||
|
child.enclosing_upvalues = Some(Rc::clone(&self.upvalues));
|
||||||
|
child.grandparent_upvalues = self.enclosing_upvalues.clone();
|
||||||
|
|
||||||
|
// Add params as locals without StoreLocal — they're already on stack from Call
|
||||||
|
for param in params {
|
||||||
|
child.locals.push(Local {
|
||||||
|
name: param.clone(),
|
||||||
|
depth: 1, // params are at scope depth 1 (function body)
|
||||||
|
is_captured: false,
|
||||||
|
is_const: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
child.function.arity = params.len() as u8;
|
||||||
|
|
||||||
|
// Compile the body
|
||||||
|
child.scope_depth = 1;
|
||||||
|
for stmt in body {
|
||||||
|
child.compile_stmt(stmt)?;
|
||||||
|
}
|
||||||
|
// Implicit return nil
|
||||||
|
child.emit_op(OpCode::LoadNil);
|
||||||
|
child.emit_op(OpCode::Return);
|
||||||
|
|
||||||
|
// Mark captured locals in the parent compiler
|
||||||
|
for uv in child.upvalues.borrow().iter() {
|
||||||
|
if uv.is_local {
|
||||||
|
if let Some(local) = self.locals.get_mut(uv.index as usize) {
|
||||||
|
local.is_captured = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let child_upvalues = child.upvalues.borrow();
|
||||||
|
child.function.upvalue_count = child_upvalues.len() as u8;
|
||||||
|
child.function.upvalues = child_upvalues.iter().map(|uv| (uv.is_local, uv.index)).collect();
|
||||||
|
drop(child_upvalues);
|
||||||
|
Ok(child.function)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Scope management
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
fn begin_scope(&mut self) {
|
||||||
|
self.scope_depth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_scope(&mut self) {
|
||||||
|
self.scope_depth -= 1;
|
||||||
|
// Pop locals that are going out of scope (except captured ones)
|
||||||
|
let mut pop_count = 0u8;
|
||||||
|
while let Some(local) = self.locals.last() {
|
||||||
|
if local.depth > self.scope_depth {
|
||||||
|
if local.is_captured {
|
||||||
|
// Don't pop — the upvalue still needs it on the stack
|
||||||
|
// The VM will close the upvalue when the function returns
|
||||||
|
self.locals.pop();
|
||||||
|
} else {
|
||||||
|
self.locals.pop();
|
||||||
|
pop_count += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _ in 0..pop_count {
|
||||||
|
self.emit_op(OpCode::Pop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Variable resolution
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/// Find a local variable by name, returning its slot index.
|
||||||
|
fn resolve_local(&self, name: &str) -> Option<u8> {
|
||||||
|
for (i, local) in self.locals.iter().enumerate().rev() {
|
||||||
|
if local.name == name && local.depth > 0 {
|
||||||
|
return Some(i as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to resolve a variable as an upvalue from enclosing functions.
|
||||||
|
/// Returns the upvalue index in this function's upvalues list.
|
||||||
|
fn resolve_upvalue(&mut self, name: &str) -> Option<u8> {
|
||||||
|
// Check if already captured
|
||||||
|
for (j, uv) in self.upvalues.borrow().iter().enumerate() {
|
||||||
|
if uv.name == name {
|
||||||
|
return Some(j as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check all enclosing locals (merged chain: parent, grandparent, ...)
|
||||||
|
if let Some(ref enclosing_locals) = self.enclosing_locals {
|
||||||
|
for (i, local) in enclosing_locals.iter().enumerate().rev() {
|
||||||
|
if local.name == name && local.depth > 0 {
|
||||||
|
if i < self.parent_locals_count {
|
||||||
|
// Found in direct parent's locals → capture as upvalue
|
||||||
|
let idx = self.upvalues.borrow().len() as u8;
|
||||||
|
self.upvalues.borrow_mut().push(Upvalue {
|
||||||
|
index: i as u8, is_local: true, name: name.to_string()
|
||||||
|
});
|
||||||
|
return Some(idx);
|
||||||
|
} else {
|
||||||
|
// Found deeper than parent. Need to create upvalue chain.
|
||||||
|
if let Some(ref enc_upvalues) = self.enclosing_upvalues {
|
||||||
|
// Check if parent already has this upvalue
|
||||||
|
let parent_uv_idx = {
|
||||||
|
let enc = enc_upvalues.borrow();
|
||||||
|
enc.iter().position(|uv| uv.name == name).map(|p| p as u8)
|
||||||
|
};
|
||||||
|
let parent_idx = match parent_uv_idx {
|
||||||
|
Some(idx) => idx,
|
||||||
|
None => {
|
||||||
|
// Add upvalue to parent's list.
|
||||||
|
// The parent sees this variable at a certain index in its own
|
||||||
|
// enclosing_locals. That index is (i - self.parent_locals_count)
|
||||||
|
// in the merged list, which corresponds to the same variable
|
||||||
|
// in the parent's enclosing_locals.
|
||||||
|
let enc_idx = enc_upvalues.borrow().len() as u8;
|
||||||
|
|
||||||
|
// If we have grandparent_upvalues, the parent's upvalue
|
||||||
|
// should be transitive (is_local=false), and we need to
|
||||||
|
// ensure grandparent has it too.
|
||||||
|
if let Some(ref gp_upvalues) = self.grandparent_upvalues {
|
||||||
|
// Ensure grandparent has the upvalue first
|
||||||
|
let gp_idx = {
|
||||||
|
let gp = gp_upvalues.borrow();
|
||||||
|
gp.iter().position(|uv| uv.name == name).map(|p| p as u8)
|
||||||
|
};
|
||||||
|
let gp_idx = match gp_idx {
|
||||||
|
Some(idx) => idx,
|
||||||
|
None => {
|
||||||
|
let idx = gp_upvalues.borrow().len() as u8;
|
||||||
|
// Grandparent captures this as a local
|
||||||
|
// (it's directly in the grandparent's enclosing scope)
|
||||||
|
gp_upvalues.borrow_mut().push(Upvalue {
|
||||||
|
index: (i - self.parent_locals_count) as u8,
|
||||||
|
is_local: true,
|
||||||
|
name: name.to_string()
|
||||||
|
});
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Parent's upvalue is transitive through grandparent
|
||||||
|
enc_upvalues.borrow_mut().push(Upvalue {
|
||||||
|
index: gp_idx,
|
||||||
|
is_local: false,
|
||||||
|
name: name.to_string()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// No grandparent — parent captures directly as local
|
||||||
|
enc_upvalues.borrow_mut().push(Upvalue {
|
||||||
|
index: (i - self.parent_locals_count) as u8,
|
||||||
|
is_local: true,
|
||||||
|
name: name.to_string()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
enc_idx
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Add transitive upvalue in self pointing to parent's
|
||||||
|
let idx = self.upvalues.borrow().len() as u8;
|
||||||
|
self.upvalues.borrow_mut().push(Upvalue {
|
||||||
|
index: parent_idx, is_local: false, name: name.to_string()
|
||||||
|
});
|
||||||
|
return Some(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check parent's upvalues (transitive closure over 2 levels)
|
||||||
|
if let Some(ref enclosing_upvalues) = self.enclosing_upvalues {
|
||||||
|
for uv in enclosing_upvalues.borrow().iter().rev() {
|
||||||
|
if uv.name == name {
|
||||||
|
let idx = self.upvalues.borrow().len() as u8;
|
||||||
|
self.upvalues.borrow_mut().push(Upvalue {
|
||||||
|
index: uv.index, is_local: false, name: name.to_string()
|
||||||
|
});
|
||||||
|
return Some(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Bytecode emission helpers
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
fn emit_op(&mut self, op: OpCode) {
|
||||||
|
emit_op(&mut self.function.code, op);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_jump(&mut self, op: OpCode) -> usize {
|
||||||
|
let loc = self.function.code.len();
|
||||||
|
emit_i16(&mut self.function.code, op, 0x7FFF); // placeholder
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_jump_placeholder(&mut self) -> usize {
|
||||||
|
let loc = self.function.code.len();
|
||||||
|
emit_i16(&mut self.function.code, OpCode::Jump, 0x7FFF);
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_loop_jump(&mut self, target: usize) {
|
||||||
|
let offset = target as isize - self.function.code.len() as isize;
|
||||||
|
emit_i16(&mut self.function.code, OpCode::Jump, offset as i16);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_forin_next(&mut self, items_slot: u8, idx_slot: u8) -> usize {
|
||||||
|
let loc = self.function.code.len();
|
||||||
|
let code = &mut self.function.code;
|
||||||
|
code.push(OpCode::ForInNext as u8);
|
||||||
|
code.push(items_slot);
|
||||||
|
code.push(idx_slot);
|
||||||
|
code.push(0xFF); // placeholder offset low
|
||||||
|
code.push(0x7F); // placeholder offset high
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_closure(&mut self, proto_idx: u16, upvalues: &[(bool, u8)]) {
|
||||||
|
let code = &mut self.function.code;
|
||||||
|
code.push(OpCode::Closure as u8);
|
||||||
|
code.push((proto_idx & 0xFF) as u8);
|
||||||
|
code.push(((proto_idx >> 8) & 0xFF) as u8);
|
||||||
|
let upvalue_count = upvalues.len() as u8;
|
||||||
|
code.push(upvalue_count);
|
||||||
|
for &(is_local, index) in upvalues {
|
||||||
|
code.push(if is_local { 1u8 } else { 0u8 });
|
||||||
|
code.push(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn patch_jump(&mut self, jump_loc: usize) {
|
||||||
|
let offset = (self.function.code.len() - jump_loc) as i16;
|
||||||
|
let code = &mut self.function.code;
|
||||||
|
code[jump_loc + 1] = (offset & 0xFF) as u8;
|
||||||
|
code[jump_loc + 2] = ((offset >> 8) & 0xFF) as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn patch_jump_to(&mut self, jump_loc: usize, target: usize) {
|
||||||
|
let offset = target as isize - jump_loc as isize;
|
||||||
|
let code = &mut self.function.code;
|
||||||
|
code[jump_loc + 1] = (offset & 0xFF) as u8;
|
||||||
|
code[jump_loc + 2] = ((offset >> 8) & 0xFF) as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Patch ForInNext's exit offset (at jump_loc + 3, +4)
|
||||||
|
fn patch_forin_jump(&mut self, forin_loc: usize, target: usize) {
|
||||||
|
let offset = target as isize - forin_loc as isize;
|
||||||
|
let code = &mut self.function.code;
|
||||||
|
code[forin_loc + 3] = (offset & 0xFF) as u8;
|
||||||
|
code[forin_loc + 4] = ((offset >> 8) & 0xFF) as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_constant(&mut self, val: Value) -> u16 {
|
||||||
|
self.function.add_constant(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Constant pool helpers
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
fn add_string_constant(&mut self, s: &str) -> u16 {
|
||||||
|
self.function.add_constant(Value::String(s.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_function_proto_constant(&mut self, proto: FunctionProto) -> u16 {
|
||||||
|
let idx = self.function.protos.len() as u16;
|
||||||
|
self.function.protos.push(Rc::new(proto));
|
||||||
|
// Store proto index as a sentinel value in constants
|
||||||
|
self.function.constants.push(Value::Number(f64::from_bits(idx as u64 | 0x_F000_0000_0000_0000)));
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Helpers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn assign_op_to_compound(op: &AssignOp) -> CompoundOp {
|
||||||
|
match op {
|
||||||
|
AssignOp::PlusEqual => CompoundOp::PlusEqual,
|
||||||
|
AssignOp::MinusEqual => CompoundOp::MinusEqual,
|
||||||
|
AssignOp::StarEqual => CompoundOp::StarEqual,
|
||||||
|
AssignOp::SlashEqual => CompoundOp::SlashEqual,
|
||||||
|
AssignOp::PercentEqual => CompoundOp::PercentEqual,
|
||||||
|
AssignOp::Equal => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod opcode;
|
||||||
|
pub mod compiler;
|
||||||
|
pub mod vm;
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
//! Bytecode opcode definitions for the Aster VM.
|
||||||
|
//!
|
||||||
|
//! Each instruction is encoded as a 1-byte opcode tag followed by
|
||||||
|
//! variable-length operands (u8 for local slots, u16 for constant pool
|
||||||
|
//! indices, i16 for relative jump offsets).
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum OpCode {
|
||||||
|
// --- Stack manipulation ---
|
||||||
|
Pop = 0,
|
||||||
|
Dup = 1,
|
||||||
|
|
||||||
|
// --- Constants (operand: u16 index into constant pool) ---
|
||||||
|
LoadConst = 2,
|
||||||
|
LoadNil = 3,
|
||||||
|
LoadTrue = 4,
|
||||||
|
LoadFalse = 5,
|
||||||
|
|
||||||
|
// --- Local variables (operand: u8 slot index) ---
|
||||||
|
LoadLocal = 6,
|
||||||
|
StoreLocal = 7,
|
||||||
|
LoadUpvalue = 42, // operand: u8 upvalue index
|
||||||
|
StoreUpvalue = 43, // operand: u8 upvalue index
|
||||||
|
|
||||||
|
// --- Global variables (operand: u16 index into constant pool for name) ---
|
||||||
|
LoadGlobal = 8,
|
||||||
|
StoreGlobal = 9,
|
||||||
|
DefineGlobal = 10,
|
||||||
|
|
||||||
|
// --- Property access (operand: u16 index into constant pool for name) ---
|
||||||
|
GetProperty = 11,
|
||||||
|
SetProperty = 12,
|
||||||
|
|
||||||
|
// --- Index access (operands on stack) ---
|
||||||
|
GetIndex = 13,
|
||||||
|
SetIndex = 14,
|
||||||
|
|
||||||
|
// --- Binary arithmetic ---
|
||||||
|
Add = 15,
|
||||||
|
Sub = 16,
|
||||||
|
Mul = 17,
|
||||||
|
Div = 18,
|
||||||
|
Mod = 19,
|
||||||
|
|
||||||
|
// --- Unary ---
|
||||||
|
Negate = 20,
|
||||||
|
Not = 21,
|
||||||
|
|
||||||
|
// --- Comparison ---
|
||||||
|
Equal = 22,
|
||||||
|
NotEqual = 23,
|
||||||
|
Greater = 24,
|
||||||
|
GreaterEqual = 25,
|
||||||
|
Less = 26,
|
||||||
|
LessEqual = 27,
|
||||||
|
|
||||||
|
// --- Control flow (operand: i16 relative jump offset) ---
|
||||||
|
Jump = 28,
|
||||||
|
JumpIfFalse = 29,
|
||||||
|
JumpIfTrue = 30,
|
||||||
|
PopJumpIfFalse = 31,
|
||||||
|
|
||||||
|
// --- Functions ---
|
||||||
|
Call = 32, // operand: u8 arg count
|
||||||
|
Return = 33,
|
||||||
|
Closure = 34, // operand: u16 proto idx, then N*(u8 is_local, u8 index)
|
||||||
|
|
||||||
|
// --- Object/Array construction ---
|
||||||
|
NewObject = 35,
|
||||||
|
NewArray = 36, // operand: u16 element count
|
||||||
|
|
||||||
|
// --- For-in support ---
|
||||||
|
ForInInit = 37,
|
||||||
|
ForInNext = 38, // operand: i16 done jump offset
|
||||||
|
|
||||||
|
// --- Compound assignment ---
|
||||||
|
CompoundAssignLocal = 39, // operand: u8 slot + u8 compound-op tag
|
||||||
|
CompoundAssignProp = 40, // operand: u16 name idx + u8 compound-op tag
|
||||||
|
CompoundAssignIndex = 41, // operand: u8 compound-op tag
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpCode {
|
||||||
|
pub fn from_u8(byte: u8) -> Option<Self> {
|
||||||
|
match byte {
|
||||||
|
0..=41 => Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) }),
|
||||||
|
42 => Some(OpCode::LoadUpvalue),
|
||||||
|
43 => Some(OpCode::StoreUpvalue),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Total number of distinct opcodes
|
||||||
|
pub const COUNT: usize = 44;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compound assignment operator tags (used as operand byte after
|
||||||
|
/// CompoundAssignLocal/Prop/Index).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum CompoundOp {
|
||||||
|
PlusEqual = 0,
|
||||||
|
MinusEqual = 1,
|
||||||
|
StarEqual = 2,
|
||||||
|
SlashEqual = 3,
|
||||||
|
PercentEqual = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompoundOp {
|
||||||
|
pub fn from_u8(byte: u8) -> Option<Self> {
|
||||||
|
if byte <= 4 {
|
||||||
|
Some(unsafe { std::mem::transmute::<u8, CompoundOp>(byte) })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Instruction encoding helpers (for compiler use)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Write a u8 operand after the opcode byte.
|
||||||
|
pub fn emit_u8(code: &mut Vec<u8>, op: OpCode, operand: u8) {
|
||||||
|
code.push(op as u8);
|
||||||
|
code.push(operand);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a u16 operand after the opcode byte (little-endian).
|
||||||
|
pub fn emit_u16(code: &mut Vec<u8>, op: OpCode, operand: u16) {
|
||||||
|
code.push(op as u8);
|
||||||
|
code.push((operand & 0xFF) as u8);
|
||||||
|
code.push(((operand >> 8) & 0xFF) as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write an i16 operand after the opcode byte (little-endian).
|
||||||
|
pub fn emit_i16(code: &mut Vec<u8>, op: OpCode, offset: i16) {
|
||||||
|
code.push(op as u8);
|
||||||
|
code.push((offset & 0xFF) as u8);
|
||||||
|
code.push(((offset >> 8) & 0xFF) as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a standalone opcode byte.
|
||||||
|
pub fn emit_op(code: &mut Vec<u8>, op: OpCode) {
|
||||||
|
code.push(op as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Instruction decoding helpers (for VM use)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Read a u8 at position `ip + 1` (right after the opcode byte).
|
||||||
|
pub fn read_u8(code: &[u8], ip: usize) -> u8 {
|
||||||
|
code[ip + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a u16 at position `ip + 1` (little-endian, right after the opcode byte).
|
||||||
|
pub fn read_u16(code: &[u8], ip: usize) -> u16 {
|
||||||
|
(code[ip + 1] as u16) | ((code[ip + 2] as u16) << 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read an i16 at position `ip + 1` (little-endian, right after the opcode byte).
|
||||||
|
pub fn read_i16(code: &[u8], ip: usize) -> i16 {
|
||||||
|
((code[ip + 1] as u16) | ((code[ip + 2] as u16) << 8)) as i16
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Size in bytes of an opcode with no operand.
|
||||||
|
pub const SIZE_OP: usize = 1;
|
||||||
|
/// Size in bytes of an opcode with a u8 operand.
|
||||||
|
pub const SIZE_U8: usize = 2;
|
||||||
|
/// Size in bytes of an opcode with a u16/i16 operand.
|
||||||
|
pub const SIZE_U16: usize = 3;
|
||||||
File diff suppressed because it is too large
Load Diff
+12
-4
@@ -3,13 +3,21 @@ use std::fs;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let use_vm = args.iter().any(|a| a == "--vm");
|
||||||
|
let file_args: Vec<&String> = args.iter().filter(|a| *a != "--vm").collect();
|
||||||
|
|
||||||
match args.len() {
|
match file_args.len() {
|
||||||
1 => aster_core::run_repl(),
|
1 => aster_core::run_repl(),
|
||||||
2 => {
|
2 => {
|
||||||
let filename = &args[1];
|
let filename = file_args[1];
|
||||||
match fs::read_to_string(filename) {
|
match fs::read_to_string(filename) {
|
||||||
Ok(src) => aster_core::run_file(filename, src),
|
Ok(src) => {
|
||||||
|
if use_vm {
|
||||||
|
aster_core::run_file_vm(filename, src)
|
||||||
|
} else {
|
||||||
|
aster_core::run_file(filename, src)
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error reading file '{}': {}", filename, e);
|
eprintln!("Error reading file '{}': {}", filename, e);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
@@ -17,7 +25,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("Usage: aster [script]");
|
eprintln!("Usage: aster [--vm] [script]");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user