luars 0.17.0

A library for lua 5.5 runtime implementation in Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
// Coroutine library - Full implementation
// Implements: create, resume, yield, status, running, wrap, isyieldable

use crate::lib_registry::LibraryModule;
use crate::lua_value::LuaValue;
use crate::lua_vm::{LuaError, LuaResult, LuaState};

pub fn create_coroutine_lib() -> LibraryModule {
    crate::lib_module!("coroutine", {
        "create" => coroutine_create,
        "resume" => coroutine_resume,
        "yield" => coroutine_yield,
        "status" => coroutine_status,
        "running" => coroutine_running,
        "wrap" => coroutine_wrap,
        "isyieldable" => coroutine_isyieldable,
        "close" => coroutine_close,
    })
}

/// coroutine.create(f) - Create a new coroutine
fn coroutine_create(l: &mut LuaState) -> LuaResult<usize> {
    let func = match l.get_arg(1) {
        Some(f) => f,
        None => {
            return Err(l.error("coroutine.create requires a function argument".to_string()));
        }
    };

    if !func.is_function() && !func.is_cfunction() {
        return Err(l.error("coroutine.create requires a function argument".to_string()));
    }

    // Use VM's create_thread which properly sets up the thread with the function
    let vm = l.vm_mut();
    let thread_val = vm.create_thread(func)?;

    l.push_value(thread_val)?;
    Ok(1)
}

/// coroutine.resume(co, ...) - Resume a coroutine
fn coroutine_resume(l: &mut LuaState) -> LuaResult<usize> {
    let thread_val = match l.get_arg(1) {
        Some(t) => t,
        None => {
            return Err(l.error("coroutine.resume requires a thread argument".to_string()));
        }
    };

    if !thread_val.is_thread() {
        return Err(l.error("coroutine.resume requires a thread argument".to_string()));
    }

    // Get remaining arguments
    let all_args = l.get_args();
    let args: Vec<LuaValue> = if all_args.len() > 1 {
        all_args[1..].to_vec()
    } else {
        Vec::new()
    };

    // Resume the thread
    let vm = l.vm_mut();
    match vm.resume_thread(thread_val, args) {
        Ok((_finished, results)) => {
            // Success - either yielded (finished=false) or completed (finished=true)
            // Both are successful from pcall perspective
            let result_count = results.len();
            l.push_value(LuaValue::boolean(true))?; // success=true
            for result in results {
                l.push_value(result)?;
            }
            Ok(1 + result_count)
        }
        Err(e) => {
            // Error occurred during resume — return (false, error_object)
            // Like C Lua, return the actual error value (not a string conversion).
            // Keep the error_object in the thread so coroutine.close can return it too.
            let error_val = if let Some(thread) = thread_val.as_thread_mut() {
                let err_obj = thread.error_object;
                if !err_obj.is_nil() {
                    err_obj
                } else {
                    // Fallback: create string from error message
                    let msg = thread.get_error_msg(e);
                    if msg.is_empty() {
                        LuaValue::nil()
                    } else {
                        l.create_string(&msg)?
                    }
                }
            } else {
                LuaValue::nil()
            };
            l.push_value(LuaValue::boolean(false))?; // success=false
            l.push_value(error_val)?;
            Ok(2)
        }
    }
}

/// coroutine.yield(...) - Yield from current coroutine
fn coroutine_yield(l: &mut LuaState) -> LuaResult<usize> {
    // Check if yielding is allowed (matches C Lua's lua_yieldk check)
    if l.nny > 0 {
        if l.is_main_thread() {
            return Err(l.error("attempt to yield from outside a coroutine".to_string()));
        } else {
            return Err(l.error("attempt to yield across a C-call boundary".to_string()));
        }
    }

    let args = l.get_args();

    // Yield with values
    l.do_yield(args)?;

    // This return value won't be used because do_yield returns Err(LuaError::Yield)
    Ok(0)
}

