<commit_before>//! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)]

use std::mem;
use std::ptr;
use std::os::raw::{c_int, c_char, c_void};

use ffi;

use {Connection, InnerConnection};

/// Authorizer Action Codes
#[derive(Debug, PartialEq)]
pub enum Action {
    UNKNOWN = -1,
    SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize,
    SQLITE_CREATE_TABLE = ffi::SQLITE_CREATE_TABLE as isize,
    SQLITE_CREATE_TEMP_INDEX = ffi::SQLITE_CREATE_TEMP_INDEX as isize,
    SQLITE_CREATE_TEMP_TABLE = ffi::SQLITE_CREATE_TEMP_TABLE as isize,
    SQLITE_CREATE_TEMP_TRIGGER = ffi::SQLITE_CREATE_TEMP_TRIGGER as isize,
    SQLITE_CREATE_TEMP_VIEW = ffi::SQLITE_CREATE_TEMP_VIEW as isize,
    SQLITE_CREATE_TRIGGER = ffi::SQLITE_CREATE_TRIGGER as isize,
    SQLITE_CREATE_VIEW = ffi::SQLITE_CREATE_VIEW as isize,
    SQLITE_DELETE = ffi::SQLITE_DELETE as isize,
    SQLITE_DROP_INDEX = ffi::SQLITE_DROP_INDEX as isize,
    SQLITE_DROP_TABLE = ffi::SQLITE_DROP_TABLE as isize,
    SQLITE_DROP_TEMP_INDEX = ffi::SQLITE_DROP_TEMP_INDEX as isize,
    SQLITE_DROP_TEMP_TABLE = ffi::SQLITE_DROP_TEMP_TABLE as isize,
    SQLITE_DROP_TEMP_TRIGGER = ffi::SQLITE_DROP_TEMP_TRIGGER as isize,
    SQLITE_DROP_TEMP_VIEW = ffi::SQLITE_DROP_TEMP_VIEW as isize,
    SQLITE_DROP_TRIGGER = ffi::SQLITE_DROP_TRIGGER as isize,
    SQLITE_DROP_VIEW = ffi::SQLITE_DROP_VIEW as isize,
    SQLITE_INSERT = ffi::SQLITE_INSERT as isize,
    SQLITE_PRAGMA = ffi::SQLITE_PRAGMA as isize,
    SQLITE_READ = ffi::SQLITE_READ as isize,
    SQLITE_SELECT = ffi::SQLITE_SELECT as isize,
    SQLITE_TRANSACTION = ffi::SQLITE_TRANSACTION as isize,
    SQLITE_UPDATE = ffi::SQLITE_UPDATE as isize,
    SQLITE_ATTACH = ffi::SQLITE_ATTACH as isize,
    SQLITE_DETACH = ffi::SQLITE_DETACH as isize,
    SQLITE_ALTER_TABLE = ffi::SQLITE_ALTER_TABLE as isize,
    SQLITE_REINDEX = ffi::SQLITE_REINDEX as isize,
    SQLITE_ANALYZE = ffi::SQLITE_ANALYZE as isize,
    SQLITE_CREATE_VTABLE = ffi::SQLITE_CREATE_VTABLE as isize,
    SQLITE_DROP_VTABLE = ffi::SQLITE_DROP_VTABLE as isize,
    SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize,
    SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize,
    SQLITE_COPY = ffi::SQLITE_COPY as isize,
    SQLITE_RECURSIVE = 33,
}

impl From<i32> for Action {
    fn from(code: i32) -> Action {
        match code {
            ffi::SQLITE_CREATE_INDEX => Action::SQLITE_CREATE_INDEX,
            ffi::SQLITE_CREATE_TABLE => Action::SQLITE_CREATE_TABLE,
            ffi::SQLITE_CREATE_TEMP_INDEX => Action::SQLITE_CREATE_TEMP_INDEX,
            ffi::SQLITE_CREATE_TEMP_TABLE => Action::SQLITE_CREATE_TEMP_TABLE,
            ffi::SQLITE_CREATE_TEMP_TRIGGER => Action::SQLITE_CREATE_TEMP_TRIGGER,
            ffi::SQLITE_CREATE_TEMP_VIEW => Action::SQLITE_CREATE_TEMP_VIEW,
            ffi::SQLITE_CREATE_TRIGGER => Action::SQLITE_CREATE_TRIGGER,
            ffi::SQLITE_CREATE_VIEW => Action::SQLITE_CREATE_VIEW,
            ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
            ffi::SQLITE_DROP_INDEX => Action::SQLITE_DROP_INDEX,
            ffi::SQLITE_DROP_TABLE => Action::SQLITE_DROP_TABLE,
            ffi::SQLITE_DROP_TEMP_INDEX => Action::SQLITE_DROP_TEMP_INDEX,
            ffi::SQLITE_DROP_TEMP_TABLE => Action::SQLITE_DROP_TEMP_TABLE,
            ffi::SQLITE_DROP_TEMP_TRIGGER => Action::SQLITE_DROP_TEMP_TRIGGER,
            ffi::SQLITE_DROP_TEMP_VIEW => Action::SQLITE_DROP_TEMP_VIEW,
            ffi::SQLITE_DROP_TRIGGER => Action::SQLITE_DROP_TRIGGER,
            ffi::SQLITE_DROP_VIEW => Action::SQLITE_DROP_VIEW,
            ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
            ffi::SQLITE_PRAGMA => Action::SQLITE_PRAGMA,
            ffi::SQLITE_READ => Action::SQLITE_READ,
            ffi::SQLITE_SELECT => Action::SQLITE_SELECT,
            ffi::SQLITE_TRANSACTION => Action::SQLITE_TRANSACTION,
            ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
            ffi::SQLITE_ATTACH => Action::SQLITE_ATTACH,
            ffi::SQLITE_DETACH => Action::SQLITE_DETACH,
            ffi::SQLITE_ALTER_TABLE => Action::SQLITE_ALTER_TABLE,
            ffi::SQLITE_REINDEX => Action::SQLITE_REINDEX,
            ffi::SQLITE_ANALYZE => Action::SQLITE_ANALYZE,
            ffi::SQLITE_CREATE_VTABLE => Action::SQLITE_CREATE_VTABLE,
            ffi::SQLITE_DROP_VTABLE => Action::SQLITE_DROP_VTABLE,
            ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION,
            ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT,
            ffi::SQLITE_COPY => Action::SQLITE_COPY,
            33 => Action::SQLITE_RECURSIVE,
            _ => Action::UNKNOWN,
        }
    }
}

impl Connection {
    /// Register a callback function to be invoked whenever a transaction is committed.
    ///
    /// The callback returns `true` to rollback.
    pub fn commit_hook<F>(&self, hook: F)
        where F: FnMut() -> bool
    {
        self.db.borrow_mut().commit_hook(hook);
    }

    /// Register a callback function to be invoked whenever a transaction is committed.
    ///
    /// The callback returns `true` to rollback.
    pub fn rollback_hook<F>(&self, hook: F)
        where F: FnMut()
    {
        self.db.borrow_mut().rollback_hook(hook);
    }

    /// Register a callback function to be invoked whenever a row is updated, inserted or deleted in a rowid table.
    ///
    /// The callback parameters are:
    ///
    ///   - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE),
    ///   - the name of the database ("main", "temp", ...),
    ///   - the name of the table that is updated,
    ///   - the ROWID of the row that is updated.
    pub fn update_hook<F>(&self, hook: F)
        where F: FnMut(Action, &str, &str, i64)
    {
        self.db.borrow_mut().update_hook(hook);
    }

    /// Remove hook installed by `update_hook`.
    pub fn remove_update_hook(&self) {
        self.db.borrow_mut().remove_update_hook();
    }

    /// Remove hook installed by `commit_hook`.
    pub fn remove_commit_hook(&self) {
        self.db.borrow_mut().remove_commit_hook();
    }

    /// Remove hook installed by `rollback_hook`.
    pub fn remove_rollback_hook(&self) {
        self.db.borrow_mut().remove_rollback_hook();
    }
}

impl InnerConnection {
    pub fn remove_hooks(&mut self) {
        self.remove_update_hook();
        self.remove_commit_hook();
        self.remove_rollback_hook();
    }

    fn commit_hook<F>(&self, hook: F)
        where F: FnMut() -> bool
    {
        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
            where F: FnMut() -> bool
        {
            let boxed_hook: *mut F = mem::transmute(p_arg);
            assert!(!boxed_hook.is_null(), "Internal error - null function pointer");

            if (*boxed_hook)() { 1 } else { 0 }
        }

        let previous_hook = {
            let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
            unsafe {
                ffi::sqlite3_commit_hook(self.db(),
                                         Some(call_boxed_closure::<F>),
                                         boxed_hook as *mut _)
            }
        };
        free_boxed_hook(previous_hook);
    }

    fn rollback_hook<F>(&self, hook: F)
        where F: FnMut()
    {
        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
            where F: FnMut()
        {
            let boxed_hook: *mut F = mem::transmute(p_arg);
            assert!(!boxed_hook.is_null(), "Internal error - null function pointer");

            (*boxed_hook)();
        }

        let previous_hook = {
            let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
            unsafe {
                ffi::sqlite3_rollback_hook(self.db(),
                                         Some(call_boxed_closure::<F>),
                                         boxed_hook as *mut _)
            }
        };
        free_boxed_hook(previous_hook);
    }

    fn update_hook<F>(&mut self, hook: F)
        where F: FnMut(Action, &str, &str, i64)
    {
        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void,
                                                   action_code: c_int,
                                                   db_str: *const c_char,
                                                   tbl_str: *const c_char,
                                                   row_id: i64)
            where F: FnMut(Action, &str, &str, i64)
        {
            use std::ffi::CStr;
            use std::str;

            let boxed_hook: *mut F = mem::transmute(p_arg);
            assert!(!boxed_hook.is_null(), "Internal error - null function pointer");

            let action = Action::from(action_code);
            let db_name = {
                let c_slice = CStr::from_ptr(db_str).to_bytes();
                str::from_utf8_unchecked(c_slice)
            };
            let tbl_name = {
                let c_slice = CStr::from_ptr(tbl_str).to_bytes();
                str::from_utf8_unchecked(c_slice)
            };

            (*boxed_hook)(action, db_name, tbl_name, row_id);
        }

        let previous_hook = {
            let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
            unsafe {
                ffi::sqlite3_update_hook(self.db(),
                                         Some(call_boxed_closure::<F>),
                                         boxed_hook as *mut _)
            }
        };
        free_boxed_hook(previous_hook);
    }

    fn remove_update_hook(&mut self) {
        let previous_hook = unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) };
        free_boxed_hook(previous_hook);
    }

    fn remove_commit_hook(&mut self) {
        let previous_hook = unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) };
        free_boxed_hook(previous_hook);
    }

    fn remove_rollback_hook(&mut self) {
        let previous_hook = unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) };
        free_boxed_hook(previous_hook);
    }
}

fn free_boxed_hook(hook: *mut c_void) {
    if !hook.is_null() {
        // TODO make sure that size_of::<*mut F>() is always equal to size_of::<*mut c_void>()
        let _: Box<*mut c_void> = unsafe { Box::from_raw(hook as *mut _) };
    }
}

#[cfg(test)]
mod test {
    use super::Action;
    use Connection;

    #[test]
    fn test_commit_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.commit_hook(|| {
                           called = true;
                           false
                       });
        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;").unwrap();
        assert!(called);
    }

    #[test]
    fn test_rollback_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.rollback_hook(|| {
                           called = true;
                       });
        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;").unwrap();
        assert!(called);
    }

    #[test]
    fn test_update_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.update_hook(|action, db, tbl, row_id| {
                           assert_eq!(Action::SQLITE_INSERT, action);
                           assert_eq!("main", db);
                           assert_eq!("foo", tbl);
                           assert_eq!(1, row_id);
                           called = true;
                       });
        db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
        db.execute_batch("INSERT INTO foo VALUES ('lisa')")
            .unwrap();
        assert!(called);
    }
}
<commit_msg>Rustfmt<commit_after>//! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)]

use std::mem;
use std::ptr;
use std::os::raw::{c_int, c_char, c_void};

use ffi;

