stdweb/webcore/
macros.rs

1use webcore::try_from::TryInto;
2use webcore::value::Value;
3
4macro_rules! next {
5    (empty) => {};
6
7    ((peel, $callback:tt, ($value:tt))) => {
8        $callback!( empty => );
9    };
10
11    ((peel, $callback:tt, ($value:tt, $($other:tt),+))) => {
12        $callback!( (peel, $callback, ($($other),+)) => $($other),+ );
13    };
14}
15
16macro_rules! foreach {
17    ($callback:tt => $($values:tt),*) => {
18        $callback!( (peel, $callback, ($($values),*)) => $($values),* );
19    };
20}
21
22macro_rules! loop_through_identifiers {
23    ($callback:tt) => {
24        foreach!( $callback => A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12 );
25    };
26}
27
28#[doc(hidden)]
29#[macro_export]
30macro_rules! __js_raw_asm {
31    ($code:expr, $($token:expr),*) => {{
32        #[$crate::private::js_raw_attr]
33        fn snippet() {
34            call!( $code, $($token),* );
35        }
36
37        snippet( $($token as *const u8),* )
38    }};
39
40    ($code:expr) => { $crate::__js_raw_asm!( $code, ) };
41}
42
43#[doc(hidden)]
44#[macro_export]
45macro_rules! __js_raw_asm_int {
46    ($code:expr, $($token:expr),*) => {{
47        #[$crate::private::js_raw_attr]
48        fn snippet() -> i32 {
49            call!( $code, $($token),* );
50        }
51
52        snippet( $($token as *const u8),* )
53    }};
54
55    ($code:expr) => { $crate::__js_raw_asm_int!( $code, ) };
56}
57
58// TODO: This should be handled inside of the procedural macro.
59#[cfg(not(all(target_arch = "wasm32", target_vendor = "unknown", target_os = "unknown", not(cargo_web))))]
60#[doc(hidden)]
61#[macro_export]
62macro_rules! __js_raw_asm_bool {
63    ($code:expr, $($token:expr),*) => {{
64        #[$crate::private::js_raw_attr]
65        fn snippet() -> i32 {
66            call!( $code, $($token),* );
67        }
68
69        snippet( $($token as *const u8),* )
70    } == 1};
71
72    ($code:expr) => { $crate::__js_raw_asm_bool!( $code, ) };
73}
74
75#[cfg(all(target_arch = "wasm32", target_vendor = "unknown", target_os = "unknown", not(cargo_web)))]
76#[doc(hidden)]
77#[macro_export]
78macro_rules! __js_raw_asm_bool {
79    ($code:expr, $($token:expr),*) => {{
80        #[$crate::private::js_raw_attr]
81        fn snippet() -> bool {
82            call!( $code, $($token),* );
83        }
84
85        snippet( $($token as *const u8),* )
86    }};
87
88    ($code:expr) => { $crate::__js_raw_asm_bool!( $code, ) };
89}
90
91// Abandon all hope, ye who enter here!
92//
93// If there was a contest for the ugliest and most hacky macro ever written,
94// I would enter this one.
95//
96// There is probably a more clever way to write this macro, but oh well.
97#[doc(hidden)]
98#[macro_export]
99macro_rules! _js_impl {
100    (@if no_return in [no_return $($rest:tt)*] {$($true_case:tt)*} else {$($false_case:tt)*}) => {
101        $($true_case)*
102    };
103
104    (@if $condition:tt in [] {$($true_case:tt)*} else {$($false_case:tt)*}) => {
105        $($false_case)*
106    };
107
108    (@if $condition:tt in [$token:tt $($rest:tt)*] {$($true_case:tt)*} else {$($false_case:tt)*}) => {
109        $crate::_js_impl!( @if $condition in [$($rest)*] {$($true_case)*} else {$($false_case)*} );
110    };
111
112    (@serialize [] [$($names:tt)*]) => {};
113    (@serialize [$arg:tt $($rest_args:tt)*] [$name:tt $($rest_names:tt)*]) => {
114        let $name = $arg;
115        let $name = $crate::private::IntoNewtype::into_newtype( $name );
116        let mut $name = Some( $name );
117        let $name = $crate::private::JsSerializeOwned::into_js_owned( &mut $name );
118        let $name = &$name as *const $crate::private::SerializedValue as *const _;
119        $crate::_js_impl!( @serialize [$($rest_args)*] [$($rest_names)*] );
120    };
121
122    (@call [$($code:tt)*] [$($flags:tt)*] [$($args:tt)*] [$($arg_names:tt)*] [$($unused_arg_names:tt)*] ->) => {
123        // It'd be nice to put at least some of this inside a function inside the crate,
124        // but then it wouldn't work (I tried!) as the string with the code wouldn't be
125        // passed as a direct reference to a constant, and Emscripten needs that to actually
126        // use the JavaScript code we're passing to it.
127        {
128            if cfg!( test ) {
129                $crate::initialize();
130            }
131
132            let restore_point = $crate::private::ArenaRestorePoint::new();
133            $crate::_js_impl!( @serialize [$($args)*] [$($arg_names)*] );
134
135            #[allow(unused_unsafe, unused_parens)]
136            let result = unsafe {
137                $crate::_js_impl!(
138                    @if no_return in [$($flags)*] {{
139                        #[$crate::private::js_no_return_attr]
140                        fn snippet() {
141                            call!( $($code)* );
142                        }
143
144                        snippet( $($arg_names),* );
145                    }} else {{
146                        let mut result: $crate::private::SerializedValue = std::default::Default::default();
147                        let result_ptr = &mut result as *mut $crate::private::SerializedValue as *mut _;
148
149                        #[$crate::private::js_attr]
150                        fn snippet() {
151                            call!( $($code)* );
152                        }
153
154                        snippet( result_ptr, $($arg_names),* );
155
156                        result.deserialize()
157                    }}
158                )
159            };
160
161            std::mem::drop( restore_point );
162            result
163        }
164    };
165
166    (@call [$($code:tt)*] [$($flags:tt)*] [$($args:tt)*] [$($arg_names:tt)*] [$($unused_arg_names:tt)*] -> { $($inner:tt)* } $($rest:tt)*) => {
167        $crate::_js_impl!( @call [$($code)*] [$($flags)*] [$($args)*] [$($arg_names)*] [$($unused_arg_names)*] -> $($inner)* $($rest)* );
168    };
169
170    (@call [$($code:tt)*] [$($flags:tt)*] [$($args:tt)*] [$($arg_names:tt)*] [$($unused_arg_names:tt)*] -> ( $($inner:tt)* ) $($rest:tt)*) => {
171        $crate::_js_impl!( @call [$($code)*] [$($flags)*] [$($args)*] [$($arg_names)*] [$($unused_arg_names)*] -> $($inner)* $($rest)* );
172    };
173
174    (@call [$($code:tt)*] [$($flags:tt)*] [$($args:tt)*] [$($arg_names:tt)*] [$($unused_arg_names:tt)*] -> [ $($inner:tt)* ] $($rest:tt)*) => {
175        $crate::_js_impl!( @call [$($code)*] [$($flags)*] [$($args)*] [$($arg_names)*] [$($unused_arg_names)*] -> $($inner)* $($rest)* );
176    };
177
178    (@call [$($code:tt)*] [$($flags:tt)*] [$($args:tt)*] [$($arg_names:tt)*] [$arg_name:tt $($unused_arg_names:tt)*] -> @{$arg:expr} $($rest:tt)*) => {
179        $crate::_js_impl!( @call [$($code)*] [$($flags)*] [$($args)* $arg] [$($arg_names)* $arg_name] [$($unused_arg_names)*] -> $($rest)* );
180    };
181
182    (@call [$($code:tt)*] [$($flags:tt)*] [$($args:tt)*] [$($arg_names:tt)*] [$($unused_arg_names:tt)*] -> $token:tt $($rest:tt)*) => {
183        $crate::_js_impl!( @call [$($code)*] [$($flags)*] [$($args)*] [$($arg_names)*] [$($unused_arg_names)*] -> $($rest)* );
184    };
185}
186
187/// Embeds JavaScript code into your Rust program.
188///
189/// This macro supports normal JavaScript syntax, albeit with a few limitations:
190///
191///   * String literals delimited with `'` are not supported.
192///   * Semicolons are always required.
193///   * The macro will hit the default recursion limit pretty fast, so you'll
194///     probably want to increase it with `#![recursion_limit="500"]`.
195///     (This is planned to be fixed once procedural macros land in stable Rust.)
196///   * Any callbacks passed into JavaScript will **leak memory** by default!
197///     You need to call `.drop()` on the callback from the JavaScript side to free it.
198///
199/// You can pass Rust expressions into the JavaScript code with `@{...expr...}`.
200/// The value returned by this macro is an instance of [Value](enum.Value.html).
201///
202/// # Examples
203///
204/// ## Regular Usage
205///
206/// ```
207/// let name = "Bob";
208/// let result = js! {
209///     console.log( "Hello " + @{name} + "!" );
210///     return 2 + 2;
211/// };
212///
213/// println!( "2 + 2 = {:?}", result );
214/// ```
215///
216/// Note: you **must** include the `return ...;` statement to get a value.
217///
218/// ## No Return
219///
220/// If you don't need to return a value from your snippet you can add a @(no_return) attribute to
221/// slightly improve performance.
222///
223/// ```
224/// let name = "Bob";
225/// js! { @(no_return)
226///     console.log( "Hello " + @{name} + "!" );
227/// };
228/// ```
229#[macro_export]
230macro_rules! js {
231    (@($($flags:tt),*) $($token:tt)*) => {
232        $crate::_js_impl!( @call [$($token)*] [$($flags)*] [] [] [a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15] -> $($token)* )
233    };
234
235    ($($token:tt)*) => {
236        js! { @() $($token)* }
237    };
238}
239
240#[doc(hidden)]
241#[macro_export]
242macro_rules! __js_serializable_boilerplate {
243    ($kind:tt) => {
244        __js_serializable_boilerplate!( () ($kind) () );
245    };
246
247    (impl< $($impl_arg:tt),* > for $kind:ty where $($bounds:tt)*) => {
248        __js_serializable_boilerplate!( ($($impl_arg),*) ($kind) ($($bounds)*) );
249    };
250
251    (impl< $($impl_arg:tt),* > for $kind:ty) => {
252        __js_serializable_boilerplate!( ($($impl_arg),*) ($kind) () );
253    };
254
255    (($($impl_arg:tt)*) ($($kind_arg:tt)*) ($($bounds:tt)*)) => {
256        impl< $($impl_arg)* > $crate::private::JsSerializeOwned for $($kind_arg)* where $($bounds)* {
257            #[inline]
258            fn into_js_owned< '_a >( value: &'_a mut Option< Self > ) -> $crate::private::SerializedValue< '_a > {
259                $crate::private::JsSerialize::_into_js( value.as_ref().unwrap() )
260            }
261        }
262
263        impl< '_r, $($impl_arg)* > $crate::private::JsSerializeOwned for &'_r $($kind_arg)* where $($bounds)* {
264            #[inline]
265            fn into_js_owned< '_a >( value: &'_a mut Option< Self > ) -> $crate::private::SerializedValue< '_a > {
266                $crate::private::JsSerialize::_into_js( value.unwrap() )
267            }
268        }
269    };
270}
271
272macro_rules! error_boilerplate {
273    ($type_name:ident) => {
274        impl std::fmt::Display for $type_name {
275            fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
276                write!(formatter, "{}: {}", stringify!($type_name), self.message())
277            }
278        }
279
280        impl std::error::Error for $type_name {
281            fn description(&self) -> &str {
282                stringify!($type_name)
283            }
284        }
285    };
286
287    ($type_name:ident, dom_exception = $error_name:expr) => {
288        impl ::InstanceOf for $type_name {
289            #[inline]
290            fn instance_of( reference: &Reference ) -> bool {
291                $crate::__js_raw_asm_bool!(
292                    concat!(
293                        "var r = Module.STDWEB_PRIVATE.acquire_js_reference( $0 );",
294                        "return (r instanceof DOMException) && (r.name === \"", $error_name, "\");"
295                    ),
296                    reference.as_raw()
297                )
298            }
299        }
300
301        error_boilerplate!( $type_name );
302    };
303}
304
305macro_rules! instanceof {
306    ($value:expr, $kind:ident) => {{
307        use $crate::unstable::TryInto;
308        let reference: Option< &$crate::Reference > = (&$value).try_into().ok();
309        reference.map( |reference| {
310            $crate::__js_raw_asm_int!(
311                concat!( "return (Module.STDWEB_PRIVATE.acquire_js_reference( $0 ) instanceof ", stringify!( $kind ), ") | 0;" ),
312                reference.as_raw()
313            ) == 1
314        }).unwrap_or( false )
315    }};
316}
317
318macro_rules! newtype_enum {
319    ($name:ident {
320        $(
321            $(#[$attr:meta])*
322            $variant:ident = $value:expr
323        ),* $(,)*
324    }) => {
325        impl $name {
326            $(
327                $(#[$attr])*
328                pub const $variant: $name = $name($value);
329            )*
330        }
331        impl std::fmt::Debug for $name {
332            fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
333                match self.0 {
334                    $($value => write!(formatter, "{}::{}", stringify!($name), stringify!($variant)),)*
335                    other => write!(formatter, "{}({})", stringify!($name), other)
336                }
337            }
338        }
339    }
340}
341
342// This helps with type inference and converts the outer error
343// type when the `TryInto`'s error type in the `js_try!`'s
344// success and error cases differ.
345#[inline]
346pub fn js_try_convert< T, E, P >( value: Value ) -> Result< Result< T, E >, P >
347    where Value: TryInto< T >, <Value as TryInto< T >>::Error: Into< P >
348{
349    match value.try_into() {
350        Ok( value ) => Ok( Ok( value ) ),
351        Err( error ) => Err( error.into() )
352    }
353}
354
355/// Embeds JavaScript code into your Rust program similar to the `js!` macro, but
356/// catches errors that may be thrown.
357///
358/// This macro will attempt to coerce the value into the inferred `Result` type.
359/// The success and error types should implement `TryFrom<Value>`.
360///
361/// # Examples
362///
363/// ```
364/// let result: Result<i32, String> = js_try! {
365///     throw "error";
366/// }.unwrap();
367/// assert_eq!(result, Err("error".to_string()));
368/// ```
369macro_rules! js_try {
370    (@(no_return) $($token:tt)*) => {{
371        let result = js! {
372            try {
373                $($token)*
374                return {
375                    success: true
376                };
377            } catch( error ) {
378                return {
379                    error: error,
380                    success: false
381                };
382            }
383        };
384
385        use ::webcore::try_from::TryInto;
386        if js!( return @{result.as_ref()}.success; ) == true {
387            Ok(Ok(()))
388        } else {
389            match js!( return @{result}.error; ).try_into() {
390                Ok(e) => Ok(Err(e)),
391                Err(e) => Err(e),
392            }
393        }
394    }};
395
396    ($($token:tt)*) => {{
397        let result = js! {
398            try {
399                return {
400                    value: function() { $($token)* }(),
401                    success: true
402                };
403            } catch( error ) {
404                return {
405                    error: error,
406                    success: false
407                };
408            }
409        };
410
411        use webcore::try_from::TryInto;
412        if js!( return @{result.as_ref()}.success; ) == true {
413            ::webcore::macros::js_try_convert( js!( return @{result}.value; ) )
414        } else {
415            match js!( return @{result}.error; ).try_into() {
416                Ok(e) => Ok(Err(e)),
417                Err(e) => Err(e),
418            }
419        }
420    }};
421}
422
423macro_rules! comma_join {
424    ($a:ident) => {
425        stringify!( $a )
426    };
427
428    ($a:ident $b:ident) => {
429        concat!(
430            stringify!( $a ),
431            " or ",
432            stringify!( $b ),
433        )
434    };
435
436    ($a:ident $($item:ident)+) => {
437        concat!(
438            stringify!( $a ),
439            ", ",
440            comma_join!( $($item)+ )
441        )
442    };
443}
444
445macro_rules! error_enum_boilerplate {
446    ($( #[ $error_meta:meta ] )* $error_name:ident, $( $( #[ $variant_meta:meta ] )* $variant:ident),*) => {
447        $( #[ $error_meta ] )*
448        #[derive(Debug, Clone)]
449        pub enum $error_name {
450            $(
451                $( #[ $variant_meta ] )*
452                $variant($variant)
453            ),*
454        }
455
456        impl TryFrom<::webcore::value::Value> for $error_name {
457            type Error = ::webcore::value::ConversionError;
458
459            fn try_from(value: ::webcore::value::Value) -> Result<Self, Self::Error> {
460                $(
461                    if let Ok(v) = $variant::try_from(value.clone()) {
462                        return Ok($error_name::$variant(v));
463                    }
464                )*
465
466                let expected = comma_join!( $($variant)+ ).into();
467                Err( ::webcore::value::ConversionError::type_mismatch( &value, expected ) )
468            }
469        }
470
471        impl std::fmt::Display for $error_name {
472            fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
473                match *self {
474                    $($error_name::$variant( ref r ) => r.fmt(formatter),)*
475                }
476            }
477        }
478
479        impl std::error::Error for $error_name {
480            fn description(&self) -> &str {
481                stringify!($error_name)
482            }
483        }
484
485        impl ::webcore::serialization::JsSerialize for $error_name {
486            #[doc(hidden)]
487            #[inline]
488            fn _into_js< 'a >( &'a self ) -> ::webcore::serialization::SerializedValue< 'a > {
489                let reference: &::webcore::value::Reference = match self {
490                    $(
491                        &$error_name::$variant( ref variant ) => variant.as_ref(),
492                    )+
493                };
494
495                reference._into_js()
496            }
497        }
498    }
499}
500
501#[cfg(test)]
502mod tests {
503    use webcore::value::Value;
504
505    #[test]
506    fn js_try() {
507        let v: Result<Value, Value> = js_try!( return "test"; ).unwrap();
508        assert_eq!( v, Ok(Value::String("test".to_string())) );
509
510        let v: Result<bool, bool> = js_try!( return true; ).unwrap();
511        assert_eq!( v, Ok(true) );
512
513        let v: Result<bool, bool> = js_try!( throw true; ).unwrap();
514        assert_eq!( v, Err(true) );
515
516        let v: Result<i32, String> = js_try!( throw "error"; ).unwrap();
517        assert_eq!( v, Err("error".to_string()) );
518
519        let v: Result<(), String> = js_try!( @(no_return) 2+2; ).unwrap();
520        assert_eq!( v, Ok(()) );
521
522        let v: Result<(), f64> = js_try!( @(no_return) throw 3.3; ).unwrap();
523        assert_eq!( v, Err(3.3) );
524
525        let v: Result< Result<i32, i32>, _ > = js_try!( return "f"; );
526        assert!( v.is_err() );
527
528        let v: Result< Result<i32, i32>, _ > = js_try!( throw "Broken"; );
529        assert!( v.is_err() );
530    }
531
532    #[test]
533    fn js_try_from_value_to_value() {
534        let output: Result< Value, String > = js_try!( return null; ).unwrap();
535        assert_eq!( output, Ok( Value::Null ) );
536    }
537}