/// coroutine.status(co) - Get coroutine status
fn coroutine_status(l: &mut LuaState) -> LuaResult<usize> {
    let thread_val = match l.get_arg(1) {
        Some(t) => t,
        None => {
            return Err(l.error("coroutine.status requires a thread argument".to_string()));
        }
    };

    if !thread_val.is_thread() {
        return Err(l.error("coroutine.status requires a thread argument".to_string()));
    }

    // Check if thread exists and get status
    // Pre-read const strings before mutable borrow of thread
    let cs = &l.vm_mut().const_strings;
    let str_running = cs.str_running;
    let str_suspended = cs.str_suspended;
    let str_normal = cs.str_normal;
    let str_dead = cs.str_dead;

    let status_val = if let Some(thread) = thread_val.as_thread_mut() {
        if thread.is_main_thread() {
            // Main thread is always running
            str_running
        } else if thread.dead {
            // Dead by error — still has stack/frames for debug.traceback
            str_dead
        } else if thread.call_depth() > 0 {
            if thread.is_yielded() {
                str_suspended
            } else {
                // Thread has frames and is not yielded — it's either running
                // or normal (resumed another coroutine and waiting).
                // Compare identity: if calling thread IS this thread, it's "running".
                let is_self = std::ptr::eq(l as *const LuaState, thread as *const LuaState);
                if is_self { str_running } else { str_normal }
            }
        } else if !thread.stack().is_empty() {
            // Has stack but no frames - initial state
            str_suspended
        } else {
            str_dead
        }
    } else {
        str_dead
    };

    l.push_value(status_val)?;
    Ok(1)
}

/// coroutine.running() - Get currently running coroutine
fn coroutine_running(l: &mut LuaState) -> LuaResult<usize> {
    // In the main thread, return nil and true
    let thread_ptr = unsafe { l.thread_ptr() };
    if l.is_main_thread() {
        l.push_value(LuaValue::thread(thread_ptr))?;
        l.push_value(LuaValue::boolean(true))?;
        return Ok(2);
    }

    let thread_value = LuaValue::thread(thread_ptr);
    l.push_value(thread_value)?;
    l.push_value(LuaValue::boolean(false))?;
    Ok(2)
}

/// coroutine.wrap(f) - Create a wrapped coroutine
fn coroutine_wrap(l: &mut LuaState) -> LuaResult<usize> {
    let func = match l.get_arg(1) {
        Some(f) => f,
        None => {
            return Err(l.error("coroutine.wrap requires a function argument".to_string()));
        }
    };

    if !func.is_function() && !func.is_cfunction() {
        return Err(l.error("coroutine.wrap requires a function argument".to_string()));
    }

    // Create the coroutine
    let vm = l.vm_mut();
    let thread_val = vm.create_thread(func)?;

    // Create a C closure with the thread as upvalue
    let wrapper_func = vm.create_c_closure(coroutine_wrap_call, vec![thread_val])?;

    l.push_value(wrapper_func)?;
    Ok(1)
}

/// Helper function for coroutine.wrap - called when the wrapper is invoked
fn coroutine_wrap_call(l: &mut LuaState) -> LuaResult<usize> {
    // Get the thread from upvalue
    let mut thread_val = LuaValue::nil();
    if let Some(frame_idx) = l.call_depth().checked_sub(1)
        && let Some(func_val) = l.get_frame_func(frame_idx)
        && let Some(cclosure) = func_val.as_cclosure()
    {
        // Check if it's a C closure (coroutine.wrap creates a C closure)

        if let Some(upval) = cclosure.upvalues().first() {
            // Upvalue should be closed with the thread value
            thread_val = *upval;
        }
    }

    if !thread_val.is_thread() {
        return Err(l.error("invalid wrapped coroutine".to_string()));
    }

    // Collect arguments
    let args = l.get_args();

    // Resume the coroutine
    let vm = l.vm_mut();
    match vm.resume_thread(thread_val, args) {
        Ok((_finished, results)) => {
            // Success - push all results
            for result in &results {
                l.push_value(*result)?;
            }
            Ok(results.len())
        }
        Err(_e) => {
            // Error occurred — propagate the error object from the child thread
            // directly (like Lua 5.5's auxresume → lua_error).
            if let Some(thread) = thread_val.as_thread_mut() {
                // Get the error object from the child thread
                let err_obj = std::mem::take(&mut thread.error_object);
                if !err_obj.is_nil() {
                    l.error_object = err_obj;
                    let msg = std::mem::take(&mut thread.error_msg);
                    l.error_msg = msg;
                } else {
                    let msg = std::mem::take(&mut thread.error_msg);
                    l.error_msg = msg;
                }
            }
            Err(LuaError::RuntimeError)
        }
    }
}

/// coroutine.isyieldable([co]) - Check if the given coroutine (or current) can yield
/// Returns true iff nny == 0 (not inside a non-yieldable C call boundary).
fn coroutine_isyieldable(l: &mut LuaState) -> LuaResult<usize> {
    // If a thread argument is given, check that thread; otherwise check current
    let is_yieldable = if let Some(arg) = l.get_arg(1) {
        if let Some(thread) = arg.as_thread_mut() {
            thread.nny == 0
        } else {
            return Err(l.error("value is not a thread".to_string()));
        }
    } else {
        l.nny == 0
    };
    l.push_value(LuaValue::boolean(is_yieldable))?;
    Ok(1)
}

