Skip to content

Commit

Permalink
Begin working on emscripten_em_js
Browse files Browse the repository at this point in the history
  • Loading branch information
curiousdannii committed Feb 13, 2024
1 parent 219d1ad commit 7a1b3a6
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 9 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[workspace]
members = [
"emscripten_em_js",
"remglk",
"remglk_capi",
]
Expand Down
19 changes: 19 additions & 0 deletions emscripten_em_js/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "emscripten_em_js"
version = "0.1.0"
edition = "2021"

authors = ["Dannii Willis <[email protected]>"]
description = "Rust versions of the Emscripten EM_JS and EM_ASYNC_JS macros"
homepage = "https://github.com/curiousdannii/remglk-rs"
license = "MIT"
repository = "https://github.com/curiousdannii/remglk-rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
proc-macro = true

[dependencies]
quote = "1.0.35"
syn = {version="2.0.48", features=["full"]}
122 changes: 122 additions & 0 deletions emscripten_em_js/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
Rust versions of the Emscripten EM_JS and EM_ASYNC_JS macros
============================================================
Copyright (c) 2024 Dannii Willis
MIT licenced
https://github.com/curiousdannii/remglk-rs
*/

//! em_js!() declares a Javascript function. It is largely similar to the Emscripten macro `EM_JS`.
//!
//! ```c
//! EM_JS(int, add, (int x, int y), {
//! return x + y;
//! })
//! ```
//!
//! But instead of separate parameters, it takes a whole Rust function declaration, formatted as usual.
//! The Javascript code must be included as a string. If your JS code uses double quotes, you can use a
//! raw string.
//!
//! ```
//! em_js!(fn add(x: i32, y: i32) -> i32 { r#"
//! return x + y;
//! "# })
//! ```
//!
//! You may also declare async functions. Unlike in Emscripten where you would use the `EM_ASYNC_JS`
//! macro, these use the same macro, just declare the function as `async`:
//!
//! ```
//! em_js!(async fn add(x: i32, y: i32) -> i32 { r#"
//! return x + y;
//! "# })
//! ```
//!
//! Supported types:
//!
//! | Type | Input | Output |
//! |-------|-------|--------|
//! | [f64] | Y | Y |
//! | [i32] | Y | Y |
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{Block, Expr, FnArg, ItemFn, Lit, Pat, Stmt, Type};
use syn::punctuated::Punctuated;
use syn::token::Comma;

/** em_js!() declares a Javascript function. It is largely similar to the Emscripten macro `EM_JS`.
*
* For examples, and supported types, see [the module documentation](crate).
*/
#[proc_macro]
pub fn em_js(input: TokenStream) -> TokenStream {
let parsed = syn::parse::<ItemFn>(input).unwrap();
let name = parsed.sig.ident;
let link_name = name.to_string();
let js_name = format_ident!("__em_js__{}{}", if parsed.sig.asyncness.is_some() {"__asyncjs__"} else {""}, name);
let inputs = parsed.sig.inputs;
let output = parsed.sig.output;
let body = format!("({})<::>{{{}}}", rust_args_to_c(&inputs), get_body_str(parsed.block.as_ref()));

let result = quote! {
extern "C" {
#[link_name = #link_name]
pub fn #name(#inputs) #output;
}

#[link_section = ".em_js"]
#[no_mangle]
#[used]
static #js_name: &str = #body;
};

// Do I need to manually emit bytes? https://github.com/rust-lang/rust/issues/70239

result.into()
}

fn get_body_str(block: &Block) -> String {
let body = &block.stmts[0];
if let Stmt::Expr(Expr::Lit(lit), _) = body {
if let Lit::Str(body) = &lit.lit {
return body.value().to_owned();
}
}
panic!("em_js body was not string");
}

fn rust_args_to_c(args: &Punctuated<FnArg, Comma>) -> String {
let mut results: Vec<String> = vec![];
for arg in args.iter() {
let c_type = if let FnArg::Typed(arg) = arg {
let name = if let Pat::Ident(name) = arg.pat.as_ref() {
&name.ident
}
else {
unreachable!();
};
let rust_type = if let Type::Path(path) = arg.ty.as_ref() {
path.path.segments.first().unwrap().ident.to_string()
}
else {
unreachable!();
};
let c_type = match rust_type.as_str() {
"f64" => "double",
"i32" => "int",
other => panic!("unsupported argument type: {}", other),
};
format!("{} {}", c_type, name)
}
else {
panic!("self arg in em_js");
};
results.push(c_type);
}
results.join(", ")
}
3 changes: 3 additions & 0 deletions remglk_capi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ serde_json = "1.0"
thiserror = "1.0.40"
widestring = "1.0.2"