use {Connection, InnerConnection};

/// Authorizer Action Codes
#[derive(Debug, PartialEq)]
pub enum Action {
    UNKNOWN = -1,
    SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize,
    SQLITE_CREATE_TABLE = ffi::SQLITE_CREATE_TABLE as isize,
    SQLITE_CREATE_TEMP_INDEX = ffi::SQLITE_CREATE_TEMP_INDEX as isize,
    SQLITE_CREATE_TEMP_TABLE = ffi::SQLITE_CREATE_TEMP_TABLE as isize,
    SQLITE_CREATE_TEMP_TRIGGER = ffi::SQLITE_CREATE_TEMP_TRIGGER as isize,
    SQLITE_CREATE_TEMP_VIEW = ffi::SQLITE_CREATE_TEMP_VIEW as isize,
    SQLITE_CREATE_TRIGGER = ffi::SQLITE_CREATE_TRIGGER as isize,
    SQLITE_CREATE_VIEW = ffi::SQLITE_CREATE_VIEW as isize,
    SQLITE_DELETE = ffi::SQLITE_DELETE as isize,
    SQLITE_DROP_INDEX = ffi::SQLITE_DROP_INDEX as isize,
    SQLITE_DROP_TABLE = ffi::SQLITE_DROP_TABLE as isize,
    SQLITE_DROP_TEMP_INDEX = ffi::SQLITE_DROP_TEMP_INDEX as isize,
    SQLITE_DROP_TEMP_TABLE = ffi::SQLITE_DROP_TEMP_TABLE as isize,
    SQLITE_DROP_TEMP_TRIGGER = ffi::SQLITE_DROP_TEMP_TRIGGER as isize,
    SQLITE_DROP_TEMP_VIEW = ffi::SQLITE_DROP_TEMP_VIEW as isize,
    SQLITE_DROP_TRIGGER = ffi::SQLITE_DROP_TRIGGER as isize,
    SQLITE_DROP_VIEW = ffi::SQLITE_DROP_VIEW as isize,
    SQLITE_INSERT = ffi::SQLITE_INSERT as isize,
    SQLITE_PRAGMA = ffi::SQLITE_PRAGMA as isize,
    SQLITE_READ = ffi::SQLITE_READ as isize,
    SQLITE_SELECT = ffi::SQLITE_SELECT as isize,
    SQLITE_TRANSACTION = ffi::SQLITE_TRANSACTION as isize,
    SQLITE_UPDATE = ffi::SQLITE_UPDATE as isize,
    SQLITE_ATTACH = ffi::SQLITE_ATTACH as isize,
    SQLITE_DETACH = ffi::SQLITE_DETACH as isize,
    SQLITE_ALTER_TABLE = ffi::SQLITE_ALTER_TABLE as isize,
    SQLITE_REINDEX = ffi::SQLITE_REINDEX as isize,
    SQLITE_ANALYZE = ffi::SQLITE_ANALYZE as isize,
    SQLITE_CREATE_VTABLE = ffi::SQLITE_CREATE_VTABLE as isize,
    SQLITE_DROP_VTABLE = ffi::SQLITE_DROP_VTABLE as isize,
    SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize,
    SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize,
    SQLITE_COPY = ffi::SQLITE_COPY as isize,
    SQLITE_RECURSIVE = 33,
}

impl From<i32> for Action {
    fn from(code: i32) -> Action {
        match code {
            ffi::SQLITE_CREATE_INDEX => Action::SQLITE_CREATE_INDEX,
            ffi::SQLITE_CREATE_TABLE => Action::SQLITE_CREATE_TABLE,
            ffi::SQLITE_CREATE_TEMP_INDEX => Action::SQLITE_CREATE_TEMP_INDEX,
            ffi::SQLITE_CREATE_TEMP_TABLE => Action::SQLITE_CREATE_TEMP_TABLE,
            ffi::SQLITE_CREATE_TEMP_TRIGGER => Action::SQLITE_CREATE_TEMP_TRIGGER,
            ffi::SQLITE_CREATE_TEMP_VIEW => Action::SQLITE_CREATE_TEMP_VIEW,
            ffi::SQLITE_CREATE_TRIGGER => Action::SQLITE_CREATE_TRIGGER,
            ffi::SQLITE_CREATE_VIEW => Action::SQLITE_CREATE_VIEW,
            ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
            ffi::SQLITE_DROP_INDEX => Action::SQLITE_DROP_INDEX,
            ffi::SQLITE_DROP_TABLE => Action::SQLITE_DROP_TABLE,
            ffi::SQLITE_DROP_TEMP_INDEX => Action::SQLITE_DROP_TEMP_INDEX,
            ffi::SQLITE_DROP_TEMP_TABLE => Action::SQLITE_DROP_TEMP_TABLE,
            ffi::SQLITE_DROP_TEMP_TRIGGER => Action::SQLITE_DROP_TEMP_TRIGGER,
            ffi::SQLITE_DROP_TEMP_VIEW => Action::SQLITE_DROP_TEMP_VIEW,
            ffi::SQLITE_DROP_TRIGGER => Action::SQLITE_DROP_TRIGGER,
            ffi::SQLITE_DROP_VIEW => Action::SQLITE_DROP_VIEW,
            ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
            ffi::SQLITE_PRAGMA => Action::SQLITE_PRAGMA,
            ffi::SQLITE_READ => Action::SQLITE_READ,
            ffi::SQLITE_SELECT => Action::SQLITE_SELECT,
            ffi::SQLITE_TRANSACTION => Action::SQLITE_TRANSACTION,
            ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
            ffi::SQLITE_ATTACH => Action::SQLITE_ATTACH,
            ffi::SQLITE_DETACH => Action::SQLITE_DETACH,
            ffi::SQLITE_ALTER_TABLE => Action::SQLITE_ALTER_TABLE,
            ffi::SQLITE_REINDEX => Action::SQLITE_REINDEX,
            ffi::SQLITE_ANALYZE => Action::SQLITE_ANALYZE,
            ffi::SQLITE_CREATE_VTABLE => Action::SQLITE_CREATE_VTABLE,
            ffi::SQLITE_DROP_VTABLE => Action::SQLITE_DROP_VTABLE,
            ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION,
            ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT,
            ffi::SQLITE_COPY => Action::SQLITE_COPY,
            33 => Action::SQLITE_RECURSIVE,
            _ => Action::UNKNOWN,
        }
    }
}

impl Connection {
    /// Register a callback function to be invoked whenever a transaction is committed.
    ///
    /// The callback returns `true` to rollback.
    pub fn commit_hook<F>(&self, hook: F)
        where F: FnMut() -> bool
    {
        self.db.borrow_mut().commit_hook(hook);
    }

    /// Register a callback function to be invoked whenever a transaction is committed.
    ///
    /// The callback returns `true` to rollback.
    pub fn rollback_hook<F>(&self, hook: F)
        where F: FnMut()
    {
        self.db.borrow_mut().rollback_hook(hook);
    }

    /// Register a callback function to be invoked whenever a row is updated,
    /// inserted or deleted in a rowid table.
    ///
    /// The callback parameters are:
    ///
    ///   - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE),
    ///   - the name of the database ("main", "temp", ...),
    ///   - the name of the table that is updated,
    ///   - the ROWID of the row that is updated.
    pub fn update_hook<F>(&self, hook: F)
        where F: FnMut(Action, &str, &str, i64)
    {
        self.db.borrow_mut().update_hook(hook);
    }

    /// Remove hook installed by `update_hook`.
    pub fn remove_update_hook(&self) {
        self.db.borrow_mut().remove_update_hook();
    }

    /// Remove hook installed by `commit_hook`.
    pub fn remove_commit_hook(&self) {
        self.db.borrow_mut().remove_commit_hook();
    }

    /// Remove hook installed by `rollback_hook`.
    pub fn remove_rollback_hook(&self) {
        self.db.borrow_mut().remove_rollback_hook();
    }
}

impl InnerConnection {
    pub fn remove_hooks(&mut self) {
        self.remove_update_hook();
        self.remove_commit_hook();
        self.remove_rollback_hook();
    }

    fn commit_hook<F>(&self, hook: F)
        where F: FnMut() -> bool
    {
        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
            where F: FnMut() -> bool
        {
            let boxed_hook: *mut F = mem::transmute(p_arg);
            assert!(!boxed_hook.is_null(),
                    "Internal error - null function pointer");

            if (*boxed_hook)() { 1 } else { 0 }
        }

        let previous_hook = {
            let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
            unsafe {
                ffi::sqlite3_commit_hook(self.db(),
                                         Some(call_boxed_closure::<F>),
                                         boxed_hook as *mut _)
            }
        };
        free_boxed_hook(previous_hook);
    }

    fn rollback_hook<F>(&self, hook: F)
        where F: FnMut()
    {
        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
            where F: FnMut()
        {
            let boxed_hook: *mut F = mem::transmute(p_arg);
            assert!(!boxed_hook.is_null(),
                    "Internal error - null function pointer");

            (*boxed_hook)();
        }

        let previous_hook = {
            let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
            unsafe {
                ffi::sqlite3_rollback_hook(self.db(),
                                           Some(call_boxed_closure::<F>),
                                           boxed_hook as *mut _)
            }
        };
        free_boxed_hook(previous_hook);
    }

    fn update_hook<F>(&mut self, hook: F)
        where F: FnMut(Action, &str, &str, i64)
    {
        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void,
                                                   action_code: c_int,
                                                   db_str: *const c_char,
                                                   tbl_str: *const c_char,
                                                   row_id: i64)
            where F: FnMut(Action, &str, &str, i64)
        {
            use std::ffi::CStr;
            use std::str;

            let boxed_hook: *mut F = mem::transmute(p_arg);
            assert!(!boxed_hook.is_null(),
                    "Internal error - null function pointer");

            let action = Action::from(action_code);
            let db_name = {
                let c_slice = CStr::from_ptr(db_str).to_bytes();
                str::from_utf8_unchecked(c_slice)
            };
            let tbl_name = {
                let c_slice = CStr::from_ptr(tbl_str).to_bytes();
                str::from_utf8_unchecked(c_slice)
            };

            (*boxed_hook)(action, db_name, tbl_name, row_id);
        }

        let previous_hook = {
            let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
            unsafe {
                ffi::sqlite3_update_hook(self.db(),
                                         Some(call_boxed_closure::<F>),
                                         boxed_hook as *mut _)
            }
        };
        free_boxed_hook(previous_hook);
    }

    fn remove_update_hook(&mut self) {
        let previous_hook = unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) };
        free_boxed_hook(previous_hook);
    }

    fn remove_commit_hook(&mut self) {
        let previous_hook = unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) };
        free_boxed_hook(previous_hook);
    }

    fn remove_rollback_hook(&mut self) {
        let previous_hook = unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) };
        free_boxed_hook(previous_hook);
    }
}

fn free_boxed_hook(hook: *mut c_void) {
    if !hook.is_null() {
        // TODO make sure that size_of::<*mut F>() is always equal to size_of::<*mut c_void>()
        let _: Box<*mut c_void> = unsafe { Box::from_raw(hook as *mut _) };
    }
}

#[cfg(test)]
mod test {
    use super::Action;
    use Connection;

    #[test]
    fn test_commit_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.commit_hook(|| {
                           called = true;
                           false
                       });
        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
            .unwrap();
        assert!(called);
    }

    #[test]
    fn test_rollback_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.rollback_hook(|| { called = true; });
        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
            .unwrap();
        assert!(called);
    }

    #[test]
    fn test_update_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.update_hook(|action, db, tbl, row_id| {
                           assert_eq!(Action::SQLITE_INSERT, action);
                           assert_eq!("main", db);
                           assert_eq!("foo", tbl);
                           assert_eq!(1, row_id);
                           called = true;
                       });
        db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
        db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
        assert!(called);
    }
}
<|endoftext|>


