Skip to main content

sim_lib_lang_lua/
stdlib_coroutine.rs

1use std::sync::{Arc, Mutex};
2
3use sim_kernel::{
4    Args, Callable, ClassRef, Cx, Error, Expr, Object, ObjectCompat, Result, Symbol, Value,
5};
6use sim_lib_control::{CoroutineFrame, CoroutineFrameStep};
7use sim_lib_standard_core::{Arity, SharedOrganRuntime};
8
9use crate::{LuaEvalPolicy, call::call_lua_value, lua_core_profile, lua_table_from_values};
10
11#[derive(Clone, Copy)]
12pub(crate) enum LuaCoroutineKind {
13    Create,
14    Resume,
15    Yield,
16    Status,
17    Wrap,
18    IsYieldable,
19    Running,
20}
21
22impl LuaCoroutineKind {
23    const ALL: [Self; 7] = [
24        Self::Create,
25        Self::Resume,
26        Self::Yield,
27        Self::Status,
28        Self::Wrap,
29        Self::IsYieldable,
30        Self::Running,
31    ];
32
33    fn env_name(self) -> &'static str {
34        match self {
35            Self::Create => "create",
36            Self::Resume => "resume",
37            Self::Yield => "yield",
38            Self::Status => "status",
39            Self::Wrap => "wrap",
40            Self::IsYieldable => "isyieldable",
41            Self::Running => "running",
42        }
43    }
44
45    fn function_symbol(self) -> Symbol {
46        Symbol::qualified("lua/coroutine", self.env_name())
47    }
48}
49
50#[derive(Clone)]
51pub(crate) struct LuaCoroutineFunction {
52    kind: LuaCoroutineKind,
53}
54
55impl LuaCoroutineFunction {
56    fn new(kind: LuaCoroutineKind) -> Self {
57        Self { kind }
58    }
59
60    pub(crate) fn kind(&self) -> LuaCoroutineKind {
61        self.kind
62    }
63}
64
65impl Object for LuaCoroutineFunction {
66    fn display(&self, _cx: &mut Cx) -> Result<String> {
67        Ok(format!(
68            "#<lua-coroutine-function {}>",
69            self.kind.env_name()
70        ))
71    }
72
73    fn as_any(&self) -> &dyn std::any::Any {
74        self
75    }
76}
77
78impl ObjectCompat for LuaCoroutineFunction {
79    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
80        cx.resolve_class(&Symbol::qualified("core", "Function"))
81    }
82
83    fn as_callable(&self) -> Option<&dyn Callable> {
84        Some(self)
85    }
86}
87
88impl Callable for LuaCoroutineFunction {
89    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
90        let policy = LuaEvalPolicy::new(cx)?;
91        let values = run_lua_coroutine_function(cx, &policy, self.kind, args.into_vec())?;
92        Ok(policy
93            .kit()
94            .adjust_values(values, sim_lib_standard_core::Arity::AtLeastOne)
95            .into_iter()
96            .next()
97            .unwrap_or_else(|| policy.kit().nil.clone()))
98    }
99}
100
101/// Lua coroutine handle.
102pub struct LuaThread {
103    state: Mutex<LuaThreadState>,
104}
105
106#[derive(Clone)]
107enum LuaThreadState {
108    New(Value),
109    Frame(CoroutineFrame<Value>),
110    Dead,
111}
112
113impl LuaThread {
114    fn new(function: Value) -> Self {
115        Self {
116            state: Mutex::new(LuaThreadState::New(function)),
117        }
118    }
119
120    fn frame(produced: Vec<Value>, consumed: Vec<Value>) -> Self {
121        Self {
122            state: Mutex::new(LuaThreadState::Frame(CoroutineFrame::new(
123                produced, consumed,
124            ))),
125        }
126    }
127
128    fn status(&self) -> Result<&'static str> {
129        let state = self
130            .state
131            .lock()
132            .map_err(|_| Error::PoisonedLock("lua coroutine"))?;
133        Ok(match &*state {
134            LuaThreadState::New(_) | LuaThreadState::Frame(_) => "suspended",
135            LuaThreadState::Dead => "dead",
136        })
137    }
138
139    fn resume(&self, cx: &mut Cx, policy: &LuaEvalPolicy, args: Vec<Value>) -> Result<Vec<Value>> {
140        let state = {
141            let mut guard = self
142                .state
143                .lock()
144                .map_err(|_| Error::PoisonedLock("lua coroutine"))?;
145            std::mem::replace(&mut *guard, LuaThreadState::Dead)
146        };
147        match state {
148            LuaThreadState::New(function) => match call_lua_value(cx, policy, function, args) {
149                Ok(values) => {
150                    let mut out = vec![cx.factory().bool(true)?];
151                    out.extend(values);
152                    Ok(out)
153                }
154                Err(error) => Ok(vec![
155                    cx.factory().bool(false)?,
156                    cx.factory().string(error.to_string())?,
157                ]),
158            },
159            LuaThreadState::Frame(mut frame) => match frame.resume() {
160                CoroutineFrameStep::Produced(value) | CoroutineFrameStep::Consumed(value) => {
161                    let done = frame.is_complete();
162                    *self
163                        .state
164                        .lock()
165                        .map_err(|_| Error::PoisonedLock("lua coroutine"))? = if done {
166                        LuaThreadState::Dead
167                    } else {
168                        LuaThreadState::Frame(frame)
169                    };
170                    Ok(vec![cx.factory().bool(true)?, value])
171                }
172                CoroutineFrameStep::Complete => Ok(vec![cx.factory().bool(true)?]),
173            },
174            LuaThreadState::Dead => Ok(vec![
175                cx.factory().bool(false)?,
176                cx.factory()
177                    .string("cannot resume dead coroutine".to_owned())?,
178            ]),
179        }
180    }
181}
182
183impl Object for LuaThread {
184    fn display(&self, _cx: &mut Cx) -> Result<String> {
185        Ok(format!("#<lua-thread {}>", self.status()?))
186    }
187
188    fn as_any(&self) -> &dyn std::any::Any {
189        self
190    }
191}
192
193impl ObjectCompat for LuaThread {
194    fn truth(&self, _cx: &mut Cx) -> Result<bool> {
195        Ok(true)
196    }
197}
198
199#[derive(Clone)]
200pub(crate) struct LuaCoroutineWrapper {
201    thread: Arc<LuaThread>,
202}
203
204impl LuaCoroutineWrapper {
205    fn new(function: Value) -> Self {
206        Self {
207            thread: Arc::new(LuaThread::new(function)),
208        }
209    }
210}
211
212impl Object for LuaCoroutineWrapper {
213    fn display(&self, _cx: &mut Cx) -> Result<String> {
214        Ok(format!(
215            "#<lua-coroutine-wrapper {}>",
216            self.thread.status()?
217        ))
218    }
219
220    fn as_any(&self) -> &dyn std::any::Any {
221        self
222    }
223}
224
225impl ObjectCompat for LuaCoroutineWrapper {
226    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
227        cx.resolve_class(&Symbol::qualified("core", "Function"))
228    }
229
230    fn as_callable(&self) -> Option<&dyn Callable> {
231        Some(self)
232    }
233}
234
235impl Callable for LuaCoroutineWrapper {
236    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
237        let policy = LuaEvalPolicy::new(cx)?;
238        let values = call_lua_coroutine_wrapper(cx, &policy, self, args.into_vec())?;
239        Ok(policy
240            .kit()
241            .adjust_values(values, Arity::AtLeastOne)
242            .into_iter()
243            .next()
244            .unwrap_or_else(|| policy.kit().nil.clone()))
245    }
246}
247
248/// Build a Lua coroutine handle over a shared producer/consumer frame.
249pub fn lua_coroutine_frame_value(
250    cx: &mut Cx,
251    produced: Vec<Value>,
252    consumed: Vec<Value>,
253) -> Result<Value> {
254    cx.factory()
255        .opaque(Arc::new(LuaThread::frame(produced, consumed)))
256}
257
258pub(crate) fn install_lua_coroutine_stdlib(
259    cx: &mut Cx,
260    policy: &LuaEvalPolicy,
261    env: &mut crate::LuaEnv,
262) -> Result<()> {
263    let mut runtime = SharedOrganRuntime::new();
264    let profile = lua_core_profile();
265    let profile_symbol = profile.symbol.clone();
266    runtime.register_profile(profile)?;
267    runtime.register_kit(&profile_symbol, policy.kit().clone())?;
268
269    let mut table_entries = Vec::new();
270    for kind in LuaCoroutineKind::ALL {
271        let function = cx
272            .factory()
273            .opaque(Arc::new(LuaCoroutineFunction::new(kind)))?;
274        runtime.define_function(
275            &profile_symbol,
276            sim_lib_control::control_organ_symbol(),
277            kind.function_symbol(),
278            function.clone(),
279        )?;
280        table_entries.push((
281            cx.factory().string(kind.env_name().to_owned())?,
282            function.clone(),
283        ));
284        define_or_assign(
285            env,
286            Symbol::new(format!("coroutine.{}", kind.env_name())),
287            function,
288        )?;
289    }
290    let table = lua_table_from_values(cx, table_entries)?;
291    define_or_assign(env, Symbol::new("coroutine"), table)
292}
293
294pub(crate) fn run_lua_coroutine_function(
295    cx: &mut Cx,
296    policy: &LuaEvalPolicy,
297    kind: LuaCoroutineKind,
298    args: Vec<Value>,
299) -> Result<Vec<Value>> {
300    match kind {
301        LuaCoroutineKind::Create => {
302            let function = first_arg(args, "coroutine.create")?;
303            cx.factory()
304                .opaque(Arc::new(LuaThread::new(function)))
305                .map(|value| vec![value])
306        }
307        LuaCoroutineKind::Resume => {
308            let mut args = args;
309            let thread = required_arg(&mut args, "coroutine.resume")?;
310            let thread = lua_thread_value(&thread)?;
311            thread.resume(cx, policy, args)
312        }
313        LuaCoroutineKind::Yield => Ok(args),
314        LuaCoroutineKind::Status => {
315            let value = first_arg(args, "coroutine.status")?;
316            let thread = lua_thread_value(&value)?;
317            cx.factory()
318                .string(thread.status()?.to_owned())
319                .map(|value| vec![value])
320        }
321        LuaCoroutineKind::Wrap => {
322            let function = first_arg(args, "coroutine.wrap")?;
323            cx.factory()
324                .opaque(Arc::new(LuaCoroutineWrapper::new(function)))
325                .map(|value| vec![value])
326        }
327        LuaCoroutineKind::IsYieldable => cx.factory().bool(true).map(|value| vec![value]),
328        LuaCoroutineKind::Running => Ok(vec![policy.kit().nil.clone(), cx.factory().bool(false)?]),
329    }
330}
331
332pub(crate) fn call_lua_coroutine_wrapper(
333    cx: &mut Cx,
334    policy: &LuaEvalPolicy,
335    wrapper: &LuaCoroutineWrapper,
336    args: Vec<Value>,
337) -> Result<Vec<Value>> {
338    let mut values = wrapper.thread.resume(cx, policy, args)?.into_iter();
339    let status = values
340        .next()
341        .ok_or_else(|| Error::Eval("coroutine wrapper resume returned no status".to_owned()))?;
342    match status.object().as_expr(cx)? {
343        Expr::Bool(true) => Ok(values.collect()),
344        Expr::Bool(false) => {
345            let message = values
346                .next()
347                .map(|value| value.object().display(cx))
348                .transpose()?
349                .unwrap_or_else(|| "coroutine error".to_owned());
350            Err(Error::Eval(message))
351        }
352        _ => Err(Error::Eval(
353            "coroutine wrapper resume returned non-boolean status".to_owned(),
354        )),
355    }
356}
357
358fn lua_thread_value(value: &Value) -> Result<&LuaThread> {
359    value
360        .object()
361        .downcast_ref::<LuaThread>()
362        .ok_or(Error::TypeMismatch {
363            expected: "lua coroutine thread",
364            found: "non-thread",
365        })
366}
367
368fn first_arg(args: Vec<Value>, context: &str) -> Result<Value> {
369    args.into_iter()
370        .next()
371        .ok_or_else(|| Error::Eval(format!("{context} requires a value")))
372}
373
374fn required_arg(args: &mut Vec<Value>, context: &str) -> Result<Value> {
375    if args.is_empty() {
376        return Err(Error::Eval(format!("{context} requires a value")));
377    }
378    Ok(args.remove(0))
379}
380
381fn define_or_assign(env: &mut crate::LuaEnv, name: Symbol, value: Value) -> Result<()> {
382    if env.contains(&name) {
383        env.assign(&name, value)?;
384    } else {
385        env.define(name, value)?;
386    }
387    Ok(())
388}