Skip to content

Commit

Permalink
feat: impl Date API (closes #70)
Browse files Browse the repository at this point in the history
  • Loading branch information
amilajack committed Nov 21, 2020
1 parent 6a3848b commit f538442
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 20 deletions.
9 changes: 5 additions & 4 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ jobs:
run: sudo apt-get install -y g++-4.8
- name: Start Xvfb
run: |
Xvfb :99 &
disown -ar
echo "::set-env name=DISPLAY:::99"
sudo apt-get install libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0
sudo apt-get install xvfb
export DISPLAY=:99.0
Xvfb -ac :99 -screen 0 1280x1024x16 > /dev/null 2>&1 &
- name: run cargo test
run: cargo test --release -- --nocapture
run: xvfb-run --auto-servernum cargo test --release -- --nocapture
14 changes: 14 additions & 0 deletions crates/neon-runtime/src/napi/date.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use nodejs_sys as napi;
use raw::{Env, Local};

pub unsafe extern "C" fn new_date(env: Env, out: *mut Local, num: f64) {
let status = napi::napi_create_date(env, num, out);
assert_eq!(status, napi::napi_status::napi_ok);
}

pub unsafe extern "C" fn value(env: Env, p: Local) -> f64 {
let mut value = 0.0;
let status = napi::napi_get_date_value(env, p, &mut value as *mut f64);
assert_eq!(status, napi::napi_status::napi_ok);
return value;
}
1 change: 1 addition & 0 deletions crates/neon-runtime/src/napi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ pub mod string;
pub mod tag;
pub mod task;
pub mod handler;
pub mod date;
39 changes: 23 additions & 16 deletions src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use context::internal::Env;
use handle::{Managed, Handle};
use types::{JsValue, Value, JsObject, JsArray, JsFunction, JsBoolean, JsNumber, JsString, StringResult, JsNull, JsUndefined};
#[cfg(feature = "napi-runtime")]
use types::{JsDate};
#[cfg(feature = "napi-runtime")]
use types::boxed::{Finalize, JsBox};
use types::binary::{JsArrayBuffer, JsBuffer};
use types::error::JsError;
Expand Down Expand Up @@ -131,22 +133,22 @@ impl<'a> Lock<'a> {
}

/// An _execution context_, which provides context-sensitive access to the JavaScript engine. Most operations that interact with the engine require passing a reference to a context.
///
///
/// A context has a lifetime `'a`, which ensures the safety of handles managed by the JS garbage collector. All handles created during the lifetime of a context are kept alive for that duration and cannot outlive the context.
pub trait Context<'a>: ContextInternal<'a> {

/// Lock the JavaScript engine, returning an RAII guard that keeps the lock active as long as the guard is alive.
///
///
/// If this is not the currently active context (for example, if it was used to spawn a scoped context with `execute_scoped` or `compute_scoped`), this method will panic.
fn lock(&self) -> Lock<'_> {
self.check_active();
Lock::new(self.env())
}

/// Convenience method for locking the JavaScript engine and borrowing a single JS value's internals.
///
///
/// # Example:
///
///
/// ```no_run
/// # use neon::prelude::*;
/// # fn my_neon_function(mut cx: FunctionContext) -> JsResult<JsNumber> {
Expand All @@ -156,7 +158,7 @@ pub trait Context<'a>: ContextInternal<'a> {
/// # Ok(n)
/// # }
/// ```
///
///
/// Note: the borrowed value is required to be a reference to a handle instead of a handle
/// as a workaround for a [Rust compiler bug](https://github.com/rust-lang/rust/issues/29997).
/// We may be able to generalize this compatibly in the future when the Rust bug is fixed,
Expand All @@ -173,9 +175,9 @@ pub trait Context<'a>: ContextInternal<'a> {
}

/// Convenience method for locking the JavaScript engine and mutably borrowing a single JS value's internals.
///
///
/// # Example:
///
///
/// ```no_run
/// # use neon::prelude::*;
/// # fn my_neon_function(mut cx: FunctionContext) -> JsResult<JsUndefined> {
Expand All @@ -187,7 +189,7 @@ pub trait Context<'a>: ContextInternal<'a> {
/// # Ok(cx.undefined())
/// # }
/// ```
///
///
/// Note: the borrowed value is required to be a reference to a handle instead of a handle
/// as a workaround for a [Rust compiler bug](https://github.com/rust-lang/rust/issues/29997).
/// We may be able to generalize this compatibly in the future when the Rust bug is fixed,
Expand All @@ -204,9 +206,9 @@ pub trait Context<'a>: ContextInternal<'a> {
}

/// Executes a computation in a new memory management scope.
///
///
/// Handles created in the new scope are kept alive only for the duration of the computation and cannot escape.
///
///
/// This method can be useful for limiting the life of temporary values created during long-running computations, to prevent leaks.
fn execute_scoped<T, F>(&self, f: F) -> T
where F: for<'b> FnOnce(ExecuteContext<'b>) -> T
Expand All @@ -219,9 +221,9 @@ pub trait Context<'a>: ContextInternal<'a> {
}

/// Executes a computation in a new memory management scope and computes a single result value that outlives the computation.
///
///
/// Handles created in the new scope are kept alive only for the duration of the computation and cannot escape, with the exception of the result value, which is rooted in the outer context.
///
///
/// This method can be useful for limiting the life of temporary values created during long-running computations, to prevent leaks.
fn compute_scoped<V, F>(&self, f: F) -> JsResult<'a, V>
where V: Value,
Expand Down Expand Up @@ -269,14 +271,14 @@ pub trait Context<'a>: ContextInternal<'a> {
}

/// Convenience method for creating a `JsString` value.
///
///
/// If the string exceeds the limits of the JS engine, this method panics.
fn string<S: AsRef<str>>(&mut self, s: S) -> Handle<'a, JsString> {
JsString::new(self, s)
}

/// Convenience method for creating a `JsString` value.
///
///
/// If the string exceeds the limits of the JS engine, this method returns an `Err` value.
fn try_string<S: AsRef<str>>(&mut self, s: S) -> StringResult<'a> {
JsString::try_new(self, s)
Expand Down Expand Up @@ -318,6 +320,11 @@ pub trait Context<'a>: ContextInternal<'a> {
JsBuffer::new(self, size)
}

#[cfg(feature = "napi-runtime")]
fn date<V: Into<f64>>(&mut self, date: V) -> Handle<'a, JsDate> {
JsDate::new(self, date)
}