<commit_before>pub mod raw {
   use libc::{c_char, c_int, c_uint, c_long, c_void};

   use apr::raw::{apr_pool_t, apr_time_t, apr_array_header_t, apr_off_t, apr_thread_mutex_t,
      apr_int64_t, apr_table_t, apr_bucket_brigade, apr_uri_t, apr_finfo_t, apr_sockaddr_t,
      apr_thread_t, apr_bucket_alloc_t};

   use util_filter::raw::{ap_filter_t};

   pub const OK:        c_int = 0;
   pub const DECLINED:  c_int = -1;
   pub const DONE:      c_int = -2;
   pub const SUSPENDED: c_int = -3;

   #[repr(C)]
   pub struct request_rec {
      pub pool: *mut apr_pool_t,
      pub connection: *mut conn_rec,
      pub server: *mut server_rec,
      pub next: *mut request_rec,
      pub prev: *mut request_rec,
      pub main: *mut request_rec,
      pub the_request: *mut c_char,
      pub assbackwards: c_int,
      pub proxyreq: c_int,
      pub header_only: c_int,
      pub proto_num: c_int,
      pub protocol: *mut c_char,
      pub hostname: *const c_char,
      pub request_time: apr_time_t,
      pub status_line: *const c_char,
      pub status: c_int,
      pub method_number: c_int,
      pub method: *const c_char,
      pub allowed: apr_int64_t,
      pub allowed_xmethods: *mut apr_array_header_t,
      pub allowed_methods: *mut ap_method_list_t,
      pub sent_bodyct: apr_off_t,
      pub bytes_sent: apr_off_t,
      pub mtime: apr_time_t,
      pub range: *const c_char,
      pub clength: apr_off_t,
      pub chunked: c_int,
      pub read_body: c_int,
      pub read_chunked: c_int,
      pub expecting_100: c_uint,
      pub kept_body: *mut apr_bucket_brigade,
      pub body_table: *mut apr_table_t,
      pub remaining: apr_off_t,
      pub read_length: apr_off_t,
      pub headers_in: *mut apr_table_t,
      pub headers_out: *mut apr_table_t,
      pub err_headers_out: *mut apr_table_t,
      pub subprocess_env: *mut apr_table_t,
      pub notes: *mut apr_table_t,
      pub content_type: *const c_char,
      pub handler: *const c_char,
      pub content_encoding: *const c_char,
      pub content_languages: *mut apr_array_header_t,
      pub vlist_validator: *mut c_char,
      pub user: *mut c_char,
      pub ap_auth_type: *mut c_char,
      pub unparsed_uri: *mut c_char,
      pub uri: *mut c_char,
      pub filename: *mut c_char,
      pub canonical_filename: *mut c_char,
      pub path_info: *mut c_char,
      pub args: *mut c_char,
      pub used_path_info: c_int,
      pub eos_sent: c_int,
      pub per_dir_config: *mut ap_conf_vector_t,
      pub request_config: *mut ap_conf_vector_t,
      pub log: *const ap_logconf,
      pub log_id: *const c_char,
      pub htaccess: *const htaccess_result,
      pub output_filters: *mut ap_filter_t,
      pub input_filters: *mut ap_filter_t,
      pub proto_output_filters: *mut ap_filter_t,
      pub proto_input_filters: *mut ap_filter_t,
      pub no_cache: c_int,
      pub no_local_copy: c_int,
      pub invoke_mtx: *mut apr_thread_mutex_t,
      pub parsed_uri: apr_uri_t,
      pub finfo: apr_finfo_t,
      pub useragent_addr: *mut apr_sockaddr_t,
      pub useragent_ip: *mut c_char,
      pub trailers_in: *mut apr_table_t,
      pub trailers_out: *mut apr_table_t,
   }

   #[repr(C)]
   pub struct conn_rec {
       pub pool: *mut apr_pool_t,
       pub base_server: *mut server_rec,
       pub vhost_lookup_data: *mut c_void,
       pub local_addr: *mut apr_sockaddr_t,
       pub client_addr: *mut apr_sockaddr_t,
       pub client_ip: *mut c_char,
       pub remote_host: *mut c_char,
       pub remote_logname: *mut c_char,
       pub local_ip: *mut c_char,
       pub local_host: *mut c_char,
       pub id: c_long,
       pub conn_config: *mut ap_conf_vector_t,
       pub notes: *mut apr_table_t,
       pub input_filters: *mut ap_filter_t,
       pub output_filters: *mut ap_filter_t,
       pub sbh: *mut c_void,
       pub bucket_alloc: *mut apr_bucket_alloc_t,
       pub cs: *mut conn_state_t,
       pub data_in_input_filters: c_int,
       pub data_in_output_filters: c_int,
       pub _bindgen_bitfield_1_: c_uint,
       pub _bindgen_bitfield_2_: c_int,
       pub aborted: c_uint,
       pub keepalive: ap_conn_keepalive_e,
       pub keepalives: c_int,
       pub log: *const ap_logconf,
       pub log_id: *const c_char,
       pub current_thread: *mut apr_thread_t,
   }

   #[repr(C)]
   pub struct ap_logconf {
       pub module_levels: *mut c_char,
       pub level: c_int,
   }

   #[repr(C)]
   pub struct ap_conn_keepalive_e;

   #[repr(C)]
   pub struct ap_conf_vector_t;

   #[repr(C)]
   pub struct ap_method_list_t;

   #[repr(C)]
   pub struct conn_state_t;

   #[repr(C)]
   pub struct htaccess_result;

   #[repr(C)]
   pub struct process_rec;

   #[repr(C)]
   pub struct server_rec;

   extern "C" {
      pub fn ap_get_server_description() -> *const c_char;
   }
}

use libc::{c_void, c_int};

use std::ffi::CString;

use wrapper::{Wrapper, c_str_value, wrap_ptr};

use apr::AprTable;

pub enum Status {
   // non-HTTP status codes returned by hooks
   OK,            // Module has handled this stage.
   DECLINED,      // Module declines to handle
   DONE,          // Module has served the response completely
                  // - it's safe to die() with no more output
   SUSPENDED,     // Module will handle the remainder of the request.
                  // The core will never invoke the request again,
}

impl Into<c_int> for Status {
   fn into(self) -> c_int {
      match self {
         Status::OK => raw::OK,
         Status::DECLINED => raw::DECLINED,
         Status::DONE => raw::DONE,
         Status::SUSPENDED => raw::SUSPENDED,
      }
   }
}

pub type Request<'a> = Wrapper<'a, raw::request_rec>;

impl<'a> Request<'a> {
   pub fn connection(&self) -> Option<Conn> {
      wrap_ptr(self.raw.connection)
   }

   pub fn the_request(&self) -> Option<&'a str> {
      c_str_value(self.raw.the_request)
   }

   pub fn protocol(&self) -> Option<&'a str> {
      c_str_value(self.raw.protocol)
   }

   pub fn hostname(&self) -> Option<&'a str> {
      c_str_value(self.raw.hostname)
   }

   pub fn status_line(&self) -> Option<&'a str> {
      c_str_value(self.raw.status_line)
   }

   pub fn method(&self) -> Option<&'a str> {
      c_str_value(self.raw.method)
   }

   pub fn range(&self) -> Option<&'a str> {
      c_str_value(self.raw.range)
   }

   pub fn headers_in(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.headers_in)
   }

   pub fn headers_out(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.headers_out)
   }

   pub fn err_headers_out(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.err_headers_out)
   }

   pub fn subprocess_env(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.subprocess_env)
   }

   pub fn notes(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.notes)
   }

   pub fn content_type(&self) -> Option<&'a str> {
      c_str_value(self.raw.content_type)
   }

   pub fn set_content_type<T: Into<Vec<u8>>>(&self, ct: T) {
      let s = CString::new(ct).unwrap();

      unsafe {
         ::http_protocol::raw::ap_set_content_type(
            self.raw,
            s.as_ptr()
         );
      }
   }

   pub fn handler(&self) -> Option<&'a str> {
      c_str_value(self.raw.handler)
   }

   pub fn content_encoding(&self) -> Option<&'a str> {
      c_str_value(self.raw.content_encoding)
   }

   pub fn vlist_validator(&self) -> Option<&'a str> {
      c_str_value(self.raw.vlist_validator)
   }

   pub fn user(&self) -> Option<&'a str> {
      c_str_value(self.raw.user)
   }

   pub fn ap_auth_type(&self) -> Option<&'a str> {
      c_str_value(self.raw.ap_auth_type)
   }

   pub fn unparsed_uri(&self) -> Option<&'a str> {
      c_str_value(self.raw.unparsed_uri)
   }

   pub fn uri(&self) -> Option<&'a str> {
      c_str_value(self.raw.uri)
   }

   pub fn filename(&self) -> Option<&'a str> {
      c_str_value(self.raw.filename)
   }

   pub fn canonical_filename(&self) -> Option<&'a str> {
      c_str_value(self.raw.canonical_filename)
   }

   pub fn path_info(&self) -> Option<&'a str> {
      c_str_value(self.raw.path_info)
   }

   pub fn args(&self) -> Option<&'a str> {
      c_str_value(self.raw.args)
   }

   pub fn log_id(&self) -> Option<&'a str> {
      c_str_value(self.raw.log_id)
   }

   pub fn useragent_ip(&self) -> Option<&'a str> {
      c_str_value(self.raw.useragent_ip)
   }

   pub fn write<T: Into<Vec<u8>>>(&self, data: T) {
      let s = CString::new(data).unwrap();

      unsafe {
         ::http_protocol::raw::ap_rwrite(
            s.as_ptr() as *mut c_void,
            s.to_bytes().len() as i32,
            self.raw
         );
      }
   }
}

pub type Conn<'a> = Wrapper<'a, raw::conn_rec>;

impl<'a> Conn<'a> {
   pub fn client_ip(&self) -> Option<&'a str> {
      c_str_value(self.raw.client_ip)
   }

   pub fn remote_host(&self) -> Option<&'a str> {
      c_str_value(self.raw.remote_host)
   }

   pub fn remote_logname(&self) -> Option<&'a str> {
      c_str_value(self.raw.remote_logname)
   }

   pub fn local_ip(&self) -> Option<&'a str> {
      c_str_value(self.raw.local_ip)
   }

   pub fn local_host(&self) -> Option<&'a str> {
      c_str_value(self.raw.local_host)
   }

   pub fn log_id(&self) -> Option<&'a str> {
      c_str_value(self.raw.log_id)
   }
}

