use core::ffi::c_void;
use std::sync::Arc;
pub(crate) mod convert;
#[cfg(test)]
#[cfg(feature = "unstable")]
mod tests;
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum Ty {
Nil,
Bool,
Number,
String,
Vector,
Optional(&'static Ty),
List(&'static Ty),
Record(&'static Record),
Choice(&'static Choice),
Function(&'static Signature),
}
#[derive(Debug)]
pub struct Record {
name: &'static str,
doc: &'static str,
fields: &'static [Field],
}
impl Record {
#[doc(hidden)]
#[must_use]
pub const fn new(name: &'static str, doc: &'static str, fields: &'static [Field]) -> Self {
Self { name, doc, fields }
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn doc(&self) -> &'static str {
self.doc
}
#[must_use]
pub const fn fields(&self) -> &'static [Field] {
self.fields
}
}
#[derive(Debug)]
pub struct Field {
name: &'static str,
doc: &'static str,
ty: &'static Ty,
}
impl Field {
#[doc(hidden)]
#[must_use]
pub const fn new(name: &'static str, doc: &'static str, ty: &'static Ty) -> Self {
Self { name, doc, ty }
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn doc(&self) -> &'static str {
self.doc
}
#[must_use]
pub const fn ty(&self) -> &'static Ty {
self.ty
}
}
#[derive(Debug)]
pub struct Choice {
name: &'static str,
doc: &'static str,
values: &'static [(&'static str, &'static str)],
}
impl Choice {
#[doc(hidden)]
#[must_use]
pub const fn new(
name: &'static str,
doc: &'static str,
values: &'static [(&'static str, &'static str)],
) -> Self {
Self { name, doc, values }
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn doc(&self) -> &'static str {
self.doc
}
pub fn names(&self) -> impl Iterator<Item = &'static str> {
self.values.iter().map(|(name, _)| *name)
}
#[must_use]
pub const fn values(&self) -> &'static [(&'static str, &'static str)] {
self.values
}
}
#[derive(Debug)]
pub struct Signature {
params: &'static [Param],
ret: &'static Ty,
}
impl Signature {
#[doc(hidden)]
#[must_use]
pub const fn new(params: &'static [Param], ret: &'static Ty) -> Self {
Self { params, ret }
}
#[must_use]
pub const fn params(&self) -> &'static [Param] {
self.params
}
#[must_use]
pub const fn ret(&self) -> &'static Ty {
self.ret
}
}
#[derive(Debug)]
pub struct Param {
name: &'static str,
ty: &'static Ty,
}
impl Param {
#[doc(hidden)]
#[must_use]
pub const fn new(name: &'static str, ty: &'static Ty) -> Self {
Self { name, ty }
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn ty(&self) -> &'static Ty {
self.ty
}
}
pub type Call<T> = fn(&T, Vec<Value>) -> Result<Value, Error>;
pub struct Function<T: 'static> {
name: &'static str,
doc: &'static str,
signature: Signature,
call: Call<T>,
}
impl<T> core::fmt::Debug for Function<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Function")
.field("name", &self.name)
.field("signature", &self.signature)
.finish_non_exhaustive()
}
}
impl<T> Function<T> {
#[doc(hidden)]
#[must_use]
pub const fn new(
name: &'static str,
doc: &'static str,
signature: Signature,
call: Call<T>,
) -> Self {
Self {
name,
doc,
signature,
call,
}
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn doc(&self) -> &'static str {
self.doc
}
#[must_use]
pub const fn signature(&self) -> &Signature {
&self.signature
}
pub fn call(&self, this: &T, args: Vec<Value>) -> Result<Value, Error> {
if args.len() > self.signature.params.len() {
return Err(Error::new(format!(
"{} takes {} arguments, not {}",
self.name,
self.signature.params.len(),
args.len()
)));
}
(self.call)(this, args)
}
}
pub trait Bound: Copy + Send + Sync + 'static {
const NAME: &'static str;
const DOC: &'static str;
const PARTS: &'static [&'static [Function<Self>]];
fn resolve(load: &mut crate::Load<'_>) -> Result<Self, crate::Off>;
fn functions() -> impl Iterator<Item = &'static Function<Self>> {
Self::PARTS.iter().flat_map(|part| part.iter())
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Value {
Nil,
Bool(bool),
Number(f64),
String(String),
Vector([f64; 3]),
List(Vec<Value>),
Record(Vec<Value>),
Function(Callback),
}
type CallbackFn = dyn Fn(Vec<Value>) -> Result<Value, Error> + Send + Sync;
#[derive(Clone)]
pub struct Callback(Arc<CallbackFn>);
impl core::fmt::Debug for Callback {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Callback")
}
}
impl Callback {
pub fn new(
function: impl Fn(Vec<Value>) -> Result<Value, Error> + Send + Sync + 'static,
) -> Self {
Self(Arc::new(function))
}
pub fn call(&self, args: Vec<Value>) -> Result<Value, Error> {
(self.0)(args)
}
}
#[derive(Clone, Debug, thiserror::Error)]
#[error("{0}")]
pub struct Error(String);
impl Error {
pub fn new(message: impl Into<String>) -> Self {
Self(message.into())
}
#[must_use]
pub(crate) fn within(self, context: &str) -> Self {
Self(format!("{context}: {}", self.0))
}
}
impl From<Error> for crate::Off {
fn from(error: Error) -> Self {
Self::new(error.0)
}
}
pub trait Typed {
const TY: &'static Ty;
}
pub trait IntoScript: Typed {
fn into_value(self) -> Value;
}
pub trait FromScript: Typed + Sized {
fn from_value(value: Value) -> Result<Self, Error>;
}
pub trait Answer {
const TY: &'static Ty;
fn from_answer(answer: Result<Value, Error>) -> Self;
}
#[derive(Debug)]
pub struct EventKind {
name: &'static str,
doc: &'static str,
event: crate::abi::Event,
data: &'static Ty,
read: unsafe fn(*const c_void) -> Option<Value>,
}
impl EventKind {
pub(crate) const fn new(
name: &'static str,
doc: &'static str,
event: crate::abi::Event,
data: &'static Ty,
read: unsafe fn(*const c_void) -> Option<Value>,
) -> Self {
Self {
name,
doc,
event,
data,
read,
}
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn doc(&self) -> &'static str {
self.doc
}
#[must_use]
pub const fn event(&self) -> crate::abi::Event {
self.event
}
#[must_use]
pub const fn data(&self) -> &'static Ty {
self.data
}
#[must_use]
pub unsafe fn read(&self, raw: *const c_void) -> Option<Value> {
unsafe { (self.read)(raw) }
}
}
pub fn events() -> impl Iterator<Item = &'static EventKind> {
crate::events::SCRIPT_EVENTS
.iter()
.flat_map(|part| part.iter())
}
#[derive(Debug)]
pub struct Namespace {
name: &'static str,
doc: &'static str,
describe: fn() -> Vec<Declared>,
open: fn(&mut crate::Load<'_>) -> Result<Opened, crate::Off>,
}
#[derive(Clone, Copy, Debug)]
pub struct Declared {
index: usize,
name: &'static str,
doc: &'static str,
signature: &'static Signature,
}
impl Declared {
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn doc(&self) -> &'static str {
self.doc
}
#[must_use]
pub const fn signature(&self) -> &'static Signature {
self.signature
}
}
type OpenedFn = dyn Fn(usize, Vec<Value>) -> Result<Value, Error> + Send + Sync;
#[derive(Clone)]
pub struct Opened(Arc<OpenedFn>);
impl core::fmt::Debug for Opened {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Opened")
}
}
impl Opened {
pub fn call(&self, function: &Declared, args: Vec<Value>) -> Result<Value, Error> {
(self.0)(function.index, args)
}
}
impl Namespace {
const fn of<T: Bound>() -> Self {
Self {
name: T::NAME,
doc: T::DOC,
describe: describe::<T>,
open: open::<T>,
}
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn doc(&self) -> &'static str {
self.doc
}
#[must_use]
pub fn functions(&self) -> Vec<Declared> {
(self.describe)()
}
pub fn open(&self, load: &mut crate::Load<'_>) -> Result<Opened, crate::Off> {
(self.open)(load)
}
}
fn describe<T: Bound>() -> Vec<Declared> {
T::functions()
.enumerate()
.map(|(index, f)| Declared {
index,
name: f.name,
doc: f.doc,
signature: &f.signature,
})
.collect()
}
fn open<T: Bound>(load: &mut crate::Load<'_>) -> Result<Opened, crate::Off> {
let this = T::resolve(load)?;
Ok(Opened(Arc::new(move |index, args| {
T::functions()
.nth(index)
.ok_or_else(|| Error::new(format!("{} has no such function", T::NAME)))?
.call(&this, args)
})))
}
pub const NAMESPACES: &[Namespace] = &[
Namespace::of::<Log>(),
#[cfg(feature = "unstable")]
Namespace::of::<crate::world::World>(),
Namespace::of::<crate::schedule::Scheduler>(),
Namespace::of::<crate::inventory::Inventories>(),
];
#[derive(Clone, Copy, Debug)]
pub struct Log(crate::Host);
#[crate::script(lines)]
impl Log {
#[script]
pub fn info(&self, text: &str) {
self.0.log(crate::abi::Level::INFO, text);
}
#[script]
pub fn warn(&self, text: &str) {
self.0.log(crate::abi::Level::WARN, text);
}
#[script]
pub fn error(&self, text: &str) {
self.0.log(crate::abi::Level::ERROR, text);
}
}
impl Bound for Log {
const NAME: &'static str = "log";
const DOC: &'static str = "The mod's log file, one line per call.";
const PARTS: &'static [&'static [Function<Self>]] = &[Self::SCRIPT_LINES];
fn resolve(load: &mut crate::Load<'_>) -> Result<Self, crate::Off> {
Ok(Self(load.host()))
}
}
type SubscribeGame = fn(&mut crate::Load<'_>, Box<GameHandler>) -> Result<(), crate::Off>;
type GameHandler = dyn Fn(&dyn Fn() -> Value) + Send + Sync;
#[derive(Debug)]
pub struct GameEventKind {
record: &'static Record,
data: &'static Ty,
subscribe: SubscribeGame,
}
impl GameEventKind {
pub(crate) const fn of<E>() -> Self
where
E: crate::events::Kind<Data = E> + IntoScript + Clone + Send + Sync,
{
let record = match E::TY {
Ty::Record(record) => *record,
#[expect(
clippy::panic,
reason = "evaluated at compile time: a typed game event that is no record does not build"
)]
_ => panic!("a typed game event derives Script as a record"),
};
Self {
record,
data: E::TY,
subscribe: subscribe_game::<E>,
}
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.record.name()
}
#[must_use]
pub const fn doc(&self) -> &'static str {
self.record.doc()
}
#[must_use]
pub const fn data(&self) -> &'static Ty {
self.data
}
pub fn subscribe(
&self,
load: &mut crate::Load<'_>,
handler: impl Fn(&dyn Fn() -> Value) + Send + Sync + 'static,
) -> Result<(), crate::Off> {
(self.subscribe)(load, Box::new(handler))
}
}
fn subscribe_game<E>(
load: &mut crate::Load<'_>,
handler: Box<GameHandler>,
) -> Result<(), crate::Off>
where
E: crate::events::Kind<Data = E> + IntoScript + Clone + Send + Sync,
{
load.on::<E>(move |event| handler(&|| event.clone().into_value()))
}
pub fn game_events() -> impl Iterator<Item = &'static GameEventKind> {
crate::game_events::SCRIPT_KINDS.iter()
}