[target.'cfg(emscripten)'.dependencies]
emscripten_em_js = {path = "../emscripten_em_js", version = "0.1.0"}

[build-dependencies]
cc = "1.0"
16 changes: 7 additions & 9 deletions remglk_capi/src/glkapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,15 @@ pub type StreamPtr = *const Mutex<GlkObjectMetadata<Stream>>;
pub type WindowPtr = *const Mutex<GlkObjectMetadata<Window>>;
type WindowPtrMut = *mut Mutex<GlkObjectMetadata<Window>>;

#[cfg(target_os = "emscripten")]
#[path = "systems/emglken.rs"]
mod system;

#[cfg(not(target_os = "emscripten"))]
#[path = "systems/standard.rs"]
mod standard;
use standard::StandardSystem;
type GlkApi = glkapi::GlkApi<StandardSystem>;
mod system;

pub fn glkapi() -> &'static Mutex<GlkApi> {
static GLKAPI: OnceLock<Mutex<GlkApi>> = OnceLock::new();
GLKAPI.get_or_init(|| {
Mutex::new(GlkApi::new(StandardSystem::default()))
})
}
pub use system::{glkapi, GlkApi};

// TODO: error handling!

Expand Down
80 changes: 80 additions & 0 deletions remglk_capi/src/systems/emglken.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Emglken system
==============
Copyright (c) 2024 Dannii Willis
MIT licenced
https://github.com/curiousdannii/remglk-rs
*/

use std::collections::HashMap;
use std::env::temp_dir;
use std::fs;
use std::io::{self, BufRead};
use std::path::Path;

use emscripten_em_js::em_js;

use super::*;
use remglk::GlkSystem;
use glkapi::protocol::{Event, SystemFileRef, Update};

pub type GlkApi = glkapi::GlkApi<EmglkenSystem>;

pub fn glkapi() -> &'static Mutex<GlkApi> {
static GLKAPI: OnceLock<Mutex<GlkApi>> = OnceLock::new();
GLKAPI.get_or_init(|| {
Mutex::new(GlkApi::new(EmglkenSystem::default()))
})
}

#[derive(Default)]
pub struct EmglkenSystem {
cache: HashMap<String, Box<[u8]>>,
tempfile_counter: u32,
}

impl GlkSystem for EmglkenSystem {
fn fileref_construct(&mut self, filename: String, filetype: FileType, gameid: Option<String>) -> SystemFileRef {
SystemFileRef {
filename,
gameid,
usage: Some(filetype),
..Default::default()
}
}

fn fileref_delete(&mut self, fileref: &SystemFileRef) {
unimplemented!()
}

fn fileref_exists(&mut self, fileref: &SystemFileRef) -> bool {
unimplemented!()
}

fn fileref_read(&mut self, fileref: &SystemFileRef) -> Option<Box<[u8]>> {
unimplemented!()
}

fn fileref_temporary(&mut self, filetype: FileType) -> SystemFileRef {
unimplemented!()
}

fn fileref_write_buffer(&mut self, fileref: &SystemFileRef, buf: Box<[u8]>) {
unimplemented!()
}

fn flush_writeable_files(&mut self) {
unimplemented!()
}

fn get_glkote_event(&mut self) -> Event {
unimplemented!()
}

fn send_glkote_update(&mut self, update: Update) {
unimplemented!()
}
}
9 changes: 9 additions & 0 deletions remglk_capi/src/systems/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ use super::*;
use remglk::GlkSystem;
use glkapi::protocol::{Event, SystemFileRef, Update};

pub type GlkApi = glkapi::GlkApi<StandardSystem>;

pub fn glkapi() -> &'static Mutex<GlkApi> {
static GLKAPI: OnceLock<Mutex<GlkApi>> = OnceLock::new();
GLKAPI.get_or_init(|| {
Mutex::new(GlkApi::new(StandardSystem::default()))
})
}

#[derive(Default)]
pub struct StandardSystem {
cache: HashMap<String, Box<[u8]>>,
Expand Down

0 comments on commit 7a1b3a6

Please sign in to comment.