pub fn get_server_description<'a>() -> Option<&'a str> {
   c_str_value(
      unsafe { raw::ap_get_server_description() }
   )
}
<commit_msg>escape_html wrapper.<commit_after>pub mod raw {
   use libc::{c_char, c_int, c_uint, c_long, c_void};

   use apr::raw::{apr_pool_t, apr_time_t, apr_array_header_t, apr_off_t, apr_thread_mutex_t,
      apr_int64_t, apr_table_t, apr_bucket_brigade, apr_uri_t, apr_finfo_t, apr_sockaddr_t,
      apr_thread_t, apr_bucket_alloc_t};

   use util_filter::raw::{ap_filter_t};

   pub const OK:        c_int = 0;
   pub const DECLINED:  c_int = -1;
   pub const DONE:      c_int = -2;
   pub const SUSPENDED: c_int = -3;

   #[repr(C)]
   pub struct request_rec {
      pub pool: *mut apr_pool_t,
      pub connection: *mut conn_rec,
      pub server: *mut server_rec,
      pub next: *mut request_rec,
      pub prev: *mut request_rec,
      pub main: *mut request_rec,
      pub the_request: *mut c_char,
      pub assbackwards: c_int,
      pub proxyreq: c_int,
      pub header_only: c_int,
      pub proto_num: c_int,
      pub protocol: *mut c_char,
      pub hostname: *const c_char,
      pub request_time: apr_time_t,
      pub status_line: *const c_char,
      pub status: c_int,
      pub method_number: c_int,
      pub method: *const c_char,
      pub allowed: apr_int64_t,
      pub allowed_xmethods: *mut apr_array_header_t,
      pub allowed_methods: *mut ap_method_list_t,
      pub sent_bodyct: apr_off_t,
      pub bytes_sent: apr_off_t,
      pub mtime: apr_time_t,
      pub range: *const c_char,
      pub clength: apr_off_t,
      pub chunked: c_int,
      pub read_body: c_int,
      pub read_chunked: c_int,
      pub expecting_100: c_uint,
      pub kept_body: *mut apr_bucket_brigade,
      pub body_table: *mut apr_table_t,
      pub remaining: apr_off_t,
      pub read_length: apr_off_t,
      pub headers_in: *mut apr_table_t,
      pub headers_out: *mut apr_table_t,
      pub err_headers_out: *mut apr_table_t,
      pub subprocess_env: *mut apr_table_t,
      pub notes: *mut apr_table_t,
      pub content_type: *const c_char,
      pub handler: *const c_char,
      pub content_encoding: *const c_char,
      pub content_languages: *mut apr_array_header_t,
      pub vlist_validator: *mut c_char,
      pub user: *mut c_char,
      pub ap_auth_type: *mut c_char,
      pub unparsed_uri: *mut c_char,
      pub uri: *mut c_char,
      pub filename: *mut c_char,
      pub canonical_filename: *mut c_char,
      pub path_info: *mut c_char,
      pub args: *mut c_char,
      pub used_path_info: c_int,
      pub eos_sent: c_int,
      pub per_dir_config: *mut ap_conf_vector_t,
      pub request_config: *mut ap_conf_vector_t,
      pub log: *const ap_logconf,
      pub log_id: *const c_char,
      pub htaccess: *const htaccess_result,
      pub output_filters: *mut ap_filter_t,
      pub input_filters: *mut ap_filter_t,
      pub proto_output_filters: *mut ap_filter_t,
      pub proto_input_filters: *mut ap_filter_t,
      pub no_cache: c_int,
      pub no_local_copy: c_int,
      pub invoke_mtx: *mut apr_thread_mutex_t,
      pub parsed_uri: apr_uri_t,
      pub finfo: apr_finfo_t,
      pub useragent_addr: *mut apr_sockaddr_t,
      pub useragent_ip: *mut c_char,
      pub trailers_in: *mut apr_table_t,
      pub trailers_out: *mut apr_table_t,
   }

   #[repr(C)]
   pub struct conn_rec {
       pub pool: *mut apr_pool_t,
       pub base_server: *mut server_rec,
       pub vhost_lookup_data: *mut c_void,
       pub local_addr: *mut apr_sockaddr_t,
       pub client_addr: *mut apr_sockaddr_t,
       pub client_ip: *mut c_char,
       pub remote_host: *mut c_char,
       pub remote_logname: *mut c_char,
       pub local_ip: *mut c_char,
       pub local_host: *mut c_char,
       pub id: c_long,
       pub conn_config: *mut ap_conf_vector_t,
       pub notes: *mut apr_table_t,
       pub input_filters: *mut ap_filter_t,
       pub output_filters: *mut ap_filter_t,
       pub sbh: *mut c_void,
       pub bucket_alloc: *mut apr_bucket_alloc_t,
       pub cs: *mut conn_state_t,
       pub data_in_input_filters: c_int,
       pub data_in_output_filters: c_int,
       pub _bindgen_bitfield_1_: c_uint,
       pub _bindgen_bitfield_2_: c_int,
       pub aborted: c_uint,
       pub keepalive: ap_conn_keepalive_e,
       pub keepalives: c_int,
       pub log: *const ap_logconf,
       pub log_id: *const c_char,
       pub current_thread: *mut apr_thread_t,
   }

   #[repr(C)]
   pub struct ap_logconf {
       pub module_levels: *mut c_char,
       pub level: c_int,
   }

   #[repr(C)]
   pub struct ap_conn_keepalive_e;

   #[repr(C)]
   pub struct ap_conf_vector_t;

   #[repr(C)]
   pub struct ap_method_list_t;

   #[repr(C)]
   pub struct conn_state_t;

   #[repr(C)]
   pub struct htaccess_result;

   #[repr(C)]
   pub struct process_rec;

   #[repr(C)]
   pub struct server_rec;

   extern "C" {
      pub fn ap_get_server_description() -> *const c_char;

      pub fn ap_escape_html2(p: *mut apr_pool_t, s: *const c_char, toasc: c_int) -> *mut c_char;
   }
}

use libc::{c_void, c_int};

use std::ffi::CString;

use wrapper::{Wrapper, c_str_value, wrap_ptr};

use apr::AprTable;

pub enum Status {
   // non-HTTP status codes returned by hooks
   OK,            // Module has handled this stage.
   DECLINED,      // Module declines to handle
   DONE,          // Module has served the response completely
                  // - it's safe to die() with no more output
   SUSPENDED,     // Module will handle the remainder of the request.
                  // The core will never invoke the request again,
}

impl Into<c_int> for Status {
   fn into(self) -> c_int {
      match self {
         Status::OK => raw::OK,
         Status::DECLINED => raw::DECLINED,
         Status::DONE => raw::DONE,
         Status::SUSPENDED => raw::SUSPENDED,
      }
   }
}

pub type Request<'a> = Wrapper<'a, raw::request_rec>;

impl<'a> Request<'a> {
   pub fn connection(&self) -> Option<Conn> {
      wrap_ptr(self.raw.connection)
   }

   pub fn the_request(&self) -> Option<&'a str> {
      c_str_value(self.raw.the_request)
   }

   pub fn protocol(&self) -> Option<&'a str> {
      c_str_value(self.raw.protocol)
   }

   pub fn hostname(&self) -> Option<&'a str> {
      c_str_value(self.raw.hostname)
   }

   pub fn status_line(&self) -> Option<&'a str> {
      c_str_value(self.raw.status_line)
   }

   pub fn method(&self) -> Option<&'a str> {
      c_str_value(self.raw.method)
   }

   pub fn range(&self) -> Option<&'a str> {
      c_str_value(self.raw.range)
   }

   pub fn headers_in(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.headers_in)
   }

   pub fn headers_out(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.headers_out)
   }

   pub fn err_headers_out(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.err_headers_out)
   }

   pub fn subprocess_env(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.subprocess_env)
   }

   pub fn notes(&self) -> Option<AprTable> {
      wrap_ptr(self.raw.notes)
   }

   pub fn content_type(&self) -> Option<&'a str> {
      c_str_value(self.raw.content_type)
   }

   pub fn set_content_type<T: Into<Vec<u8>>>(&self, ct: T) {
      let cstr = CString::new(ct).unwrap();

      unsafe {
         ::http_protocol::raw::ap_set_content_type(
            self.raw,
            cstr.as_ptr()
         );
      }
   }

   pub fn handler(&self) -> Option<&'a str> {
      c_str_value(self.raw.handler)
   }

   pub fn content_encoding(&self) -> Option<&'a str> {
      c_str_value(self.raw.content_encoding)
   }

   pub fn vlist_validator(&self) -> Option<&'a str> {
      c_str_value(self.raw.vlist_validator)
   }

   pub fn user(&self) -> Option<&'a str> {
      c_str_value(self.raw.user)
   }

   pub fn ap_auth_type(&self) -> Option<&'a str> {
      c_str_value(self.raw.ap_auth_type)
   }

   pub fn unparsed_uri(&self) -> Option<&'a str> {
      c_str_value(self.raw.unparsed_uri)
   }

   pub fn uri(&self) -> Option<&'a str> {
      c_str_value(self.raw.uri)
   }

   pub fn filename(&self) -> Option<&'a str> {
      c_str_value(self.raw.filename)
   }

   pub fn canonical_filename(&self) -> Option<&'a str> {
      c_str_value(self.raw.canonical_filename)
   }

   pub fn path_info(&self) -> Option<&'a str> {
      c_str_value(self.raw.path_info)
   }

   pub fn args(&self) -> Option<&'a str> {
      c_str_value(self.raw.args)
   }

   pub fn log_id(&self) -> Option<&'a str> {
      c_str_value(self.raw.log_id)
   }

   pub fn useragent_ip(&self) -> Option<&'a str> {
      c_str_value(self.raw.useragent_ip)
   }

   pub fn write<T: Into<Vec<u8>>>(&self, data: T) {
      let cstr = CString::new(data).unwrap();

      unsafe {
         ::http_protocol::raw::ap_rwrite(
            cstr.as_ptr() as *mut c_void,
            cstr.to_bytes().len() as i32,
            self.raw
         );
      }
   }

   pub fn escape_html<T: Into<Vec<u8>>>(&self, s: T) -> Option<&'a str> {
      let cstr = CString::new(s).unwrap();

      let escaped = unsafe {
         raw::ap_escape_html2(
            self.raw.pool,
            cstr.as_ptr(),
            0
         )
      };

      c_str_value(escaped)
   }
}

pub type Conn<'a> = Wrapper<'a, raw::conn_rec>;

impl<'a> Conn<'a> {
   pub fn client_ip(&self) -> Option<&'a str> {
      c_str_value(self.raw.client_ip)
   }

   pub fn remote_host(&self) -> Option<&'a str> {
      c_str_value(self.raw.remote_host)
   }

   pub fn remote_logname(&self) -> Option<&'a str> {
      c_str_value(self.raw.remote_logname)
   }

   pub fn local_ip(&self) -> Option<&'a str> {
      c_str_value(self.raw.local_ip)
   }

   pub fn local_host(&self) -> Option<&'a str> {
      c_str_value(self.raw.local_host)
   }

   pub fn log_id(&self) -> Option<&'a str> {
      c_str_value(self.raw.log_id)
   }
}

pub fn get_server_description<'a>() -> Option<&'a str> {
   c_str_value(
      unsafe { raw::ap_get_server_description() }
   )
}
<|endoftext|>


<commit_before>use crate::{buffer, Document};

#[cfg(feature = "import")]
#[cfg_attr(docsrs, doc(cfg(feature = "import")))]
use image_crate::DynamicImage;

/// Format of image pixel data.
#[cfg(feature = "import")]
#[cfg_attr(docsrs, doc(cfg(feature = "import")))]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Format {
    /// Red only.
    R8,

    /// Red, green.
    R8G8,

    /// Red, green, blue.
    R8G8B8,

    /// Red, green, blue, alpha.
    R8G8B8A8,

    /// Red only (16 bits).
    R16,

    /// Red, green (16 bits).
    R16G16,

    /// Red, green, blue (16 bits).
    R16G16B16,

    /// Red, green, blue, alpha (16 bits).
    R16G16B16A16,

    /// Red, green, blue (32 bits float)
    R32G32B32FLOAT,

    /// Red, green, blue, alpha (32 bits float)
    R32G32B32A32FLOAT,
}

/// Describes an image data source.
#[derive(Clone, Debug)]
pub enum Source<'a> {
    /// Image data is contained in a buffer view.
    View {
        /// The buffer view containing the encoded image data.
        view: buffer::View<'a>,

        /// The image data MIME type.
        mime_type: &'a str,
    },

    /// Image data is contained in an external data source.
    Uri {
        /// The URI of the external data source.
        uri: &'a str,

        /// The image data MIME type, if provided.
        mime_type: Option<&'a str>,
    },
}

/// Image data used to create a texture.
#[derive(Clone, Debug)]
pub struct Image<'a> {
    /// The parent `Document` struct.
    document: &'a Document,

    /// The corresponding JSON index.
    index: usize,

    /// The corresponding JSON struct.
    json: &'a json::image::Image,
}

/// Image data belonging to an imported glTF asset.
#[cfg(feature = "import")]
#[cfg_attr(docsrs, doc(cfg(feature = "import")))]
#[derive(Clone, Debug)]
pub struct Data {
    /// The image pixel data (8 bits per channel).
    pub pixels: Vec<u8>,

    /// The image pixel data format.
    pub format: Format,

    /// The image height in pixels.
    pub width: u32,

    /// The image width in pixels.
    pub height: u32,
}

impl<'a> Image<'a> {
    /// Constructs an `Image` from owned data.
    pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::image::Image) -> Self {
        Self {
            document,
            index,
            json,
        }
    }

    /// Returns the internal JSON index.
    pub fn index(&self) -> usize {
        self.index
    }

    /// Optional user-defined name for this object.
    #[cfg(feature = "names")]
    #[cfg_attr(docsrs, doc(cfg(feature = "names")))]
    pub fn name(&self) -> Option<&'a str> {
        self.json.name.as_deref()
    }

    /// Returns the image data source.
    pub fn source(&self) -> Source<'a> {
        if let Some(index) = self.json.buffer_view.as_ref() {
            let view = self.document.views().nth(index.value()).unwrap();
            let mime_type = self.json.mime_type.as_ref().map(|x| x.0.as_str()).unwrap();
            Source::View { view, mime_type }
        } else {
            let uri = self.json.uri.as_ref().unwrap();
            let mime_type = self.json.mime_type.as_ref().map(|x| x.0.as_str());
            Source::Uri { uri, mime_type }
        }
    }

    /// Optional application specific data.
    pub fn extras(&self) -> &'a json::Extras {
        &self.json.extras
    }
}

