Skip to content

Commit

Permalink
Add basic adapters for Lua and Javascript runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
mattkleiny committed Jul 14, 2024
1 parent 260fa3a commit 0f46277
Show file tree
Hide file tree
Showing 12 changed files with 642 additions and 168 deletions.
287 changes: 277 additions & 10 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 5 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ opt-level = 3
[features]
default = []
serde = ["common/serde", "graphics/serde"]
serializer-json = ["common/json"]
serializer-ron = ["common/ron"]
serializer-xml = ["common/xml"]
serializer-yaml = ["common/yaml"]
lang-basic = ["scripting/basic"]
lang-wren = ["scripting/wren"]
serde-json = ["common/json"]
serde-ron = ["common/ron"]
serde-xml = ["common/xml"]
serde-yaml = ["common/yaml"]
lang-lua = ["scripting/lua"]
lang-lisp = ["scripting/lisp"]
lang-javascript = ["scripting/javascript"]

[workspace.dependencies]
# shared dependencies
Expand Down
6 changes: 5 additions & 1 deletion crates/common/src/abstractions/variant.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Quat, StringName, Vec2, Vec3, Vec4};
use crate::{Color, Color32, Quat, StringName, Vec2, Vec3, Vec4};

/// A type that can hold varying different values.
///
Expand Down Expand Up @@ -26,6 +26,8 @@ pub enum Variant {
Vec3(Vec3),
Vec4(Vec4),
Quat(Quat),
Color(Color),
Color32(Color32),
}

/// Allows for a type to be converted to a [`Variant`].
Expand Down Expand Up @@ -95,6 +97,8 @@ impl_variant!(Vec2, Vec2);
impl_variant!(Vec3, Vec3);
impl_variant!(Vec4, Vec4);
impl_variant!(Quat, Quat);
impl_variant!(Color, Color);
impl_variant!(Color32, Color32);

#[cfg(test)]
mod tests {
Expand Down
8 changes: 4 additions & 4 deletions crates/scripting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ version.workspace = true

[dependencies]
common = { package = "surreal-common", path = "../common" }
mlua = { version = "0.9.9", features = ["vendored", "lua54"], optional = true }
quick-js = { version = "0.4.1", optional = true }

[features]
basic = []
wren = []
lua = []
lisp = []
lua = ["mlua"]
javascript = ["quick-js"]
110 changes: 110 additions & 0 deletions crates/scripting/src/interop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use common::{Color, Color32, Quat, StringName, Variant, Vec2, Vec3, Vec4};

/// A value that can be passed to and from a scripting language.
///
/// This is a new-type pattern over [`Variant`] to allow simple interop with
/// many different scripting language vendor crates.
#[repr(transparent)]
#[derive(Clone, Debug, PartialEq)]
pub struct ScriptValue(pub Variant);

impl ScriptValue {
/// Creates a new `ScriptValue` from a `Variant`.
pub const fn new(variant: Variant) -> Self {
Self(variant)
}

/// Returns a reference to the inner `Variant` value.
#[inline]
pub fn as_variant(&self) -> &Variant {
&self.0
}

/// Returns the inner `Variant` value.
#[inline]
pub fn into_variant(self) -> Variant {
self.0
}
}

/// Allow conversion from [`Variant`].
impl From<Variant> for ScriptValue {
#[inline]
fn from(variant: Variant) -> Self {
Self(variant)
}
}

/// Allow conversion into [`Variant`].
impl From<ScriptValue> for Variant {
#[inline]
fn from(value: ScriptValue) -> Self {
value.0
}
}

/// A type that can be converted to a [`ScriptValue`].
pub trait ToScriptValue {
/// Converts the type into a [`ScriptValue`].
fn to_script_value(&self) -> ScriptValue;
}

/// A type that can be converted from a [`ScriptValue`].
pub trait FromScriptValue {
/// Converts a [`ScriptValue`] into the type.
fn from_script_value(value: ScriptValue) -> Self;
}

macro_rules! impl_script_value {
($type:ty, $kind:ident) => {
impl ToScriptValue for $type {
#[inline]
fn to_script_value(&self) -> ScriptValue {
ScriptValue(Variant::from(self.clone()))
}
}

impl FromScriptValue for $type {
#[inline]
fn from_script_value(value: ScriptValue) -> Self {
match value.0 {
Variant::$kind(value) => value,
_ => panic!("ScriptValue is not convertible"),
}
}
}
};
}

impl_script_value!(bool, Bool);
impl_script_value!(u8, U8);
impl_script_value!(u16, U16);
impl_script_value!(u32, U32);
impl_script_value!(u64, U64);
impl_script_value!(i8, I8);
impl_script_value!(i16, I16);
impl_script_value!(i32, I32);
impl_script_value!(i64, I64);
impl_script_value!(f32, F32);
impl_script_value!(f64, F64);
impl_script_value!(String, String);
impl_script_value!(StringName, StringName);
impl_script_value!(Vec2, Vec2);
impl_script_value!(Vec3, Vec3);
impl_script_value!(Vec4, Vec4);
impl_script_value!(Quat, Quat);
impl_script_value!(Color, Color);
impl_script_value!(Color32, Color32);

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_script_value_conversion() {
let value = true.to_script_value();
let result = bool::from_script_value(value);

assert_eq!(result, true);
}
}
67 changes: 8 additions & 59 deletions crates/scripting/src/lang.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,13 @@
//! Scripting language abstractions
use common::{ToVirtualPath, Variant};
use super::*;

