Skip to content

Commit 056a071

Browse files
committed
Implement basic generator functions with function* syntax, yield expressions, and iterator protocol
1 parent 83cfb06 commit 056a071

File tree

15 files changed

+359
-25
lines changed

15 files changed

+359
-25
lines changed

examples/js.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ fn print_eval_result(result: &Value) {
7171
Value::Set(_) => println!("[object Set]"),
7272
Value::WeakMap(_) => println!("[object WeakMap]"),
7373
Value::WeakSet(_) => println!("[object WeakSet]"),
74+
Value::GeneratorFunction(_, _, _) => println!("[GeneratorFunction]"),
75+
Value::Generator(_) => println!("[object Generator]"),
7476
}
7577
}
7678

src/core.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ pub enum Expr {
156156
Call(Box<Expr>, Vec<Expr>),
157157
Function(Vec<String>, Vec<Statement>), // parameters, body
158158
AsyncFunction(Vec<String>, Vec<Statement>), // parameters, body for async functions
159+
GeneratorFunction(Vec<String>, Vec<Statement>), // parameters, body for generator functions
159160
ArrowFunction(Vec<String>, Vec<Statement>), // parameters, body
160161
AsyncArrowFunction(Vec<String>, Vec<Statement>), // parameters, body for async arrow functions
161162
Object(Vec<(String, Expr)>), // object literal: key-value pairs
@@ -167,6 +168,8 @@ pub enum Expr {
167168
OptionalCall(Box<Expr>, Vec<Expr>), // optional call: obj?.method(args)
168169
OptionalIndex(Box<Expr>, Box<Expr>), // optional bracket access: obj?.[expr]
169170
Await(Box<Expr>), // await expression
171+
Yield(Option<Box<Expr>>), // yield expression (optional value)
172+
YieldStar(Box<Expr>), // yield* expression (delegation)
170173
This, // this keyword
171174
New(Box<Expr>, Vec<Expr>), // new expression: new Constructor(args)
172175
Super, // super keyword

src/core/eval.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,7 @@ pub fn evaluate_expr(env: &JSObjectDataPtr, expr: &Expr) -> Result<Value, JSErro
17401740
}
17411741
},
17421742
Expr::Function(params, body) => Ok(Value::Closure(params.clone(), body.clone(), env.clone())),
1743+
Expr::GeneratorFunction(params, body) => Ok(Value::GeneratorFunction(params.clone(), body.clone(), env.clone())),
17431744
Expr::ArrowFunction(params, body) => Ok(Value::Closure(params.clone(), body.clone(), env.clone())),
17441745
Expr::AsyncArrowFunction(params, body) => Ok(Value::AsyncClosure(params.clone(), body.clone(), env.clone())),
17451746
Expr::Object(properties) => evaluate_object(env, properties),
@@ -1805,6 +1806,14 @@ pub fn evaluate_expr(env: &JSObjectDataPtr, expr: &Expr) -> Result<Value, JSErro
18051806
_ => Err(raise_eval_error!("await can only be used with promises")),
18061807
}
18071808
}
1809+
Expr::Yield(_expr) => {
1810+
// Yield expressions are only valid in generator functions
1811+
Err(raise_eval_error!("yield expression is only valid in generator functions"))
1812+
}
1813+
Expr::YieldStar(_expr) => {
1814+
// Yield* expressions are only valid in generator functions
1815+
Err(raise_eval_error!("yield* expression is only valid in generator functions"))
1816+
}
18081817
Expr::Value(value) => Ok(value.clone()),
18091818
Expr::Regex(pattern, flags) => {
18101819
// Build temporary Expr list to reuse the existing RegExp constructor
@@ -2724,7 +2733,7 @@ fn evaluate_typeof(env: &JSObjectDataPtr, expr: &Expr) -> Result<Value, JSError>
27242733
Value::BigInt(_) => "bigint",
27252734
Value::Object(_) => "object",
27262735
Value::Function(_) => "function",
2727-
Value::Closure(_, _, _) | Value::AsyncClosure(_, _, _) => "function",
2736+
Value::Closure(_, _, _) | Value::AsyncClosure(_, _, _) | Value::GeneratorFunction(_, _, _) => "function",
27282737
Value::ClassDefinition(_) => "function",
27292738
Value::Getter(_, _) => "function",
27302739
Value::Setter(_, _, _) => "function",
@@ -2735,6 +2744,7 @@ fn evaluate_typeof(env: &JSObjectDataPtr, expr: &Expr) -> Result<Value, JSError>
27352744
Value::Set(_) => "object",
27362745
Value::WeakMap(_) => "object",
27372746
Value::WeakSet(_) => "object",
2747+
Value::Generator(_) => "object",
27382748
};
27392749
Ok(Value::String(utf8_to_utf16(type_str)))
27402750
}
@@ -3788,6 +3798,7 @@ fn evaluate_call(env: &JSObjectDataPtr, func_expr: &Expr, args: &[Expr]) -> Resu
37883798
(Value::Set(set), method) => crate::js_set::handle_set_instance_method(&set, method, args, env),
37893799
(Value::WeakMap(weakmap), method) => crate::js_weakmap::handle_weakmap_instance_method(&weakmap, method, args, env),
37903800
(Value::WeakSet(weakset), method) => crate::js_weakset::handle_weakset_instance_method(&weakset, method, args, env),
3801+
(Value::Generator(generator), method) => crate::js_generator::handle_generator_instance_method(&generator, method, args, env),
37913802
(Value::Object(obj_map), method) => {
37923803
// Object prototype methods are supplied on `Object.prototype`.
37933804
// Lookups will find user-defined (own) methods before inherited
@@ -4013,6 +4024,10 @@ fn evaluate_call(env: &JSObjectDataPtr, func_expr: &Expr, args: &[Expr]) -> Resu
40134024
let func_val = evaluate_expr(env, func_expr)?;
40144025
match func_val {
40154026
Value::Function(func_name) => crate::js_function::handle_global_function(&func_name, args, env),
4027+
Value::GeneratorFunction(params, body, captured_env) => {
4028+
// Generator function call - return a generator object
4029+
crate::js_generator::handle_generator_function_call(&params, &body, args, &captured_env)
4030+
}
40164031
Value::Closure(params, body, captured_env) => {
40174032
// Function call
40184033
// Collect all arguments, expanding spreads

src/core/ffi.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -943,10 +943,12 @@ pub unsafe fn JS_Eval(_ctx: *mut JSContext, input: *const i8, input_len: usize,
943943
Ok(Value::Promise(_)) => JS_UNDEFINED, // For now
944944
Ok(Value::Symbol(_)) => JS_UNDEFINED, // For now
945945
Ok(Value::BigInt(_)) => JS_UNDEFINED,
946-
Ok(Value::Map(_)) => JS_UNDEFINED, // For now
947-
Ok(Value::Set(_)) => JS_UNDEFINED, // For now
948-
Ok(Value::WeakMap(_)) => JS_UNDEFINED, // For now
949-
Ok(Value::WeakSet(_)) => JS_UNDEFINED, // For now
946+
Ok(Value::Map(_)) => JS_UNDEFINED, // For now
947+
Ok(Value::Set(_)) => JS_UNDEFINED, // For now
948+
Ok(Value::WeakMap(_)) => JS_UNDEFINED, // For now
949+
Ok(Value::WeakSet(_)) => JS_UNDEFINED, // For now
950+
Ok(Value::GeneratorFunction(_, _, _)) => JS_UNDEFINED, // For now
951+
Ok(Value::Generator(_)) => JS_UNDEFINED, // For now
950952
Err(_) => JS_UNDEFINED,
951953
}
952954
}

src/core/parser.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,24 @@ fn parse_primary(tokens: &mut Vec<Token>) -> Result<Expr, JSError> {
407407
let inner = parse_primary(tokens)?;
408408
Expr::Await(Box::new(inner))
409409
}
410+
Token::Yield => {
411+
// yield can be followed by an optional expression
412+
if tokens.is_empty()
413+
|| matches!(
414+
tokens[0],
415+
Token::Semicolon | Token::Comma | Token::RParen | Token::RBracket | Token::RBrace | Token::Colon
416+
)
417+
{
418+
Expr::Yield(None)
419+
} else {
420+
let inner = parse_assignment(tokens)?;
421+
Expr::Yield(Some(Box::new(inner)))
422+
}
423+
}
424+
Token::YieldStar => {
425+
let inner = parse_assignment(tokens)?;
426+
Expr::YieldStar(Box::new(inner))
427+
}
410428
Token::LogicalNot => {
411429
let inner = parse_primary(tokens)?;
412430
Expr::LogicalNot(Box::new(inner))
@@ -1076,7 +1094,8 @@ fn parse_primary(tokens: &mut Vec<Token>) -> Result<Expr, JSError> {
10761094
}
10771095
Expr::Array(elements)
10781096
}
1079-
Token::Function => {
1097+
Token::Function | Token::FunctionStar => {
1098+
let is_generator = matches!(tokens[0], Token::FunctionStar);
10801099
// Parse function expression
10811100
if !tokens.is_empty() && matches!(tokens[0], Token::LParen) {
10821101
tokens.remove(0); // consume (
@@ -1114,7 +1133,11 @@ fn parse_primary(tokens: &mut Vec<Token>) -> Result<Expr, JSError> {
11141133
return Err(raise_parse_error!());
11151134
}
11161135
tokens.remove(0); // consume }
1117-
Expr::Function(params, body)
1136+
if is_generator {
1137+
Expr::GeneratorFunction(params, body)
1138+
} else {
1139+
Expr::Function(params, body)
1140+
}
11181141
} else {
11191142
return Err(raise_parse_error!());
11201143
}

src/core/statement.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,9 @@ pub fn parse_statement(tokens: &mut Vec<Token>) -> Result<Statement, JSError> {
442442
}
443443
return Err(raise_parse_error!());
444444
}
445-
if !tokens.is_empty() && matches!(tokens[0], Token::Function) {
446-
tokens.remove(0); // consume function
445+
if !tokens.is_empty() && (matches!(tokens[0], Token::Function) || matches!(tokens[0], Token::FunctionStar)) {
446+
let is_generator = matches!(tokens[0], Token::FunctionStar);
447+
tokens.remove(0); // consume function or function*
447448
log::trace!(
448449
"parse_statement: entered Function branch; next tokens: {:?}",
449450
tokens.iter().take(12).collect::<Vec<_>>()
@@ -500,7 +501,11 @@ pub fn parse_statement(tokens: &mut Vec<Token>) -> Result<Statement, JSError> {
500501
return Err(raise_parse_error!());
501502
}
502503
tokens.remove(0); // consume }
503-
return Ok(Statement::Let(name, Some(Expr::Function(params, body))));
504+
if is_generator {
505+
return Ok(Statement::Let(name, Some(Expr::GeneratorFunction(params, body))));
506+
} else {
507+
return Ok(Statement::Let(name, Some(Expr::Function(params, body))));
508+
}
504509
}
505510
}
506511
}

src/core/token.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ pub enum Token {
9090
Decrement,
9191
Async,
9292
Await,
93+
Yield,
94+
YieldStar,
95+
FunctionStar,
9396
LineTerminator,
9497
/// Exponentiation assignment (`**=`)
9598
PowAssign,
@@ -143,6 +146,8 @@ impl Token {
143146
Token::False => Some("false".to_string()),
144147
Token::Async => Some("async".to_string()),
145148
Token::Await => Some("await".to_string()),
149+
Token::Yield => Some("yield".to_string()),
150+
Token::FunctionStar => Some("function*".to_string()),
146151
_ => None,
147152
}
148153
}
@@ -622,7 +627,15 @@ pub fn tokenize(expr: &str) -> Result<Vec<Token>, JSError> {
622627
"catch" => tokens.push(Token::Catch),
623628
"finally" => tokens.push(Token::Finally),
624629
"throw" => tokens.push(Token::Throw),
625-
"function" => tokens.push(Token::Function),
630+
"function" => {
631+
// Check if followed by '*'
632+
if i < chars.len() && chars[i] == '*' {
633+
tokens.push(Token::FunctionStar);
634+
i += 1; // consume '*'
635+
} else {
636+
tokens.push(Token::Function);
637+
}
638+
}
626639
"return" => tokens.push(Token::Return),
627640
"if" => tokens.push(Token::If),
628641
"else" => tokens.push(Token::Else),
@@ -638,6 +651,15 @@ pub fn tokenize(expr: &str) -> Result<Vec<Token>, JSError> {
638651
"false" => tokens.push(Token::False),
639652
"async" => tokens.push(Token::Async),
640653
"await" => tokens.push(Token::Await),
654+
"yield" => {
655+
// Check if followed by '*'
656+
if i < chars.len() && chars[i] == '*' {
657+
tokens.push(Token::YieldStar);
658+
i += 1; // consume '*'
659+
} else {
660+
tokens.push(Token::Yield);
661+
}
662+
}
641663
_ => tokens.push(Token::Identifier(ident)),
642664
}
643665
}

src/core/value.rs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@ pub struct JSWeakSet {
2929
pub values: Vec<std::rc::Weak<RefCell<JSObjectData>>>, // weak values
3030
}
3131

32+
#[derive(Clone, Debug)]
33+
pub struct JSGenerator {
34+
pub params: Vec<String>,
35+
pub body: Vec<Statement>,
36+
pub env: JSObjectDataPtr, // captured environment
37+
pub state: GeneratorState,
38+
}
39+
40+
#[derive(Clone, Debug)]
41+
pub enum GeneratorState {
42+
NotStarted,
43+
Running { pc: usize, stack: Vec<Value> }, // program counter and value stack
44+
Suspended { pc: usize, stack: Vec<Value> }, // suspended at yield
45+
Completed,
46+
}
47+
3248
pub type JSObjectDataPtr = Rc<RefCell<JSObjectData>>;
3349

3450
#[derive(Clone, Default)]
@@ -99,25 +115,27 @@ pub enum Value {
99115
String(Vec<u16>), // UTF-16 code units
100116
Boolean(bool),
101117
Undefined,
102-
Object(JSObjectDataPtr), // Object with properties
103-
Function(String), // Function name
104-
Closure(Vec<String>, Vec<Statement>, JSObjectDataPtr), // parameters, body, captured environment
105-
AsyncClosure(Vec<String>, Vec<Statement>, JSObjectDataPtr), // parameters, body, captured environment
106-
ClassDefinition(Rc<ClassDefinition>), // Class definition
107-
Getter(Vec<Statement>, JSObjectDataPtr), // getter body, captured environment
108-
Setter(Vec<String>, Vec<Statement>, JSObjectDataPtr), // setter parameter, body, captured environment
118+
Object(JSObjectDataPtr), // Object with properties
119+
Function(String), // Function name
120+
Closure(Vec<String>, Vec<Statement>, JSObjectDataPtr), // parameters, body, captured environment
121+
AsyncClosure(Vec<String>, Vec<Statement>, JSObjectDataPtr), // parameters, body, captured environment
122+
GeneratorFunction(Vec<String>, Vec<Statement>, JSObjectDataPtr), // parameters, body, captured environment
123+
ClassDefinition(Rc<ClassDefinition>), // Class definition
124+
Getter(Vec<Statement>, JSObjectDataPtr), // getter body, captured environment
125+
Setter(Vec<String>, Vec<Statement>, JSObjectDataPtr), // setter parameter, body, captured environment
109126
Property {
110127
// Property descriptor with getter/setter/value
111128
value: Option<Rc<RefCell<Value>>>,
112129
getter: Option<(Vec<Statement>, JSObjectDataPtr)>,
113130
setter: Option<(Vec<String>, Vec<Statement>, JSObjectDataPtr)>,
114131
},
115-
Promise(Rc<RefCell<JSPromise>>), // Promise object
116-
Symbol(Rc<SymbolData>), // Symbol primitive with description
117-
Map(Rc<RefCell<JSMap>>), // Map object
118-
Set(Rc<RefCell<JSSet>>), // Set object
119-
WeakMap(Rc<RefCell<JSWeakMap>>), // WeakMap object
120-
WeakSet(Rc<RefCell<JSWeakSet>>), // WeakSet object
132+
Promise(Rc<RefCell<JSPromise>>), // Promise object
133+
Symbol(Rc<SymbolData>), // Symbol primitive with description
134+
Map(Rc<RefCell<JSMap>>), // Map object
135+
Set(Rc<RefCell<JSSet>>), // Set object
136+
WeakMap(Rc<RefCell<JSWeakMap>>), // WeakMap object
137+
WeakSet(Rc<RefCell<JSWeakSet>>), // WeakSet object
138+
Generator(Rc<RefCell<JSGenerator>>), // Generator object
121139
}
122140

123141
impl std::fmt::Debug for Value {
@@ -132,6 +150,7 @@ impl std::fmt::Debug for Value {
132150
Value::Function(name) => write!(f, "Function({})", name),
133151
Value::Closure(_, _, _) => write!(f, "Closure"),
134152
Value::AsyncClosure(_, _, _) => write!(f, "AsyncClosure"),
153+
Value::GeneratorFunction(_, _, _) => write!(f, "GeneratorFunction"),
135154
Value::ClassDefinition(_) => write!(f, "ClassDefinition"),
136155
Value::Getter(_, _) => write!(f, "Getter"),
137156
Value::Setter(_, _, _) => write!(f, "Setter"),
@@ -142,6 +161,7 @@ impl std::fmt::Debug for Value {
142161
Value::Set(s) => write!(f, "Set({:p})", Rc::as_ptr(s)),
143162
Value::WeakMap(wm) => write!(f, "WeakMap({:p})", Rc::as_ptr(wm)),
144163
Value::WeakSet(ws) => write!(f, "WeakSet({:p})", Rc::as_ptr(ws)),
164+
Value::Generator(g) => write!(f, "Generator({:p})", Rc::as_ptr(g)),
145165
}
146166
}
147167
}
@@ -171,6 +191,7 @@ pub fn is_truthy(val: &Value) -> bool {
171191
Value::Function(_) => true,
172192
Value::Closure(_, _, _) => true,
173193
Value::AsyncClosure(_, _, _) => true,
194+
Value::GeneratorFunction(_, _, _) => true,
174195
Value::ClassDefinition(_) => true,
175196
Value::Getter(_, _) => true,
176197
Value::Setter(_, _, _) => true,
@@ -181,6 +202,7 @@ pub fn is_truthy(val: &Value) -> bool {
181202
Value::Set(_) => true,
182203
Value::WeakMap(_) => true,
183204
Value::WeakSet(_) => true,
205+
Value::Generator(_) => true,
184206
}
185207
}
186208

@@ -210,6 +232,7 @@ pub fn value_to_string(val: &Value) -> String {
210232
Value::Function(name) => format!("function {}", name),
211233
Value::Closure(_, _, _) => "function".to_string(),
212234
Value::AsyncClosure(_, _, _) => "function".to_string(),
235+
Value::GeneratorFunction(_, _, _) => "function".to_string(),
213236
Value::ClassDefinition(_) => "class".to_string(),
214237
Value::Getter(_, _) => "getter".to_string(),
215238
Value::Setter(_, _, _) => "setter".to_string(),
@@ -223,6 +246,7 @@ pub fn value_to_string(val: &Value) -> String {
223246
Value::Set(_) => "[object Set]".to_string(),
224247
Value::WeakMap(_) => "[object WeakMap]".to_string(),
225248
Value::WeakSet(_) => "[object WeakSet]".to_string(),
249+
Value::Generator(_) => "[object Generator]".to_string(),
226250
}
227251
}
228252

@@ -310,7 +334,7 @@ pub fn value_to_sort_string(val: &Value) -> String {
310334
Value::Undefined => "undefined".to_string(),
311335
Value::Object(_) => "[object Object]".to_string(),
312336
Value::Function(name) => format!("[function {}]", name),
313-
Value::Closure(_, _, _) | Value::AsyncClosure(_, _, _) => "[function]".to_string(),
337+
Value::Closure(_, _, _) | Value::AsyncClosure(_, _, _) | Value::GeneratorFunction(_, _, _) => "[function]".to_string(),
314338
Value::ClassDefinition(_) => "[class]".to_string(),
315339
Value::Getter(_, _) => "[getter]".to_string(),
316340
Value::Setter(_, _, _) => "[setter]".to_string(),
@@ -321,6 +345,7 @@ pub fn value_to_sort_string(val: &Value) -> String {
321345
Value::Set(_) => "[object Set]".to_string(),
322346
Value::WeakMap(_) => "[object WeakMap]".to_string(),
323347
Value::WeakSet(_) => "[object WeakSet]".to_string(),
348+
Value::Generator(_) => "[object Generator]".to_string(),
324349
}
325350
}
326351

src/js_class.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,8 @@ pub(crate) fn handle_string_constructor(args: &[Expr], env: &JSObjectDataPtr) ->
672672
Value::Set(_) => utf8_to_utf16("[object Set]"),
673673
Value::WeakMap(_) => utf8_to_utf16("[object WeakMap]"),
674674
Value::WeakSet(_) => utf8_to_utf16("[object WeakSet]"),
675+
Value::GeneratorFunction(_, _, _) => utf8_to_utf16("[GeneratorFunction]"),
676+
Value::Generator(_) => utf8_to_utf16("[object Generator]"),
675677
}
676678
};
677679

src/js_console.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ pub fn handle_console_method(method: &str, args: &[Expr], env: &JSObjectDataPtr)
109109
Value::Set(_) => print!("[object Set]"),
110110
Value::WeakMap(_) => print!("[object WeakMap]"),
111111
Value::WeakSet(_) => print!("[object WeakSet]"),
112+
Value::GeneratorFunction(_, _, _) => print!("[GeneratorFunction]"),
113+
Value::Generator(_) => print!("[object Generator]"),
112114
}
113115
if i < count - 1 {
114116
print!(" ");

0 commit comments

Comments
 (0)