#[cfg(feature = "import")]
impl Data {
    /// Note: We don't implement `From<DynamicImage>` since we don't want
    /// to expose such functionality to the user.
    pub(crate) fn new(image: DynamicImage) -> Self {
        use image_crate::GenericImageView;
        let format = match image {
            DynamicImage::ImageLuma8(_) => Format::R8,
            DynamicImage::ImageLumaA8(_) => Format::R8G8,
            DynamicImage::ImageRgb8(_) => Format::R8G8B8,
            DynamicImage::ImageRgba8(_) => Format::R8G8B8A8,
            DynamicImage::ImageLuma16(_) => Format::R16,
            DynamicImage::ImageLumaA16(_) => Format::R16G16,
            DynamicImage::ImageRgb16(_) => Format::R16G16B16,
            DynamicImage::ImageRgba16(_) => Format::R16G16B16A16,
            DynamicImage::ImageRgb32F(_) => Format::R32G32B32FLOAT,
            DynamicImage::ImageRgba32F(_) => Format::R32G32B32A32FLOAT,
            _ => unreachable!("Unmatched DynamicImage format")
        };
        let (width, height) = image.dimensions();
        let pixels = image.into_bytes();
        Data {
            format,
            width,
            height,
            pixels,
        }
    }
}
<commit_msg>Update formatting<commit_after>use crate::{buffer, Document};

#[cfg(feature = "import")]
#[cfg_attr(docsrs, doc(cfg(feature = "import")))]
use image_crate::DynamicImage;

/// Format of image pixel data.
#[cfg(feature = "import")]
#[cfg_attr(docsrs, doc(cfg(feature = "import")))]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Format {
    /// Red only.
    R8,

    /// Red, green.
    R8G8,

    /// Red, green, blue.
    R8G8B8,

    /// Red, green, blue, alpha.
    R8G8B8A8,

    /// Red only (16 bits).
    R16,

    /// Red, green (16 bits).
    R16G16,

    /// Red, green, blue (16 bits).
    R16G16B16,

    /// Red, green, blue, alpha (16 bits).
    R16G16B16A16,

    /// Red, green, blue (32 bits float)
    R32G32B32FLOAT,

    /// Red, green, blue, alpha (32 bits float)
    R32G32B32A32FLOAT,
}

/// Describes an image data source.
#[derive(Clone, Debug)]
pub enum Source<'a> {
    /// Image data is contained in a buffer view.
    View {
        /// The buffer view containing the encoded image data.
        view: buffer::View<'a>,

        /// The image data MIME type.
        mime_type: &'a str,
    },

    /// Image data is contained in an external data source.
    Uri {
        /// The URI of the external data source.
        uri: &'a str,

        /// The image data MIME type, if provided.
        mime_type: Option<&'a str>,
    },
}

/// Image data used to create a texture.
#[derive(Clone, Debug)]
pub struct Image<'a> {
    /// The parent `Document` struct.
    document: &'a Document,

    /// The corresponding JSON index.
    index: usize,

    /// The corresponding JSON struct.
    json: &'a json::image::Image,
}

/// Image data belonging to an imported glTF asset.
#[cfg(feature = "import")]
#[cfg_attr(docsrs, doc(cfg(feature = "import")))]
#[derive(Clone, Debug)]
pub struct Data {
    /// The image pixel data (8 bits per channel).
    pub pixels: Vec<u8>,

    /// The image pixel data format.
    pub format: Format,

    /// The image height in pixels.
    pub width: u32,

    /// The image width in pixels.
    pub height: u32,
}

impl<'a> Image<'a> {
    /// Constructs an `Image` from owned data.
    pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::image::Image) -> Self {
        Self {
            document,
            index,
            json,
        }
    }

    /// Returns the internal JSON index.
    pub fn index(&self) -> usize {
        self.index
    }

    /// Optional user-defined name for this object.
    #[cfg(feature = "names")]
    #[cfg_attr(docsrs, doc(cfg(feature = "names")))]
    pub fn name(&self) -> Option<&'a str> {
        self.json.name.as_deref()
    }

    /// Returns the image data source.
    pub fn source(&self) -> Source<'a> {
        if let Some(index) = self.json.buffer_view.as_ref() {
            let view = self.document.views().nth(index.value()).unwrap();
            let mime_type = self.json.mime_type.as_ref().map(|x| x.0.as_str()).unwrap();
            Source::View { view, mime_type }
        } else {
            let uri = self.json.uri.as_ref().unwrap();
            let mime_type = self.json.mime_type.as_ref().map(|x| x.0.as_str());
            Source::Uri { uri, mime_type }
        }
    }

    /// Optional application specific data.
    pub fn extras(&self) -> &'a json::Extras {
        &self.json.extras
    }
}

#[cfg(feature = "import")]
impl Data {
    /// Note: We don't implement `From<DynamicImage>` since we don't want
    /// to expose such functionality to the user.
    pub(crate) fn new(image: DynamicImage) -> Self {
        use image_crate::GenericImageView;
        let format = match image {
            DynamicImage::ImageLuma8(_) => Format::R8,
            DynamicImage::ImageLumaA8(_) => Format::R8G8,
            DynamicImage::ImageRgb8(_) => Format::R8G8B8,
            DynamicImage::ImageRgba8(_) => Format::R8G8B8A8,
            DynamicImage::ImageLuma16(_) => Format::R16,
            DynamicImage::ImageLumaA16(_) => Format::R16G16,
            DynamicImage::ImageRgb16(_) => Format::R16G16B16,
            DynamicImage::ImageRgba16(_) => Format::R16G16B16A16,
            DynamicImage::ImageRgb32F(_) => Format::R32G32B32FLOAT,
            DynamicImage::ImageRgba32F(_) => Format::R32G32B32A32FLOAT,
            _ => unreachable!("Unmatched DynamicImage format"),
        };
        let (width, height) = image.dimensions();
        let pixels = image.into_bytes();
        Data {
            format,
            width,
            height,
            pixels,
        }
    }
}
<|endoftext|>


<commit_before>//! An abstract input state object that gets fed user
//! events and updates itself based on a set of key
//! bindings.
//! The idea is threefold:
//!
//! * Have a layer of abstract key bindings rather than
//! looking at concrete event types
//! * Use this to be able to abstract away differences
//! between keyboards, joysticks and game controllers
//! (rather based on Unity3D),
//! * Do some tweening of input axes and stuff just for
//! fun maybe.
//!
//! Right now ggez doesn't handle joysticks or controllers
//! anyway, so.

use std::cmp::PartialOrd;
use std::hash::Hash;
use std::collections::BTreeMap;
use std::collections::HashMap;
use ggez::event::*;

// Okay, but how does it actually work?
// Basically we have to bind input events to buttons and axes.
// Input events can be keys, mouse buttons/motion, or eventually
// joystick/controller inputs.  Mouse delta can be mapped to axes too.
//
// https://docs.unity3d.com/Manual/ConventionalGameInput.html has useful
// descriptions of the exact behavior of axes.
//
// So to think about this more clearly, here are the default bindings:
//
// W, ↑: +Y axis
// A, ←: -X axis
// S, ↓: -Y axis
// D, →: +X axis
// Enter, z, LMB: Button 1
// Shift, x, MMB: Button 2
// Ctrl,  c, RMB: Button 3
//
// Easy way?  Hash map of event -> axis/button bindings.

#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
enum InputEvent {
    KeyEvent(Keycode),
    MouseButtonEvent,
}

#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Copy, Clone)]
enum InputEffect<Axes, Buttons>
    where Axes: Eq + Ord + Clone,
          Buttons: Eq + Ord + Clone
{
    Axis(Axes, bool),
    Button(Buttons),
}

#[derive(Debug)]
struct AxisStatus {
    position: f64,
    current_direction: f64,
}

impl Default for AxisStatus {
    fn default() -> Self {
        AxisStatus {
            position: 0.0,
            current_direction: 0.0,
        }
    }
}

#[derive(Debug)]
pub struct InputManager<Axes, Buttons>
    where Axes: Eq + Ord + Hash + Clone,
          Buttons: Eq + Ord + Hash + Clone
{
    // Once EnumSet is stable it should be used for these
    // instead of BTreeMap. ♥?
    // Binding of keys to input values.
    bindings: HashMap<InputEvent, InputEffect<Axes, Buttons>>,
    // Input state for axes
    axes: BTreeMap<Axes, AxisStatus>,
    // Input states for buttons
    buttons: BTreeMap<Buttons, bool>,
}

impl<Axes, Buttons> InputManager<Axes, Buttons>
    where Axes: Eq + Ord + Hash + Clone,
          Buttons: Eq + Ord + Hash + Clone
{
    pub fn new() -> Self {
        InputManager {
            bindings: HashMap::new(),
            axes: BTreeMap::new(),
            buttons: BTreeMap::new(),
        }
    }

    pub fn bind_key_to_axis(mut self, keycode: Keycode, axis: Axes, positive: bool) -> Self {

        self.bindings.insert(InputEvent::KeyEvent(keycode),
                             InputEffect::Axis(axis, positive));
        self
    }

    pub fn bind_key_to_button(mut self, keycode: Keycode, button: Buttons) -> Self {
        self.bindings.insert(InputEvent::KeyEvent(keycode), InputEffect::Button(button));
        self
    }

    pub fn update_keydown(&mut self, keycode: Option<Keycode>) {
        if let Some(keycode) = keycode {
            let effect = {
                if let Some(e) = self.bindings.get(&InputEvent::KeyEvent(keycode)) {
                    e.clone()
                } else {
                    return;
                }
            };
            self.update_effect(effect, true);
        }
    }

    pub fn update_keyup(&mut self, keycode: Option<Keycode>) {
        if let Some(keycode) = keycode {
            let effect = {
                if let Some(e) = self.bindings.get(&InputEvent::KeyEvent(keycode)) {
                    e.clone()
                } else {
                    return;
                }
            };
            self.update_effect(effect, false);
        }
    }

    fn update_effect(&mut self, effect: InputEffect<Axes, Buttons>, started: bool) {
        match effect {
            InputEffect::Axis(axis, direction) => {
                let direction_float = if direction {
                    1.0
                } else {
                    -1.0
                };
                let default_status = AxisStatus {
                    position: 0.0,
                    current_direction: direction_float,
                };
                let axis_status = self.axes.entry(axis).or_insert(default_status);
                axis_status.current_direction = direction_float;
            }
            InputEffect::Button(button) => {
                let button_pressed = self.buttons.entry(button).or_insert(started);
                *button_pressed = started;

            }
        }
    }

    pub fn get_axis(&self, axis: &Axes) -> f64 {
        if let Some(ax) = self.axes.get(axis) {
            ax.position
        } else {
            0.0
        }
    }

    pub fn get_axis_raw() {}

    pub fn get_button(&self, axis: &Buttons) -> bool {
        if let Some(pressed) = self.buttons.get(axis) {
            *pressed
        } else {
            false
        }
    }

    pub fn mouse_position() {}

    pub fn mouse_scroll_delta() {}

    pub fn get_button_down() {}

    pub fn get_button_up() {}

    pub fn get_mouse_button() {}

    pub fn get_mouse_button_down() {}

    pub fn get_mouse_button_up() {}

    pub fn reset_input_axes() {}
}

mod tests {

    #[ignore(unused_imports)]
    use ggez::event::*;
    #[ignore(unused_imports)]
    use super::*;