/// Produces a handle to the JavaScript global object.
fn global(&mut self) -> Handle<'a, JsObject> {
JsObject::build(|out| {
Expand Down Expand Up @@ -372,7 +379,7 @@ pub trait Context<'a>: ContextInternal<'a> {
/// Convenience method for wrapping a value in a `JsBox`.
///
/// # Example:
///
///
/// ```rust
/// # use neon::prelude::*;
/// struct Point(usize, usize);
Expand Down Expand Up @@ -500,7 +507,7 @@ impl<'a, 'b> ContextInternal<'a> for ComputeContext<'a, 'b> {
impl<'a, 'b> Context<'a> for ComputeContext<'a, 'b> { }

/// A view of the JS engine in the context of a function call.
///
///
/// The type parameter `T` is the type of the `this`-binding.
pub struct CallContext<'a, T: This> {
scope: Scope<'a, raw::HandleScope>,
Expand Down
56 changes: 56 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,62 @@ impl ValueInternal for JsNumber {
}
}

/// A JavaScript Date object.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct JsDate(raw::Local);

impl Value for JsDate { }

impl Managed for JsDate {
fn to_raw(self) -> raw::Local { self.0 }

fn from_raw(_: Env, h: raw::Local) -> Self { JsDate(h) }
}

impl JsDate {
pub const MIN_VALID_VALUE: f64 = -8640000000000000.0;
pub const MAX_VALID_VALUE: f64 = 8640000000000000.0;

#[cfg(feature = "napi-runtime")]
pub(crate) fn new_internal<'a>(env: Env, value: f64) -> Handle<'a, JsDate> {
unsafe {
let mut local: raw::Local = std::mem::zeroed();
neon_runtime::date::new_date( env.to_raw(), &mut local, value);
Handle::new_internal(JsDate(local))
}
}

#[cfg(feature = "napi-runtime")]
pub fn new<'a, C: Context<'a>, T: Into<f64>>(cx: &mut C, time: T) -> Handle<'a, JsDate> {
JsDate::new_internal(cx.env(), time.into())
}

#[cfg(feature = "napi-runtime")]
pub fn value<'a, C: Context<'a>>(self, cx: &mut C) -> f64 {
let env = cx.env().to_raw();
unsafe {
neon_runtime::date::value(env, self.to_raw())
}
}

#[cfg(feature = "napi-runtime")]
pub fn is_valid<'a, C: Context<'a>>(self, cx: &mut C) -> bool {
let value = self.value(cx);
return value <= JsDate::MAX_VALID_VALUE && value >= JsDate::MIN_VALID_VALUE;
}
}

impl ValueInternal for JsDate {
fn name() -> String { "object".to_string() }

fn is_typeof<Other: Value>(env: Env, other: Other) -> bool {
unsafe { neon_runtime::tag::is_object(env.to_raw(), other.to_raw()) }
}
}

impl Object for JsDate { }

/// A JavaScript object.
#[repr(C)]
#[derive(Clone, Copy)]
Expand Down
30 changes: 30 additions & 0 deletions test/napi/lib/date.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
var addon = require('../native');
var assert = require('chai').assert;

describe('JsDate', function() {
it('should create a date', function () {
const date = addon.create_date();
assert.instanceOf(date, Date);
});

it('should create date from time', function () {
const date = addon.create_date_from_value(31415);
assert.instanceOf(date, Date);
assert.equal(date.toUTCString(), new Date(31415).toUTCString());
});

it('should check if date is valid', function () {
const dateIsValid = addon.check_date_is_valid(31415);
assert.isTrue(dateIsValid);
});

it('should check if date is invalid', function () {
const dateIsValid = addon.check_date_is_invalid();
assert.isFalse(dateIsValid);
});

it('should get date value', function () {
const dateValue = addon.get_date_value();
assert.equal(dateValue, 31415);
});
});
34 changes: 34 additions & 0 deletions test/napi/native/src/js/date.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use neon::prelude::*;
use neon::types::JsDate;

pub fn create_date(mut cx: FunctionContext) -> JsResult<JsDate> {
let date = cx.date(31415);
Ok(date)
}

pub fn create_date_from_value(mut cx: FunctionContext) -> JsResult<JsDate> {
let time = cx.argument::<JsNumber>(0)?.value(&mut cx);
let date = cx.date(time);
Ok(date)
}

pub fn check_date_is_valid(mut cx: FunctionContext) -> JsResult<JsBoolean> {
let time = cx.argument::<JsNumber>(0)?.value(&mut cx);
let date = cx.date(time);
let is_valid = date.is_valid(&mut cx);
Ok(cx.boolean(is_valid))
}

pub fn check_date_is_invalid(mut cx: FunctionContext) -> JsResult<JsBoolean> {
let time = 2f64.powf(64.0);
let date = cx.date(time);
let is_valid = date.is_valid(&mut cx);
Ok(cx.boolean(is_valid))
}

pub fn get_date_value(mut cx: FunctionContext) -> JsResult<JsNumber> {
let date = cx.date(31415);
let value = date.value(&mut cx);
println!("{:?}", value);
Ok(cx.number(value))
}
8 changes: 8 additions & 0 deletions test/napi/native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod js {
pub mod objects;
pub mod types;
pub mod strings;
pub mod date;
}

use js::arrays::*;
Expand All @@ -21,6 +22,7 @@ use js::numbers::*;
use js::objects::*;
use js::types::*;
use js::strings::*;
use js::date::*;

register_module!(|mut cx| {
let greeting = cx.string("Hello, World!");
Expand Down Expand Up @@ -145,6 +147,12 @@ register_module!(|mut cx| {
cx.export_function("write_buffer_with_lock", write_buffer_with_lock)?;
cx.export_function("write_buffer_with_borrow_mut", write_buffer_with_borrow_mut)?;

cx.export_function("create_date", create_date)?;
cx.export_function("get_date_value", get_date_value)?;
cx.export_function("check_date_is_invalid", check_date_is_invalid)?;
cx.export_function("check_date_is_valid", check_date_is_valid)?;
cx.export_function("create_date_from_value", create_date_from_value)?;

cx.export_function("is_array", is_array)?;
cx.export_function("is_array_buffer", is_array_buffer)?;
cx.export_function("is_boolean", is_boolean)?;
Expand Down

0 comments on commit f538442

Please sign in to comment.