#[cfg(feature = "basic")]
pub mod basic;
#[cfg(feature = "lisp")]
pub mod lisp;
#[cfg(feature = "javascript")]
mod javascript;
#[cfg(feature = "lua")]
pub mod lua;
#[cfg(feature = "wren")]
pub mod wren;
mod lua;

/// Represents a scripting language
pub trait ScriptLanguage {
/// Loads a script from the given path
fn load(path: impl ToVirtualPath) -> Result<Script, ScriptError>;
}

/// Represents a parsed script
pub struct Script {
module: ast::Module,
}

impl Script {
/// Loads a script from the given path
pub fn from_path<L: ScriptLanguage>(path: impl ToVirtualPath) -> Result<Self, ScriptError> {
L::load(path)
}

/// Executes the script
pub fn execute(&self) {
todo!()
}

/// Evaluates the script with the given arguments
pub fn evaluate(&self, _arguments: &[Variant]) -> Vec<Variant> {
todo!()
}

/// Calls the given top-level function with the given arguments
pub fn call(&self, _name: &str, _arguments: &[Variant]) -> Vec<Variant> {
todo!()
}
}

/// Represents an error that occurred while parsing a script
#[derive(Debug, Eq, PartialEq)]
pub enum ScriptError {
NotFound,
ParseError,
}

mod ast {
//! The internal AST representation of a script
pub struct Module {
pub name: String,
}

pub enum Statement {}
pub enum Expression {}
pub enum Literal {}
}
#[cfg(feature = "javascript")]
pub use javascript::*;
#[cfg(feature = "lua")]
pub use lua::*;
12 changes: 0 additions & 12 deletions crates/scripting/src/lang/basic.rs

This file was deleted.

125 changes: 125 additions & 0 deletions crates/scripting/src/lang/javascript.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! Javascript scripting language support.
use common::Variant;
use quick_js::{Context, JsValue};

use super::*;

/// A runtime for executing Javascript scripts.
pub struct JavascriptRuntime {
context: Context,
}

impl JavascriptRuntime {
pub fn new() -> Self {
Self {
context: Context::new().unwrap(),
}
}
}