    #[ignore(dead_code)]
    #[derive(Hash, Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
    enum Buttons {
        Up,
        Left,
        Right,
        Down,
    }
    #[test]
    fn test_input_events() {
        let mut im = InputManager::<(), Buttons>::new().bind_key_to_button(Keycode::W, Buttons::Up);

        im.update_keydown(Some(Keycode::W));
        assert!(im.get_button(&Buttons::Up));
        im.update_keyup(Some(Keycode::W));
        assert!(!im.get_button(&Buttons::Up));
    }
}
<commit_msg>Input subsystem is kinda done-ish.<commit_after>//! An abstract input state object that gets fed user
//! events and updates itself based on a set of key
//! bindings.
//! The idea is threefold:
//!
//! * Have a layer of abstract key bindings rather than
//! looking at concrete event types
//! * Use this to be able to abstract away differences
//! between keyboards, joysticks and game controllers
//! (rather based on Unity3D),
//! * Do some tweening of input axes and stuff just for
//! fun maybe.
//!
//! Right now ggez doesn't handle joysticks or controllers
//! anyway, so.

use std::hash::Hash;
use std::collections::HashMap;
use ggez::event::*;

// Okay, but how does it actually work?
// Basically we have to bind input events to buttons and axes.
// Input events can be keys, mouse buttons/motion, or eventually
// joystick/controller inputs.  Mouse delta can be mapped to axes too.
//
// https://docs.unity3d.com/Manual/ConventionalGameInput.html has useful
// descriptions of the exact behavior of axes.
//
// So to think about this more clearly, here are the default bindings:
//
// W, ↑: +Y axis
// A, ←: -X axis
// S, ↓: -Y axis
// D, →: +X axis
// Enter, z, LMB: Button 1
// Shift, x, MMB: Button 2
// Ctrl,  c, RMB: Button 3
//
// Easy way?  Hash map of event -> axis/button bindings.

#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
enum InputEvent {
    KeyEvent(Keycode),
    MouseButtonEvent,
}

#[derive(Debug, Copy, Clone)]
enum InputEffect<Axes, Buttons>
    where Axes: Eq + Hash + Clone,
          Buttons: Eq + Hash + Clone
{
    Axis(Axes, bool),
    Button(Buttons),
}

#[derive(Debug)]
struct AxisStatus {
    // Where the axis currently is, in [-1, 1]
    position: f64,
    // Where the axis is moving towards.  Possible
    // values are -1, 0, +1
    // (or a continuous range for analog devices I guess)
    direction: f64,
    // Speed in units per second that the axis
    // moves towards the target value.
    acceleration: f64,
    // Speed in units per second that the axis will
    // fall back toward 0 if the input stops.
    gravity: f64,
}

impl Default for AxisStatus {
    fn default() -> Self {
        AxisStatus {
            position: 0.0,
            direction: 0.0,
            acceleration: 2.0,
            gravity: 2.0,
        }
    }
}

#[derive(Debug)]
pub struct InputManager<Axes, Buttons>
    where Axes: Hash + Eq + Clone,
          Buttons: Hash + Eq + Clone
{
    // Once EnumSet is stable it should be used for these
    // instead of BTreeMap. ♥?
    // Binding of keys to input values.
    bindings: HashMap<InputEvent, InputEffect<Axes, Buttons>>,
    // Input state for axes
    axes: HashMap<Axes, AxisStatus>,
    // Input states for buttons
    buttons: HashMap<Buttons, bool>,
}

impl<Axes, Buttons> InputManager<Axes, Buttons>
    where Axes: Eq + Hash + Clone,
          Buttons: Eq + Hash + Clone
{
    pub fn new() -> Self {
        InputManager {
            bindings: HashMap::new(),
            axes: HashMap::new(),
            buttons: HashMap::new(),
        }
    }

    /// Adds a key binding connecting the given keycode to the given
    /// logical axis.
    pub fn bind_key_to_axis(mut self, keycode: Keycode, axis: Axes, positive: bool) -> Self {

        self.bindings.insert(InputEvent::KeyEvent(keycode),
                             InputEffect::Axis(axis.clone(), positive));
        self.axes.insert(axis, AxisStatus::default());
        self
    }

    /// Adds a key binding connecting the given keycode to the given
    /// logical button.
    pub fn bind_key_to_button(mut self, keycode: Keycode, button: Buttons) -> Self {
        self.bindings.insert(InputEvent::KeyEvent(keycode),
                             InputEffect::Button(button.clone()));
        self.buttons.insert(button, false);
        self
    }

    /// Updates the logical input state based on the actual
    /// physical input state.  Should be called in your update()
    /// handler.
    /// So, it will do things like move the axes and so on.
    pub fn update(&mut self, dt: f64) {
        for (_axis, axis_status) in self.axes.iter_mut() {
            if axis_status.direction != 0.0 {
                // Accelerate the axis towards the
                // input'ed direction.
                let abs_dx = f64::min(axis_status.acceleration * dt,
                                      1.0 - f64::abs(axis_status.position));
                let dx = if axis_status.direction > 0.0 {
                    abs_dx
                } else {
                    -abs_dx
                };
                axis_status.position += dx;
            } else {
                // Gravitate back towards 0.
                let abs_dx = f64::min(axis_status.gravity * dt, f64::abs(axis_status.position));
                let dx = if axis_status.position > 0.0 {
                    -abs_dx
                } else {
                    abs_dx
                };
                axis_status.position += dx;
            }
        }
    }

    /// This method should get called by your key_down_event handler.
    pub fn update_keydown(&mut self, keycode: Option<Keycode>) {
        if let Some(keycode) = keycode {
            let effect = {
                if let Some(e) = self.bindings.get(&InputEvent::KeyEvent(keycode)) {
                    e.clone()
                } else {
                    return;
                }
            };
            self.update_effect(effect, true);
        }
    }

    /// This method should get called by your key_up_event handler.
    pub fn update_keyup(&mut self, keycode: Option<Keycode>) {
        if let Some(keycode) = keycode {
            let effect = {
                if let Some(e) = self.bindings.get(&InputEvent::KeyEvent(keycode)) {
                    e.clone()
                } else {
                    return;
                }
            };
            self.update_effect(effect, false);
        }
    }

    /// Takes an InputEffect and actually applies it.
    fn update_effect(&mut self, effect: InputEffect<Axes, Buttons>, started: bool) {
        match effect {
            InputEffect::Axis(axis, direction) => {
                let f = || AxisStatus::default();
                let axis_status = self.axes.entry(axis).or_insert_with(f);
                if started {
                    let direction_float = if direction {
                        1.0
                    } else {
                        -1.0
                    };
                    axis_status.direction = direction_float;
                } else {
                    axis_status.direction = 0.0;
                }
            }
            InputEffect::Button(button) => {
                let button_pressed = self.buttons.entry(button).or_insert(started);
                *button_pressed = started;

            }
        }
    }

    pub fn get_axis(&mut self, axis: Axes) -> f64 {
        let f = || AxisStatus::default();
        let axis_status = self.axes.entry(axis).or_insert_with(f);
        axis_status.position
    }

    pub fn get_axis_raw(&mut self, axis: Axes) -> f64 {
        let f = || AxisStatus::default();
        let axis_status = self.axes.entry(axis).or_insert_with(f);
        axis_status.direction
    }

    pub fn get_button(&self, axis: &Buttons) -> bool {
        if let Some(pressed) = self.buttons.get(axis) {
            *pressed
        } else {
            false
        }
    }

    pub fn get_button_down(&self, axis: &Buttons) -> bool {
        self.get_button(axis)
    }

    pub fn get_button_up(&self, axis: &Buttons) -> bool {
        !self.get_button(axis)
    }

    pub fn mouse_position() {}

    pub fn mouse_scroll_delta() {}

    pub fn get_mouse_button() {}

    pub fn get_mouse_button_down() {}

    pub fn get_mouse_button_up() {}

    pub fn reset_input_axes(&mut self) {
        for (_axis, axis_status) in self.axes.iter_mut() {
            axis_status.position = 0.0;
            axis_status.direction = 0.0;
        }
    }
}

mod tests {

    #[ignore(unused_imports)]
    use ggez::event::*;
    #[ignore(unused_imports)]
    use super::*;

    #[ignore(dead_code)]
    #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)]
    enum Buttons {
        A,
        B,
        Select,
        Start,
    }

    #[ignore(dead_code)]
    #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)]
    enum Axes {
        Horz,
        Vert,
    }
    #[test]
    fn test_input_events() {
        let mut im = InputManager::<Axes, Buttons>::new()
                         .bind_key_to_button(Keycode::Z, Buttons::A)
                         .bind_key_to_button(Keycode::X, Buttons::B)
                         .bind_key_to_button(Keycode::Return, Buttons::Start)
                         .bind_key_to_button(Keycode::RShift, Buttons::Select)
                         .bind_key_to_axis(Keycode::Up, Axes::Vert, true)
                         .bind_key_to_axis(Keycode::Down, Axes::Vert, false)
                         .bind_key_to_axis(Keycode::Left, Axes::Horz, false)
                         .bind_key_to_axis(Keycode::Right, Axes::Horz, true);

        im.update_keydown(Some(Keycode::Z));
        assert!(im.get_button(&Buttons::A));
        assert!(im.get_button_down(&Buttons::A));
        im.update_keyup(Some(Keycode::Z));
        assert!(!im.get_button(&Buttons::A));
        assert!(im.get_button_up(&Buttons::A));

        // Push the 'up' button, watch the axis
        // increase to 1.0 but not beyond
        im.update_keydown(Some(Keycode::Up));
        assert!(im.get_axis_raw(Axes::Vert) > 0.0);
        while im.get_axis(Axes::Vert) < 0.99 {
            im.update(0.16);
            assert!(im.get_axis(Axes::Vert) >= 0.0);
            assert!(im.get_axis(Axes::Vert) <= 1.0);
        }
        // Release it, watch it wind down
        im.update_keyup(Some(Keycode::Up));
        while im.get_axis(Axes::Vert) > 0.01 {
            im.update(0.16);
            assert!(im.get_axis(Axes::Vert) >= 0.0)
        }

        // Do the same with the 'down' button.
        im.update_keydown(Some(Keycode::Down));
        while im.get_axis(Axes::Vert) > -0.99 {
            im.update(0.16);
            assert!(im.get_axis(Axes::Vert) <= 0.0);
            assert!(im.get_axis(Axes::Vert) >= -1.0);
        }
    }
}
<|endoftext|>


<commit_before>use std::collections::LinkedList;
use std::borrow::Borrow;

use token::Token;

use std::cell::Cell;
use std::rc::Rc;
use regex::Regex;
use context::Context;
use token::Posn;

pub trait ESCharExt {
    fn is_es_newline(self) -> bool;
    fn is_es_whitespace(self) -> bool;
    fn is_es_identifier(self) -> bool;
    fn is_es_identifier_start(self) -> bool;
    fn is_es_identifier_continue(self) -> bool;
}

/*
// https://github.com/ariya/esprima/blob/master/tools/generate-identifier-regex.js
// https://gist.github.com/mathiasbynens/6334847
// http://unicode.org/reports/tr31/

pub trait UnicodeCharExt {
    fn is_L(self) -> bool;
    fn is_Lu(self) -> bool;
    fn is_Ll(self) -> bool;
    fn is_Lt(self) -> bool;
    fn is_Lm(self) -> bool;
    fn is_Lo(self) -> bool;
    fn is_Nl(self) -> bool;
    fn is_OtherID_Start(self) -> bool;
}

impl UnicodeCharExt for char {
    fn is_L(self) -> bool {
        self.is_Lu() ||
        self.is_Ll() ||
        self.is_Lt() ||
        self.is_Lm() ||
        self.is_Lo()
    }

    fn is_Lu(self) -> bool {
        false
    }

    fn is_Ll(self) -> bool {
        false
    }

    fn is_Lt(self) -> bool {
        false
    }

    fn is_Lm(self) -> bool {
        false
    }

    fn is_Lo(self) -> bool {
        false
    }

    fn is_Nl(self) -> bool {
        false
    }

    fn is_OtherID_Start(self) -> bool {
        false
    }
}
*/

impl ESCharExt for char {
    fn is_es_newline(self) -> bool {
        match self {
            '\u{000a}' | '\u{000d}' | '\u{2028}' | '\u{2029}' => true,
            _ => false
        }
    }