/// coroutine.close([co]) - Close a coroutine, marking it as dead
/// If no argument, closes the calling thread (self).
/// Calls __close on any pending to-be-closed variables, then kills the thread.
fn coroutine_close(l: &mut LuaState) -> LuaResult<usize> {
    // getoptco: if no argument, use the calling thread itself
    let thread_val = match l.get_arg(1) {
        Some(t) if t.is_thread() => t,
        Some(t) if !t.is_nil() => {
            return Err(l.error("bad argument #1 to 'close' (coroutine expected)".to_string()));
        }
        _ => {
            // No argument or nil — close self
            let thread_ptr = unsafe { l.thread_ptr() };
            LuaValue::thread(thread_ptr)
        }
    };

    // Clear the thread's stack and frames to mark it as closed
    if let Some(thread) = thread_val.as_thread_mut() {
        // Determine status (matches C Lua's auxstatus)
        let is_self = std::ptr::eq(l as *const LuaState, thread as *const LuaState);
        // 0 = dead, 1 = suspended, 2 = normal, 3 = running
        let status: u8 = if is_self {
            3 // COS_RUN: L == co
        } else if thread.dead {
            0 // COS_DEAD: dead by error
        } else if thread.is_yielded() {
            1 // COS_YIELD
        } else if thread.call_depth() > 0 {
            2 // COS_NORM: has active frames, not yielded, not self
        } else if !thread.stack().is_empty() {
            1 // Initial state (not started)
        } else {
            0 // COS_DEAD
        };

        match status {
            0 | 1 => {
                // OK to close dead or suspended coroutines.
                // For dead-by-error coroutines, preserve the error.
            }
            2 => {
                return Err(l.error("cannot close a normal coroutine".to_string()));
            }
            3 => {
                if thread.is_main_thread() {
                    return Err(l.error("cannot close main thread".to_string()));
                }
                // Check if this is a re-entrant close (from __close handler)
                if l.is_closing {
                    // Nested close during __close processing — return success (no-op).
                    // The outer close is already handling TBC variables.
                    l.push_value(LuaValue::boolean(true))?;
                    return Ok(1);
                }
                // Direct self-close from within the coroutine's code.
                // Equivalent to C Lua's luaE_resetthread + luaD_throwbaselevel:
                // close TBC vars and upvalues, then throw CloseThread which
                // bypasses all pcalls and goes directly to resume().
                l.is_closing = true;
                let _ = l.close_tbc_with_error(0, LuaValue::nil());
                l.close_upvalues(0);
                l.is_closing = false;
                // error_object is set by close_tbc_with_error if __close errored
                // (nil = success, non-nil = __close error value).
                // CloseThread bypasses pcall and propagates to resume.
                return Err(LuaError::CloseThread);
            }
            _ => unreachable!(),
        }

        // Close all pending to-be-closed variables (calls __close metamethods
        // on the coroutine's thread).  The shared VM n_ccalls counter will
        // correctly track recursion depth because all threads share the same
        // LuaVM instance.
        let close_result = thread.close_tbc_with_error(0, LuaValue::nil());

        // Close all upvalues
        thread.close_upvalues(0);

        // Pop all frames and truncate stack
        while thread.call_depth() > 0 {
            thread.pop_frame();
        }
        thread.stack_truncate();

        match close_result {
            Ok(()) => {
                // Check if coroutine had a pending error (dead-by-error)
                // or if __close cascaded an error
                if !thread.error_object.is_nil() {
                    let err_obj = std::mem::take(&mut thread.error_object);
                    l.push_value(LuaValue::boolean(false))?;
                    l.push_value(err_obj)?;
                    Ok(2)
                } else {
                    l.push_value(LuaValue::boolean(true))?;
                    Ok(1)
                }
            }
            Err(LuaError::Yield) => {
                // Yield inside __close during coroutine close — propagate
                Err(LuaError::Yield)
            }
            Err(_e) => {
                // __close caused an error — return (false, error_value)
                let err_obj = std::mem::take(&mut thread.error_object);
                let error_val = if !err_obj.is_nil() {
                    err_obj
                } else {
                    let msg = std::mem::take(&mut thread.error_msg);
                    if msg.is_empty() {
                        LuaValue::nil()
                    } else {
                        l.create_string(&msg)?
                    }
                };
                l.push_value(LuaValue::boolean(false))?;
                l.push_value(error_val)?;
                Ok(2)
            }
        }
    } else {
        l.push_value(LuaValue::boolean(true))?;
        Ok(1)
    }
}