impl ScriptRuntime for JavascriptRuntime {
fn eval(&self, code: &str) -> Result<ScriptValue, ScriptError> {
self
.context
.eval(code)
.map(|it| it.to_script_value())
.map_err(|it| ScriptError::ExecutionError(it.to_string()))
}

fn eval_as<R: FromScriptValue>(&self, code: &str) -> Result<R, ScriptError> {
self
.context
.eval(code)
.map(|it| R::from_script_value(it.to_script_value()))
.map_err(|it| ScriptError::ExecutionError(it.to_string()))
}

fn add_callback<R: ToScriptValue + FromScriptValue>(
&mut self,
_name: &str,
_callback: impl ScriptCallback<R> + 'static,
) -> Result<(), ScriptError> {
todo!()
}
}

impl ToScriptValue for JsValue {
fn to_script_value(&self) -> ScriptValue {
match self {
JsValue::Undefined => ScriptValue::from(Variant::Null),
JsValue::Null => ScriptValue::from(Variant::Null),
JsValue::Bool(value) => ScriptValue::from(Variant::Bool(*value)),
JsValue::Int(value) => ScriptValue::from(Variant::I32(*value)),
JsValue::Float(value) => ScriptValue::from(Variant::F64(*value)),
JsValue::String(value) => ScriptValue::from(Variant::String(value.clone())),
JsValue::Array(_) => todo!("Array conversion not implemented"),
JsValue::Object(_) => todo!("Object conversion not implemented"),
_ => panic!("Unsupported JsValue type"),
}
}
}

impl FromScriptValue for JsValue {
fn from_script_value(value: ScriptValue) -> Self {
match value.into_variant() {
Variant::Null => JsValue::Null,
Variant::Bool(value) => JsValue::Bool(value),
Variant::U8(value) => JsValue::Int(value as i32),
Variant::U16(value) => JsValue::Int(value as i32),
Variant::U32(value) => JsValue::Int(value as i32),
Variant::U64(value) => JsValue::Int(value as i32),
Variant::I8(value) => JsValue::Int(value as i32),
Variant::I16(value) => JsValue::Int(value as i32),
Variant::I32(value) => JsValue::Int(value),
Variant::I64(value) => JsValue::Int(value as i32),
Variant::F32(value) => JsValue::Float(value as f64),
Variant::F64(value) => JsValue::Float(value),
Variant::String(value) => JsValue::String(value),
Variant::StringName(value) => JsValue::String(value.to_string()),
Variant::Vec2(value) => JsValue::Array(vec![JsValue::Float(value.x as f64), JsValue::Float(value.y as f64)]),
Variant::Vec3(value) => JsValue::Array(vec![
JsValue::Float(value.x as f64),
JsValue::Float(value.y as f64),
JsValue::Float(value.z as f64),
]),
Variant::Vec4(value) => JsValue::Array(vec![
JsValue::Float(value.x as f64),
JsValue::Float(value.y as f64),
JsValue::Float(value.z as f64),
JsValue::Float(value.w as f64),
]),
Variant::Quat(value) => JsValue::Array(vec![
JsValue::Float(value.x as f64),
JsValue::Float(value.y as f64),
JsValue::Float(value.z as f64),
JsValue::Float(value.w as f64),
]),
Variant::Color(value) => JsValue::Array(vec![
JsValue::Float(value.r as f64),
JsValue::Float(value.g as f64),
JsValue::Float(value.b as f64),
JsValue::Float(value.a as f64),
]),
Variant::Color32(value) => JsValue::Array(vec![
JsValue::Int(value.r as i32),
JsValue::Int(value.g as i32),
JsValue::Int(value.b as i32),
JsValue::Int(value.a as i32),
]),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_basic_javascript_evaluation() {
let runtime = JavascriptRuntime::new();
let result = runtime.eval("1 + 2").unwrap();

assert_eq!(result, ScriptValue(Variant::I32(3)));
}
}
12 changes: 0 additions & 12 deletions crates/scripting/src/lang/lisp.rs

This file was deleted.

Loading

0 comments on commit 0f46277

Please sign in to comment.