    fn is_es_whitespace(self) -> bool {
        match self {
              '\u{0009}' | '\u{000b}' | '\u{000c}' | '\u{0020}' | '\u{00a0}'
            | '\u{1680}' | '\u{2000}' | '\u{2001}' | '\u{2002}' | '\u{2003}' | '\u{2004}'
            | '\u{2005}' | '\u{2006}' | '\u{2009}' | '\u{200a}' | '\u{202f}' | '\u{205f}'
            | '\u{3000}' | '\u{feff}' => true,
            _ => false
        }
    }

    fn is_es_identifier(self) -> bool {
        self.is_es_identifier_continue()
    }

    fn is_es_identifier_start(self) -> bool {
        self == '$' ||
        self == '_' ||
        self.is_alphabetic()
    }

    fn is_es_identifier_continue(self) -> bool {
        self.is_es_identifier_start() ||
        self.is_numeric()
    }
}

struct LineOrientedReader<I> {
    chars: I,
    curr_char: Option<char>,
    next_char: Option<char>,
    curr_posn: Posn
}

impl<I> LineOrientedReader<I> where I: Iterator<Item=char> {
    pub fn new(mut chars: I) -> LineOrientedReader<I> {
        let curr_char = chars.next();
        let next_char = if curr_char.is_some() { chars.next() } else { None };
        LineOrientedReader {
            chars: chars,
            curr_char: curr_char,
            next_char: next_char,
            curr_posn: Posn {
                offset: 0,
                line: 0,
                column: 0
            }
        }
    }

    pub fn curr_char(&mut self) -> Option<char> { self.curr_char }
    pub fn curr_posn(&mut self) -> Posn { self.curr_posn }
    pub fn next_char(&mut self) -> Option<char> { self.next_char }

    pub fn bump(&mut self) {
        let curr_char = self.next_char;
        let next_char = if curr_char.is_some() { self.chars.next() } else { None };

        self.curr_char = curr_char;
        self.next_char = next_char;

        if (curr_char == Some('\r') && next_char != Some('\n')) ||
           curr_char == Some('\n') ||
           curr_char == Some('\u{2028}') ||
           curr_char == Some('\u{2029}') {
            self.curr_posn.line += 1;
            self.curr_posn.column = 0;
        } else {
            self.curr_posn.column += 1;
        }

        self.curr_posn.offset += 1;
    }
}

// test case: x=0;y=g=1;alert(eval("while(x)break\n/y/g.exec('y')"))
//       see: https://groups.google.com/d/msg/mozilla.dev.tech.js-engine.internals/2JLH5jRcr7E/Mxc7ZKc5r6sJ

struct TokenBuffer {
    tokens: LinkedList<Token>
}

impl TokenBuffer {
    fn new() -> TokenBuffer {
        TokenBuffer {
            tokens: LinkedList::new()
        }
    }

    fn is_empty(&mut self) -> bool {
        self.tokens.len() == 0
    }

    fn push_token(&mut self, token: Token) {
        assert!(self.tokens.len() == 0);
        self.tokens.push_back(token);
    }

    fn read_token(&mut self) -> Token {
        assert!(self.tokens.len() > 0);
        self.tokens.pop_front().unwrap()
    }

    fn peek_token(&mut self) -> &Token {
        assert!(self.tokens.len() > 0);
        self.tokens.front().unwrap()
    }

    fn unread_token(&mut self, token: Token) {
        assert!(self.tokens.len() >= 0);
        assert!(self.tokens.len() < 3);
        self.tokens.push_front(token);
    }
}

pub struct Lexer<I> {
    reader: LineOrientedReader<I>,
    cx: Rc<Cell<Context>>,
    lookahead: TokenBuffer
}

impl<I> Lexer<I> where I: Iterator<Item=char> {
    // constructor

    pub fn new(chars: I, cx: Rc<Cell<Context>>) -> Lexer<I> {
        Lexer {
            reader: LineOrientedReader::new(chars),
            cx: cx,
            lookahead: TokenBuffer::new()
        }
    }

    // public methods

    pub fn peek_token(&mut self) -> &Token {
        if self.lookahead.is_empty() {
            let token = self.read_next_token();
            self.lookahead.push_token(token);
        }
        self.lookahead.peek_token()
    }

    pub fn read_token(&mut self) -> Token {
        if self.lookahead.is_empty() {
            self.read_next_token()
        } else {
            self.lookahead.read_token()
        }
    }

    pub fn unread_token(&mut self, token: Token) {
        self.lookahead.unread_token(token);
    }

    // private methods

    fn bump_until(&mut self, ch: char) {
        while self.reader.curr_char() != Some(ch) {
            self.bump();
        }
    }

    fn skip_line_comment(&mut self) {
        self.bump();
        self.bump();
        loop {
            match self.reader.curr_char() {
                Some(ch) if ch.is_es_newline() => return,
                None => return,
                _ => ()
            }
            self.bump();
        }
    }

    fn skip_block_comment(&mut self) -> bool {
        self.bump();
        self.bump();
        self.bump_until('*');
        if self.reader.curr_char() == None {
            return false;
        }
        self.bump();
        self.bump();
        true
    }

    fn div_or_regexp(&mut self) -> Token {
        if self.cx.get().is_operator() {
            self.reader.bump();
            Token::Slash
        } else {
            unimplemented!()
        }
    }

    fn if_assign(&mut self, cons: Token, alt: Token) -> Token {
        self.reader.bump();
        if self.reader.curr_char() == Some('=') { self.reader.bump(); cons } else { alt }
    }

    fn if_equality(&mut self, zero: Token, one: Token, two: Token) -> Token {
        self.bump();
        if self.reader.curr_char() == Some('=') {
            self.bump();
            if self.reader.curr_char() == Some('=') {
                self.bump();
                two
            } else {
                one
            }
        } else {
            zero
        }
    }

    fn lt(&mut self) -> Token {
        self.bump();
        match self.reader.curr_char() {
            Some('<') => {
                self.bump();
                if self.reader.curr_char() == Some('=') {
                    self.bump();
                    Token::LShiftAssign
                } else {
                    Token::LShift
                }
            },
            Some('=') => { self.bump(); Token::LEq }
            _ => Token::LAngle
        }
    }

    fn gt(&mut self) -> Token {
        self.bump();
        match self.reader.curr_char() {
            Some('>') => {
                self.bump();
                match self.reader.curr_char() {
                    Some('>') => {
                        self.bump();
                        if self.reader.curr_char() == Some('=') {
                            self.bump();
                            Token::URShiftAssign
                        } else {
                            Token::URShift
                        }
                    },
                    Some('=') => { self.bump(); Token::RShiftAssign },
                    _ => Token::RShift
                }
            },
            Some('=') => { self.bump(); Token::GEq },
            _ => Token::RAngle
        }
    }

    fn plus(&mut self) -> Token {
        self.bump();
        match self.reader.curr_char() {
            Some('+') => { self.bump(); Token::Inc },
            Some('=') => { self.bump(); Token::PlusAssign },
            _ => Token::Plus
        }
    }

    fn minus(&mut self) -> Token {
        self.bump();
        match self.reader.curr_char() {
            Some('-') => { self.bump(); Token::Dec },
            Some('=') => { self.bump(); Token::MinusAssign },
            _ => Token::Minus
        }
    }

    fn number(&mut self) -> Token {
        if self.reader.curr_char() == Some('1') {
            self.bump();
            return Token::DecimalInt(String::from_str("1"));
        }
        unimplemented!()
    }

    fn string(&mut self) -> Token { unimplemented!() }
    fn word(&mut self) -> Token { unimplemented!() }

    fn read_next_token(&mut self) -> Token {
        self.skip_whitespace();
        println!("inspecting {:?}", self.reader.curr_char());
        loop {
            match self.reader.curr_char() {
                Some('/') => {
                    match self.reader.next_char() {
                        Some('/') => self.skip_line_comment(),
                        Some('*') => if !self.skip_block_comment() { return Token::Error(None) },
                        _ => return self.div_or_regexp()
                    }
                },
                Some('.') => {
                    match self.reader.next_char() {
                        Some(ch) if ch.is_digit(10) => { return self.number() },
                        _ => { self.reader.bump(); return Token::Dot }
                    }
                }
                Some('{') => { self.bump(); return Token::LBrace },
                Some('}') => { self.bump(); return Token::RBrace },
                Some('[') => { self.bump(); return Token::LBrack },
                Some(']') => { self.bump(); return Token::RBrack },
                Some('(') => { self.bump(); return Token::LParen },
                Some(')') => { self.bump(); return Token::RParen },
                Some(';') => { self.bump(); return Token::Semi },
                Some(':') => { self.bump(); return Token::Colon },
                Some(',') => { self.bump(); return Token::Comma },
                Some('<') => return self.lt(),
                Some('>') => return self.gt(),
                Some('=') => return self.if_equality(Token::Assign, Token::Eq, Token::StrictEq),
                Some('+') => return self.plus(),
                Some('-') => return self.minus(),
                Some('*') => return self.if_assign(Token::StarAssign, Token::Star),
                Some('%') => return self.if_assign(Token::ModAssign, Token::Mod),
                Some('^') => return self.if_assign(Token::BitXorAssign, Token::BitXor),
                Some('&') => {
                    self.bump();
                    match self.reader.curr_char() {
                        Some('&') => { self.bump(); return Token::LogicalAnd },
                        _ => return Token::BitAnd
                    }
                },
                Some('|') => {
                    self.bump();
                    match self.reader.curr_char() {
                        Some('|') => { self.bump(); return Token::LogicalOr },
                        _ => return Token::BitOr
                    }
                },
                Some('~') => { self.bump(); return Token::Tilde },
                Some('!') => return self.if_equality(Token::Bang, Token::NEq, Token::StrictNEq),
                Some('?') => { self.bump(); return Token::Question },
                Some('"') => return self.string(),
                Some('\'') => return self.string(),
                Some('\r') => {
                    self.bump();
                    if self.reader.curr_char() == Some('\n') {
                        self.bump();
                    }
                    if self.cx.get().is_asi_possible() {
                        return Token::Newline;
                    }
                },
                Some(ch) if ch.is_es_newline() => {
                    self.bump();
                    if self.cx.get().is_asi_possible() {
                        return Token::Newline;
                    }
                }
                Some(ch) if ch.is_digit(10) => return self.number(),
                Some(ch) if ch.is_es_identifier() => return self.word(),
                Some(ch) => return Token::Error(Some(ch)),
                None => return Token::EOF
            }
        }
    }

    fn is_whitespace(&mut self) -> bool {
        match self.reader.curr_char() {
            Some(ch) => ch.is_es_whitespace(),
            None => false
        }
    }

    fn bump(&mut self) {
        self.reader.bump();
    }

    fn eat(&mut self) -> Option<char> {
        let ch = self.reader.curr_char();
        self.bump();
        ch
    }

    fn skip_whitespace(&mut self) {
        while self.is_whitespace() {
            self.reader.bump();
        }
    }
}

impl<I> Iterator for Lexer<I> where I: Iterator<Item=char> {
    type Item = Token;

    fn next(&mut self) -> Option<Token> {
        match self.read_token() {
            Token::EOF => None,
            t => Some(t)
        }
    }
}
<commit_msg>refactored bump_until to a HOF on first try!<commit_after>use std::collections::LinkedList;
use std::borrow::Borrow;

use token::Token;

use std::cell::Cell;
use std::rc::Rc;
use regex::Regex;
use context::Context;
use token::Posn;

pub trait ESCharExt {
    fn is_es_newline(self) -> bool;
    fn is_es_whitespace(self) -> bool;
    fn is_es_identifier(self) -> bool;
    fn is_es_identifier_start(self) -> bool;
    fn is_es_identifier_continue(self) -> bool;
}

/*
// https://github.com/ariya/esprima/blob/master/tools/generate-identifier-regex.js
// https://gist.github.com/mathiasbynens/6334847
// http://unicode.org/reports/tr31/

pub trait UnicodeCharExt {
    fn is_L(self) -> bool;
    fn is_Lu(self) -> bool;
    fn is_Ll(self) -> bool;
    fn is_Lt(self) -> bool;
    fn is_Lm(self) -> bool;
    fn is_Lo(self) -> bool;
    fn is_Nl(self) -> bool;
    fn is_OtherID_Start(self) -> bool;
}

impl UnicodeCharExt for char {
    fn is_L(self) -> bool {
        self.is_Lu() ||
        self.is_Ll() ||
        self.is_Lt() ||
        self.is_Lm() ||
        self.is_Lo()
    }

    fn is_Lu(self) -> bool {
        false
    }

    fn is_Ll(self) -> bool {
        false
    }

    fn is_Lt(self) -> bool {
        false
    }

    fn is_Lm(self) -> bool {
        false
    }

    fn is_Lo(self) -> bool {
        false
    }

    fn is_Nl(self) -> bool {
        false
    }

    fn is_OtherID_Start(self) -> bool {
        false
    }
}
*/

impl ESCharExt for char {
    fn is_es_newline(self) -> bool {
        match self {
            '\u{000a}' | '\u{000d}' | '\u{2028}' | '\u{2029}' => true,
            _ => false
        }
    }

    fn is_es_whitespace(self) -> bool {
        match self {
              '\u{0009}' | '\u{000b}' | '\u{000c}' | '\u{0020}' | '\u{00a0}'
            | '\u{1680}' | '\u{2000}' | '\u{2001}' | '\u{2002}' | '\u{2003}' | '\u{2004}'
            | '\u{2005}' | '\u{2006}' | '\u{2009}' | '\u{200a}' | '\u{202f}' | '\u{205f}'
            | '\u{3000}' | '\u{feff}' => true,
            _ => false
        }
    }

    fn is_es_identifier(self) -> bool {
        self.is_es_identifier_continue()
    }

    fn is_es_identifier_start(self) -> bool {
        self == '$' ||
        self == '_' ||
        self.is_alphabetic()
    }

    fn is_es_identifier_continue(self) -> bool {
        self.is_es_identifier_start() ||
        self.is_numeric()
    }
}

struct LineOrientedReader<I> {
    chars: I,
    curr_char: Option<char>,
    next_char: Option<char>,
    curr_posn: Posn
}

impl<I> LineOrientedReader<I> where I: Iterator<Item=char> {
    pub fn new(mut chars: I) -> LineOrientedReader<I> {
        let curr_char = chars.next();
        let next_char = if curr_char.is_some() { chars.next() } else { None };
        LineOrientedReader {
            chars: chars,
            curr_char: curr_char,
            next_char: next_char,
            curr_posn: Posn {
                offset: 0,
                line: 0,
                column: 0
            }
        }
    }

    pub fn curr_char(&mut self) -> Option<char> { self.curr_char }
    pub fn curr_posn(&mut self) -> Posn { self.curr_posn }
    pub fn next_char(&mut self) -> Option<char> { self.next_char }

    pub fn bump(&mut self) {
        let curr_char = self.next_char;
        let next_char = if curr_char.is_some() { self.chars.next() } else { None };

        self.curr_char = curr_char;
        self.next_char = next_char;

        if (curr_char == Some('\r') && next_char != Some('\n')) ||
           curr_char == Some('\n') ||
           curr_char == Some('\u{2028}') ||
           curr_char == Some('\u{2029}') {
            self.curr_posn.line += 1;
            self.curr_posn.column = 0;
        } else {
            self.curr_posn.column += 1;
        }

        self.curr_posn.offset += 1;
    }
}

// test case: x=0;y=g=1;alert(eval("while(x)break\n/y/g.exec('y')"))
//       see: https://groups.google.com/d/msg/mozilla.dev.tech.js-engine.internals/2JLH5jRcr7E/Mxc7ZKc5r6sJ

struct TokenBuffer {
    tokens: LinkedList<Token>
}

impl TokenBuffer {
    fn new() -> TokenBuffer {
        TokenBuffer {
            tokens: LinkedList::new()
        }
    }

    fn is_empty(&mut self) -> bool {
        self.tokens.len() == 0
    }

    fn push_token(&mut self, token: Token) {
        assert!(self.tokens.len() == 0);
        self.tokens.push_back(token);
    }

    fn read_token(&mut self) -> Token {
        assert!(self.tokens.len() > 0);
        self.tokens.pop_front().unwrap()
    }

    fn peek_token(&mut self) -> &Token {
        assert!(self.tokens.len() > 0);
        self.tokens.front().unwrap()
    }

    fn unread_token(&mut self, token: Token) {
        assert!(self.tokens.len() >= 0);
        assert!(self.tokens.len() < 3);
        self.tokens.push_front(token);
    }
}

pub struct Lexer<I> {
    reader: LineOrientedReader<I>,
    cx: Rc<Cell<Context>>,
    lookahead: TokenBuffer
}

impl<I> Lexer<I> where I: Iterator<Item=char> {
    // constructor

    pub fn new(chars: I, cx: Rc<Cell<Context>>) -> Lexer<I> {
        Lexer {
            reader: LineOrientedReader::new(chars),
            cx: cx,
            lookahead: TokenBuffer::new()
        }
    }

    // public methods

    pub fn peek_token(&mut self) -> &Token {
        if self.lookahead.is_empty() {
            let token = self.read_next_token();
            self.lookahead.push_token(token);
        }
        self.lookahead.peek_token()
    }

    pub fn read_token(&mut self) -> Token {
        if self.lookahead.is_empty() {
            self.read_next_token()
        } else {
            self.lookahead.read_token()
        }
    }

    pub fn unread_token(&mut self, token: Token) {
        self.lookahead.unread_token(token);
    }

    // private methods

    fn bump_until<F: Fn(char) -> bool>(&mut self, pred: F) {
        loop {
            match self.reader.curr_char() {
                Some(ch) if pred(ch) => return,
                None => return,
                _ => ()
            }
            self.bump();
        }
    }

    fn skip_line_comment(&mut self) {
        self.bump();
        self.bump();
        self.bump_until(|ch| ch.is_es_newline());
    }

    fn skip_block_comment(&mut self) -> bool {
        self.bump();
        self.bump();
        self.bump_until(|ch| ch == '*');
        if self.reader.curr_char() == None {
            return false;
        }
        self.bump();
        self.bump();
        true
    }

    fn div_or_regexp(&mut self) -> Token {
        if self.cx.get().is_operator() {
            self.reader.bump();
            Token::Slash
        } else {
            unimplemented!()
        }
    }

    fn if_assign(&mut self, cons: Token, alt: Token) -> Token {
        self.reader.bump();
        if self.reader.curr_char() == Some('=') { self.reader.bump(); cons } else { alt }
    }

    fn if_equality(&mut self, zero: Token, one: Token, two: Token) -> Token {
        self.bump();
        if self.reader.curr_char() == Some('=') {
            self.bump();
            if self.reader.curr_char() == Some('=') {
                self.bump();
                two
            } else {
                one
            }
        } else {
            zero
        }
    }

    fn lt(&mut self) -> Token {
        self.bump();
        match self.reader.curr_char() {
            Some('<') => {
                self.bump();
                if self.reader.curr_char() == Some('=') {
                    self.bump();
                    Token::LShiftAssign
                } else {
                    Token::LShift
                }
            },
            Some('=') => { self.bump(); Token::LEq }
            _ => Token::LAngle
        }
    }

    fn gt(&mut self) -> Token {
        self.bump();
        match self.reader.curr_char() {
            Some('>') => {
                self.bump();
                match self.reader.curr_char() {
                    Some('>') => {
                        self.bump();
                        if self.reader.curr_char() == Some('=') {
                            self.bump();
                            Token::URShiftAssign
                        } else {
                            Token::URShift
                        }
                    },
                    Some('=') => { self.bump(); Token::RShiftAssign },
                    _ => Token::RShift
                }
            },
            Some('=') => { self.bump(); Token::GEq },
            _ => Token::RAngle
        }
    }

    fn plus(&mut self) -> Token {
        self.bump();
        match self.reader.curr_char() {
            Some('+') => { self.bump(); Token::Inc },
            Some('=') => { self.bump(); Token::PlusAssign },
            _ => Token::Plus
        }
    }

    fn minus(&mut self) -> Token {
        self.bump();
        match self.reader.curr_char() {
            Some('-') => { self.bump(); Token::Dec },
            Some('=') => { self.bump(); Token::MinusAssign },
            _ => Token::Minus
        }
    }

    fn number(&mut self) -> Token {
        if self.reader.curr_char() == Some('1') {
            self.bump();
            return Token::DecimalInt(String::from_str("1"));
        }
        unimplemented!()
    }

    fn string(&mut self) -> Token { unimplemented!() }
    fn word(&mut self) -> Token { unimplemented!() }

    fn read_next_token(&mut self) -> Token {
        self.skip_whitespace();
        println!("inspecting {:?}", self.reader.curr_char());
        loop {
            match self.reader.curr_char() {
                Some('/') => {
                    match self.reader.next_char() {
                        Some('/') => self.skip_line_comment(),
                        Some('*') => if !self.skip_block_comment() { return Token::Error(None) },
                        _ => return self.div_or_regexp()
                    }
                },
                Some('.') => {
                    match self.reader.next_char() {
                        Some(ch) if ch.is_digit(10) => { return self.number() },
                        _ => { self.reader.bump(); return Token::Dot }
                    }
                }
                Some('{') => { self.bump(); return Token::LBrace },
                Some('}') => { self.bump(); return Token::RBrace },
                Some('[') => { self.bump(); return Token::LBrack },
                Some(']') => { self.bump(); return Token::RBrack },
                Some('(') => { self.bump(); return Token::LParen },
                Some(')') => { self.bump(); return Token::RParen },
                Some(';') => { self.bump(); return Token::Semi },
                Some(':') => { self.bump(); return Token::Colon },
                Some(',') => { self.bump(); return Token::Comma },
                Some('<') => return self.lt(),
                Some('>') => return self.gt(),
                Some('=') => return self.if_equality(Token::Assign, Token::Eq, Token::StrictEq),
                Some('+') => return self.plus(),
                Some('-') => return self.minus(),
                Some('*') => return self.if_assign(Token::StarAssign, Token::Star),
                Some('%') => return self.if_assign(Token::ModAssign, Token::Mod),
                Some('^') => return self.if_assign(Token::BitXorAssign, Token::BitXor),
                Some('&') => {
                    self.bump();
                    match self.reader.curr_char() {
                        Some('&') => { self.bump(); return Token::LogicalAnd },
                        _ => return Token::BitAnd
                    }
                },
                Some('|') => {
                    self.bump();
                    match self.reader.curr_char() {
                        Some('|') => { self.bump(); return Token::LogicalOr },
                        _ => return Token::BitOr
                    }
                },
                Some('~') => { self.bump(); return Token::Tilde },
                Some('!') => return self.if_equality(Token::Bang, Token::NEq, Token::StrictNEq),
                Some('?') => { self.bump(); return Token::Question },
                Some('"') => return self.string(),
                Some('\'') => return self.string(),
                Some('\r') => {
                    self.bump();
                    if self.reader.curr_char() == Some('\n') {
                        self.bump();
                    }
                    if self.cx.get().is_asi_possible() {
                        return Token::Newline;
                    }
                },
                Some(ch) if ch.is_es_newline() => {
                    self.bump();
                    if self.cx.get().is_asi_possible() {
                        return Token::Newline;
                    }
                }
                Some(ch) if ch.is_digit(10) => return self.number(),
                Some(ch) if ch.is_es_identifier() => return self.word(),
                Some(ch) => return Token::Error(Some(ch)),
                None => return Token::EOF
            }
        }
    }

    fn is_whitespace(&mut self) -> bool {
        match self.reader.curr_char() {
            Some(ch) => ch.is_es_whitespace(),
            None => false
        }
    }

    fn bump(&mut self) {
        self.reader.bump();
    }

    fn eat(&mut self) -> Option<char> {
        let ch = self.reader.curr_char();
        self.bump();
        ch
    }

    fn skip_whitespace(&mut self) {
        while self.is_whitespace() {
            self.reader.bump();
        }
    }
}

impl<I> Iterator for Lexer<I> where I: Iterator<Item=char> {
    type Item = Token;

    fn next(&mut self) -> Option<Token> {
        match self.read_token() {
            Token::EOF => None,
            t => Some(t)
        }
    }
}
<|endoftext|>