<commit_before>use std::{
    sync::{
        atomic::{self, AtomicU16, AtomicU32, AtomicU64},
        Arc, Mutex,
    },
    time::Instant,
};

use anyhow::{Context as _, Error};
use gba::{Button, ButtonSet};
use sdl2::{
    event::Event,
    pixels::{Color, PixelFormatEnum},
};

const LOCK_FPS: bool = false;

fn main() {
    if let Err(err) = run() {
        eprintln!("{:#}", err);
        std::process::exit(1);
    }
    std::process::exit(0)
}

fn run() -> anyhow::Result<()> {
    pretty_env_logger::formatted_builder()
        .parse_filters(&std::env::var("RUST_LOG").unwrap_or_else(|_| String::from("info")))
        .try_init()
        .context("failed to initiaize logger")?;

    let config = pyrite::config::from_toml_path("pyrite.toml")
        .map_err(|err| {
            let err = anyhow::Error::from(err);
            log::error!("{:#}", err);
        })
        .unwrap_or_default();

    let frame_delay_target = std::time::Duration::from_secs_f64(1.0 / 60.0);

    let sdl_context = sdl2::init()
        .map_err(Error::msg)
        .context("failed to initialize SDL")?;
    let video_subsystem = sdl_context
        .video()
        .map_err(Error::msg)
        .context("failed to initialize SDL video")?;
    let window = video_subsystem
        .window("Pyrite", 480, 320)
        .position_centered()
        .resizable()
        .build()
        .context("failed to create SDL window")?;
    let mut event_pump = sdl_context
        .event_pump()
        .map_err(Error::msg)
        .context("failed to initialize SDL event pump")?;
    let mut canvas_builder = window.into_canvas().accelerated();

    if config.graphics.vsync.unwrap_or(true) {
        canvas_builder = canvas_builder.present_vsync();
    }

    let mut canvas = canvas_builder
        .build()
        .context("failed to initialize SDL canvas")?;
    let texture_creator = canvas.texture_creator();

    let mut gba_frame_texture = texture_creator
        .create_texture_streaming(PixelFormatEnum::BGR555, 240, 160)
        .context("failed to create GBA frame texture")?;
    gba_frame_texture
        .with_lock(None, |buffer: &mut [u8], _: usize| buffer.fill(0))
        .map_err(Error::msg)
        .context("failed to update GBA frame texture")?;
    let screen_buffer = Arc::new(Mutex::new([0u16; gba::SCREEN_PIXEL_COUNT]));

    let gba = pyrite::GbaHandle::new();

    let rom = get_rom_from_args().context("error occurred retrieving ROM from args")?;
    gba.after_frame_wait(move |gba, _| {
        gba.set_gamepak(rom);
        gba.reset();
    });

    let mut buttons = ButtonSet::default();
    let buttons_u16 = Arc::new(AtomicU16::new(buttons.into()));
    let buttons_u16_gba = buttons_u16.clone();

    let frame_duration_accumulate = Arc::new(AtomicU64::new(0));
    let frame_duration_count = Arc::new(AtomicU32::new(0));

    let frame_duration_accumulate_gba = frame_duration_accumulate.clone();
    let frame_duration_count_gba = frame_duration_count.clone();
    let screen = screen_buffer.clone();
    gba.on_frame(move |gba, state| {
        let buttons = buttons_u16_gba.load(atomic::Ordering::Acquire);
        gba.set_buttons(ButtonSet::from(buttons));

        let mut screen = screen.lock().expect("failed to lock screen buffer");
        screen.copy_from_slice(gba.video().screen());

        let frame_duration_us: u64 = state.frame_duration().as_micros().try_into().unwrap();
        frame_duration_accumulate_gba.fetch_add(frame_duration_us, atomic::Ordering::Release);
        frame_duration_count_gba.fetch_add(1, atomic::Ordering::Release);
    });
    gba.set_paused(false);

    let mut fps_counter = FPSCounter::default();

    log::trace!("starting SDL loop...");
    canvas.set_draw_color(Color::RGB(255, 0, 255));
    'main_loop: loop {
        let frame_start_time = Instant::now();

        for event in event_pump.poll_iter() {
            match event {
                Event::Quit { .. } => {
                    log::trace!("exiting SDL loop...");
                    break 'main_loop;
                }

                Event::KeyDown {
                    keycode: Some(keycode),
                    ..
                } => {
                    if let Some(button) = keycode_to_button(keycode) {
                        buttons.set_pressed(button, true)
                    }
                }

                Event::KeyUp {
                    keycode: Some(keycode),
                    ..
                } => {
                    if let Some(button) = keycode_to_button(keycode) {
                        buttons.set_pressed(button, false)
                    }
                }

                _ => (),
            }
        }

        buttons_u16.store(buttons.into(), atomic::Ordering::Release);

        gba_frame_texture
            .with_lock(None, |buffer: &mut [u8], pitch: usize| {
                let screen = screen_buffer.lock().expect("failed to lock screen buffer");
                for y in 0..160 {
                    for x in 0..240 {
                        let offset_src = y * 240 + x;
                        let offset_dst = y * pitch + x * 2;

                        buffer[offset_dst] = screen[offset_src] as u8;
                        buffer[offset_dst + 1] = (screen[offset_src] >> 8) as u8;
                    }
                }
            })
            .map_err(Error::msg)
            .context("failed to update GBA frame texture")?;

        canvas.clear();
        canvas
            .copy(&gba_frame_texture, None, None)
            .map_err(Error::msg)
            .context("failed to copy GBA frame texture to canvas")?;
        canvas.present();

        let frame_end_time = Instant::now();
        let frame_duration = frame_end_time.duration_since(frame_start_time);
        if frame_duration < frame_delay_target && LOCK_FPS {
            let grace = std::time::Duration::from_millis(1);
            std::thread::sleep((frame_delay_target - frame_duration).saturating_sub(grace));
        }

        if let Some(fps) = fps_counter.count(frame_end_time) {
            const GBA_TARGET_FRAME_DURATION_US: f64 = 1000000.0 / 60.0;

            let frame_duration_accumulate_value =
                frame_duration_accumulate.load(atomic::Ordering::Acquire);
            let frame_duration_count_value = frame_duration_count.load(atomic::Ordering::Acquire);
            let average_frame_duration_us =
                frame_duration_accumulate_value as f64 / frame_duration_count_value as f64;
            frame_duration_accumulate
                .fetch_sub(frame_duration_accumulate_value, atomic::Ordering::Release);
            frame_duration_count.fetch_sub(frame_duration_count_value, atomic::Ordering::Release);

            let gba_average_frame_duration = average_frame_duration_us / 1000.0;
            let gba_performance_percentage =
                (GBA_TARGET_FRAME_DURATION_US / average_frame_duration_us) * 100.0;

            let title = format!(
                "Pyrite ({:.1} FPS) (GBA: {:.1} ms | {:.1} %)",
                fps, gba_average_frame_duration, gba_performance_percentage
            );
            canvas
                .window_mut()
                .set_title(&title)
                .context("failed to set window title")?;
        }
    }

    gba.set_paused(true);
    gba.after_frame_wait(|gba, _| {
        log::debug!(
            "end address: 0x{:08X} (THUMB: {})",
            gba.cpu().next_exec_pc(),
            gba.cpu().registers.getf_t()
        );
    });

    log::info!("exiting...");

    Ok(())
}

fn get_rom_from_args() -> anyhow::Result<Vec<u8>> {
    let rom_path = std::env::args()
        .nth(1)
        .map(std::path::PathBuf::from)
        .ok_or_else(|| anyhow::anyhow!("expected ROM path as first argument"))?;
    std::fs::read(&rom_path)
        .with_context(|| format!("failed to read ROM path `{}`", rom_path.display()))
}

#[derive(Default)]
struct FPSCounter {
    start_time: Option<std::time::Instant>,
    frames: u32,
}

impl FPSCounter {
    pub fn count(&mut self, now: std::time::Instant) -> Option<f64> {
        self.frames += 1;
        if self.start_time.is_none() {
            self.start_time = Some(now);
            return None;
        }

        let elapsed = now.duration_since(self.start_time.unwrap());
        if elapsed < std::time::Duration::from_secs(1) {
            return None;
        }
        self.start_time = Some(now);

        let seconds = elapsed.as_secs_f64();
        let fps = self.frames as f64 / seconds;
        self.frames = 0;

        Some(fps)
    }
}

fn keycode_to_button(keycode: sdl2::keyboard::Keycode) -> Option<Button> {
    match keycode {
        sdl2::keyboard::Keycode::Z => Some(Button::A),
        sdl2::keyboard::Keycode::X => Some(Button::B),
        sdl2::keyboard::Keycode::Left => Some(Button::Left),
        sdl2::keyboard::Keycode::Right => Some(Button::Right),
        sdl2::keyboard::Keycode::Up => Some(Button::Up),
        sdl2::keyboard::Keycode::Down => Some(Button::Down),
        sdl2::keyboard::Keycode::A => Some(Button::L),
        sdl2::keyboard::Keycode::S => Some(Button::R),
        sdl2::keyboard::Keycode::Return => Some(Button::Start),
        sdl2::keyboard::Keycode::Backspace => Some(Button::Select),
        _ => None,
    }
}
<commit_msg>Allow exiting the emulator using the escape key.<commit_after>use std::{
    sync::{
        atomic::{self, AtomicU16, AtomicU32, AtomicU64},
        Arc, Mutex,
    },
    time::Instant,
};

use anyhow::{Context as _, Error};
use gba::{Button, ButtonSet};
use sdl2::{
    event::Event,
    keyboard::Keycode,
    pixels::{Color, PixelFormatEnum},
};

const LOCK_FPS: bool = false;

fn main() {
    if let Err(err) = run() {
        eprintln!("{:#}", err);
        std::process::exit(1);
    }
    std::process::exit(0)
}

fn run() -> anyhow::Result<()> {
    pretty_env_logger::formatted_builder()
        .parse_filters(&std::env::var("RUST_LOG").unwrap_or_else(|_| String::from("info")))
        .try_init()
        .context("failed to initiaize logger")?;

    let config = pyrite::config::from_toml_path("pyrite.toml")
        .map_err(|err| {
            let err = anyhow::Error::from(err);
            log::error!("{:#}", err);
        })
        .unwrap_or_default();

    let frame_delay_target = std::time::Duration::from_secs_f64(1.0 / 60.0);

    let sdl_context = sdl2::init()
        .map_err(Error::msg)
        .context("failed to initialize SDL")?;
    let video_subsystem = sdl_context
        .video()
        .map_err(Error::msg)
        .context("failed to initialize SDL video")?;
    let window = video_subsystem
        .window("Pyrite", 480, 320)
        .position_centered()
        .resizable()
        .build()
        .context("failed to create SDL window")?;
    let mut event_pump = sdl_context
        .event_pump()
        .map_err(Error::msg)
        .context("failed to initialize SDL event pump")?;
    let mut canvas_builder = window.into_canvas().accelerated();

    if config.graphics.vsync.unwrap_or(true) {
        canvas_builder = canvas_builder.present_vsync();
    }

    let mut canvas = canvas_builder
        .build()
        .context("failed to initialize SDL canvas")?;
    let texture_creator = canvas.texture_creator();

    let mut gba_frame_texture = texture_creator
        .create_texture_streaming(PixelFormatEnum::BGR555, 240, 160)
        .context("failed to create GBA frame texture")?;
    gba_frame_texture
        .with_lock(None, |buffer: &mut [u8], _: usize| buffer.fill(0))
        .map_err(Error::msg)
        .context("failed to update GBA frame texture")?;
    let screen_buffer = Arc::new(Mutex::new([0u16; gba::SCREEN_PIXEL_COUNT]));

    let gba = pyrite::GbaHandle::new();

    let rom = get_rom_from_args().context("error occurred retrieving ROM from args")?;
    gba.after_frame_wait(move |gba, _| {
        gba.set_gamepak(rom);
        gba.reset();
    });

    let mut buttons = ButtonSet::default();
    let buttons_u16 = Arc::new(AtomicU16::new(buttons.into()));
    let buttons_u16_gba = buttons_u16.clone();

    let frame_duration_accumulate = Arc::new(AtomicU64::new(0));
    let frame_duration_count = Arc::new(AtomicU32::new(0));

    let frame_duration_accumulate_gba = frame_duration_accumulate.clone();
    let frame_duration_count_gba = frame_duration_count.clone();
    let screen = screen_buffer.clone();
    gba.on_frame(move |gba, state| {
        let buttons = buttons_u16_gba.load(atomic::Ordering::Acquire);
        gba.set_buttons(ButtonSet::from(buttons));

        let mut screen = screen.lock().expect("failed to lock screen buffer");
        screen.copy_from_slice(gba.video().screen());

        let frame_duration_us: u64 = state.frame_duration().as_micros().try_into().unwrap();
        frame_duration_accumulate_gba.fetch_add(frame_duration_us, atomic::Ordering::Release);
        frame_duration_count_gba.fetch_add(1, atomic::Ordering::Release);
    });
    gba.set_paused(false);

    let mut fps_counter = FPSCounter::default();

    log::trace!("starting SDL loop...");
    canvas.set_draw_color(Color::RGB(255, 0, 255));
    'main_loop: loop {
        let frame_start_time = Instant::now();

        for event in event_pump.poll_iter() {
            match event {
                Event::Quit { .. }
                | Event::KeyUp {
                    keycode: Some(Keycode::Escape),
                    ..
                } => {
                    log::trace!("exiting SDL loop...");
                    break 'main_loop;
                }

                Event::KeyDown {
                    keycode: Some(keycode),
                    ..
                } => {
                    if let Some(button) = keycode_to_button(keycode) {
                        buttons.set_pressed(button, true)
                    }
                }

                Event::KeyUp {
                    keycode: Some(keycode),
                    ..
                } => {
                    if let Some(button) = keycode_to_button(keycode) {
                        buttons.set_pressed(button, false)
                    }
                }

                _ => (),
            }
        }

        buttons_u16.store(buttons.into(), atomic::Ordering::Release);

        gba_frame_texture
            .with_lock(None, |buffer: &mut [u8], pitch: usize| {
                let screen = screen_buffer.lock().expect("failed to lock screen buffer");
                for y in 0..160 {
                    for x in 0..240 {
                        let offset_src = y * 240 + x;
                        let offset_dst = y * pitch + x * 2;

                        buffer[offset_dst] = screen[offset_src] as u8;
                        buffer[offset_dst + 1] = (screen[offset_src] >> 8) as u8;
                    }
                }
            })
            .map_err(Error::msg)
            .context("failed to update GBA frame texture")?;

        canvas.clear();
        canvas
            .copy(&gba_frame_texture, None, None)
            .map_err(Error::msg)
            .context("failed to copy GBA frame texture to canvas")?;
        canvas.present();

        let frame_end_time = Instant::now();
        let frame_duration = frame_end_time.duration_since(frame_start_time);
        if frame_duration < frame_delay_target && LOCK_FPS {
            let grace = std::time::Duration::from_millis(1);
            std::thread::sleep((frame_delay_target - frame_duration).saturating_sub(grace));
        }

        if let Some(fps) = fps_counter.count(frame_end_time) {
            const GBA_TARGET_FRAME_DURATION_US: f64 = 1000000.0 / 60.0;

            let frame_duration_accumulate_value =
                frame_duration_accumulate.load(atomic::Ordering::Acquire);
            let frame_duration_count_value = frame_duration_count.load(atomic::Ordering::Acquire);
            let average_frame_duration_us =
                frame_duration_accumulate_value as f64 / frame_duration_count_value as f64;
            frame_duration_accumulate
                .fetch_sub(frame_duration_accumulate_value, atomic::Ordering::Release);
            frame_duration_count.fetch_sub(frame_duration_count_value, atomic::Ordering::Release);

            let gba_average_frame_duration = average_frame_duration_us / 1000.0;
            let gba_performance_percentage =
                (GBA_TARGET_FRAME_DURATION_US / average_frame_duration_us) * 100.0;

            let title = format!(
                "Pyrite ({:.1} FPS) (GBA: {:.1} ms | {:.1} %)",
                fps, gba_average_frame_duration, gba_performance_percentage
            );
            canvas
                .window_mut()
                .set_title(&title)
                .context("failed to set window title")?;
        }
    }

    gba.set_paused(true);
    gba.after_frame_wait(|gba, _| {
        log::debug!(
            "end address: 0x{:08X} (THUMB: {})",
            gba.cpu().next_exec_pc(),
            gba.cpu().registers.getf_t()
        );
    });

    log::info!("exiting...");

    Ok(())
}

fn get_rom_from_args() -> anyhow::Result<Vec<u8>> {
    let rom_path = std::env::args()
        .nth(1)
        .map(std::path::PathBuf::from)
        .ok_or_else(|| anyhow::anyhow!("expected ROM path as first argument"))?;
    std::fs::read(&rom_path)
        .with_context(|| format!("failed to read ROM path `{}`", rom_path.display()))
}

#[derive(Default)]
struct FPSCounter {
    start_time: Option<std::time::Instant>,
    frames: u32,
}

impl FPSCounter {
    pub fn count(&mut self, now: std::time::Instant) -> Option<f64> {
        self.frames += 1;
        if self.start_time.is_none() {
            self.start_time = Some(now);
            return None;
        }

        let elapsed = now.duration_since(self.start_time.unwrap());
        if elapsed < std::time::Duration::from_secs(1) {
            return None;
        }
        self.start_time = Some(now);

        let seconds = elapsed.as_secs_f64();
        let fps = self.frames as f64 / seconds;
        self.frames = 0;

        Some(fps)
    }
}

fn keycode_to_button(keycode: sdl2::keyboard::Keycode) -> Option<Button> {
    match keycode {
        sdl2::keyboard::Keycode::Z => Some(Button::A),
        sdl2::keyboard::Keycode::X => Some(Button::B),
        sdl2::keyboard::Keycode::Left => Some(Button::Left),
        sdl2::keyboard::Keycode::Right => Some(Button::Right),
        sdl2::keyboard::Keycode::Up => Some(Button::Up),
        sdl2::keyboard::Keycode::Down => Some(Button::Down),
        sdl2::keyboard::Keycode::A => Some(Button::L),
        sdl2::keyboard::Keycode::S => Some(Button::R),
        sdl2::keyboard::Keycode::Return => Some(Button::Start),
        sdl2::keyboard::Keycode::Backspace => Some(Button::Select),
        _ => None,
    }
}
<|endoftext|>


<commit_before>use chan;
use chan::{Sender, Receiver};
use std;
use std::borrow::Cow;

use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config,
               Error, Event, Package, UpdateRequestId};
use gateway::Interpret;
use http::{AuthClient, Client};
use oauth2::authenticate;
use package_manager::PackageManager;
use rvi::Services;
use sota::Sota;

/// An `Interpreter` loops over any incoming values, on receipt of which it
/// delegates to the `interpret` function which will respond with output values.
pub trait Interpreter<I, O> {
    fn interpret(&mut self, input: I, otx: &Sender<O>);

    fn run(&mut self, irx: Receiver<I>, otx: Sender<O>) {
        loop {
            self.interpret(irx.recv().expect("interpreter sender closed"), &otx);
        }
    }
}

/// The `EventInterpreter` listens for `Event`s and optionally responds with
/// `Command`s that may be sent to the `CommandInterpreter`.
pub struct EventInterpreter {
    pub package_manager: PackageManager
}

impl Interpreter<Event, Command> for EventInterpreter {
    fn interpret(&mut self, event: Event, ctx: &Sender<Command>) {
        info!("Event received: {}", event);
        match event {
            Event::Authenticated => {
                ctx.send(Command::SendSystemInfo);

                if self.package_manager != PackageManager::Off {
                    self.package_manager.installed_packages().map(|packages| {
                        ctx.send(Command::SendInstalledPackages(packages));
                    }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err));
                }
            }

            Event::NotAuthenticated => {
                info!("Trying to authenticate again...");
                ctx.send(Command::Authenticate(None));
            }

            Event::NewUpdatesReceived(ids) => {
                ctx.send(Command::StartDownload(ids));
            }

            Event::DownloadComplete(dl) => {
                if self.package_manager != PackageManager::Off {
                    ctx.send(Command::StartInstall(dl));
                }
            }

            Event::InstallComplete(report) => {
                ctx.send(Command::SendUpdateReport(report));
            }

            Event::UpdateReportSent => {
                if self.package_manager != PackageManager::Off {
                    self.package_manager.installed_packages().map(|packages| {
                        ctx.send(Command::SendInstalledPackages(packages));
                    }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err));
                }
            }

            _ => ()
        }
    }
}

/// The `CommandInterpreter` wraps each incoming `Command` inside an `Interpret`
/// type with no response channel for sending to the `GlobalInterpreter`.
pub struct CommandInterpreter;

impl Interpreter<Command, Interpret> for CommandInterpreter {
    fn interpret(&mut self, cmd: Command, itx: &Sender<Interpret>) {
        info!("Command received: {}", cmd);
        itx.send(Interpret { command: cmd, response_tx: None });
    }
}

/// The `GlobalInterpreter` interprets the `Command` inside incoming `Interpret`
/// messages, broadcasting `Event`s globally and (optionally) sending the final
/// outcome `Event` to the `Interpret` response channel.
pub struct GlobalInterpreter<'t> {
    pub config:      Config,
    pub token:       Option<Cow<'t, AccessToken>>,
    pub http_client: Box<Client>,
    pub rvi:         Option<Services>
}

impl<'t> Interpreter<Interpret, Event> for GlobalInterpreter<'t> {
    fn interpret(&mut self, interpret: Interpret, etx: &Sender<Event>) {
        info!("Interpreter started: {}", interpret.command);

        let (multi_tx, multi_rx) = chan::async::<Event>();
        let outcome = match (self.token.as_ref(), self.config.auth.is_none()) {
            (Some(_), _) | (_, true) => self.authenticated(interpret.command, multi_tx),
            _                        => self.unauthenticated(interpret.command, multi_tx)
        };

        let mut response_ev: Option<Event> = None;
        match outcome {
            Ok(_) => {
                for ev in multi_rx {
                    etx.send(ev.clone());
                    response_ev = Some(ev);
                }
                info!("Interpreter finished.");
            }

            Err(Error::Authorization(_)) => {
                let ev = Event::NotAuthenticated;
                etx.send(ev.clone());
                response_ev = Some(ev);
                error!("Interpreter authentication failed");
            }

            Err(err) => {
                let ev = Event::Error(format!("{}", err));
                etx.send(ev.clone());
                response_ev = Some(ev);
                error!("Interpreter failed: {}", err);
            }
        }

        let ev = response_ev.expect("no response event to send back");
        interpret.response_tx.map(|tx| tx.lock().unwrap().send(ev));
    }
}

impl<'t> GlobalInterpreter<'t> {
    fn authenticated(&self, cmd: Command, etx: Sender<Event>) -> Result<(), Error> {
        let mut sota = Sota::new(&self.config, self.http_client.as_ref());

        // always send at least one Event response
        match cmd {
            Command::Authenticate(_) => etx.send(Event::Authenticated),

            Command::GetNewUpdates => {
                let mut updates = try!(sota.get_pending_updates());
                if updates.is_empty() {
                    etx.send(Event::NoNewUpdates);
                } else {
                    updates.sort_by_key(|u| u.installPos);
                    let ids = updates.iter().map(|u| u.requestId.clone()).collect::<Vec<UpdateRequestId>>();
                    etx.send(Event::NewUpdatesReceived(ids))
                }
            }

            Command::ListInstalledPackages => {
                let mut packages: Vec<Package> = Vec::new();
                if self.config.device.package_manager != PackageManager::Off {
                    packages = try!(self.config.device.package_manager.installed_packages());
                }
                etx.send(Event::FoundInstalledPackages(packages));
            }

            Command::ListSystemInfo => {
                let info = try!(self.config.device.system_info.report());
                etx.send(Event::FoundSystemInfo(info));
            }

            Command::SendInstalledPackages(packages) => {
                let _ = sota.send_installed_packages(&packages)
                    .map_err(|err| error!("couldn't send installed packages: {}", err));
                etx.send(Event::InstalledPackagesSent);
            }

            Command::SendInstalledSoftware(sw) => {
                if let Some(ref rvi) = self.rvi {
                    let _ = rvi.remote.lock().unwrap().send_installed_software(sw);
                }
                etx.send(Event::InstalledSoftwareSent);
            }

            Command::SendSystemInfo => {
                let info = try!(self.config.device.system_info.report());
                let _ = sota.send_system_info(&info)
                    .map_err(|err| error!("couldn't send system info: {}", err));
                etx.send(Event::SystemInfoSent);
            }

            Command::SendUpdateReport(report) => {
                if let Some(ref rvi) = self.rvi {
                    let _ = rvi.remote.lock().unwrap().send_update_report(report);
                } else {
                    let _ = sota.send_update_report(&report)
                        .map_err(|err| error!("couldn't send update report: {}", err));
                }
                etx.send(Event::UpdateReportSent);
            }

            Command::StartDownload(ref ids) => {
                for id in ids {
                    etx.send(Event::DownloadingUpdate(id.clone()));
                    if let Some(ref rvi) = self.rvi {
                        let _ = rvi.remote.lock().unwrap().send_download_started(id.clone());
                    } else {
                        let _ = sota.download_update(id.clone())
                            .map(|dl| etx.send(Event::DownloadComplete(dl)))
                            .map_err(|err| etx.send(Event::DownloadFailed(id.clone(), format!("{}", err))));
                    }
                }
            }

            Command::StartInstall(dl) => {
                let _ = sota.install_update(dl)
                    .map(|report| etx.send(Event::InstallComplete(report)))
                    .map_err(|report| etx.send(Event::InstallFailed(report)));
            }

            Command::Shutdown => std::process::exit(0),
        }

        Ok(())
    }

    fn unauthenticated(&mut self, cmd: Command, etx: Sender<Event>) -> Result<(), Error> {
        match cmd {
            Command::Authenticate(_) => {
                let config = self.config.auth.clone().expect("trying to authenticate without auth config");
                self.set_client(Auth::Credentials(ClientId(config.client_id),
                                                  ClientSecret(config.client_secret)));
                let server = config.server.join("/token").expect("couldn't build authentication url");
                let token  = try!(authenticate(server, self.http_client.as_ref()));
                self.set_client(Auth::Token(token.clone()));
                self.token = Some(token.into());
                etx.send(Event::Authenticated);
            }

            Command::GetNewUpdates            |
            Command::ListInstalledPackages    |
            Command::ListSystemInfo           |
            Command::SendInstalledPackages(_) |
            Command::SendInstalledSoftware(_) |
            Command::SendUpdateReport(_)      |
            Command::SendSystemInfo           |
            Command::StartDownload(_)         |
            Command::StartInstall(_)          => etx.send(Event::NotAuthenticated),

            Command::Shutdown => std::process::exit(0),
        }

        Ok(())
    }

    fn set_client(&mut self, auth: Auth) {
        if !self.http_client.is_testing() {
            self.http_client = Box::new(AuthClient::from(auth));
        }
    }
}

#[cfg(test)]
mod tests {
    use chan;
    use chan::{Sender, Receiver};
    use std::thread;

    use super::*;
    use datatype::{AccessToken, Command, Config, DownloadComplete, Event,
                   UpdateReport, UpdateResultCode};
    use gateway::Interpret;
    use http::test_client::TestClient;
    use package_manager::PackageManager;
    use package_manager::tpm::assert_rx;

    fn new_interpreter(replies: Vec<String>, pkg_mgr: PackageManager) -> (Sender<Command>, Receiver<Event>) {
        let (etx, erx) = chan::sync::<Event>(0);
        let (ctx, crx) = chan::sync::<Command>(0);

        thread::spawn(move || {
            let mut gi = GlobalInterpreter {
                config:      Config::default(),
                token:       Some(AccessToken::default().into()),
                http_client: Box::new(TestClient::from(replies)),
                rvi:         None
            };
            gi.config.device.package_manager = pkg_mgr;

            loop {
                match crx.recv() {
                    Some(cmd) => gi.interpret(Interpret { command: cmd, response_tx: None }, &etx),
                    None      => break
                }
            }
        });

        (ctx, erx)
    }

    #[test]
    fn already_authenticated() {
        let replies    = Vec::new();
        let pkg_mgr    = PackageManager::new_tpm(true);
        let (ctx, erx) = new_interpreter(replies, pkg_mgr);

        ctx.send(Command::Authenticate(None));
        assert_rx(erx, &[Event::Authenticated]);
    }

    #[test]
    fn download_updates() {
        let replies    = vec!["[]".to_string(); 10];
        let pkg_mgr    = PackageManager::new_tpm(true);
        let (ctx, erx) = new_interpreter(replies, pkg_mgr);

        ctx.send(Command::StartDownload(vec!["1".to_string(), "2".to_string()]));
        assert_rx(erx, &[
            Event::DownloadingUpdate("1".to_string()),
            Event::DownloadComplete(DownloadComplete {
                update_id:    "1".to_string(),
                update_image: "/tmp/1".to_string(),
                signature:    "".to_string()
            }),
            Event::DownloadingUpdate("2".to_string()),
            Event::DownloadComplete(DownloadComplete {
                update_id:    "2".to_string(),
                update_image: "/tmp/2".to_string(),
                signature:    "".to_string()
            }),
        ]);
    }

    #[test]
    fn install_update_success() {
        let replies    = vec!["[]".to_string(); 10];
        let pkg_mgr    = PackageManager::new_tpm(true);
        let (ctx, erx) = new_interpreter(replies, pkg_mgr);

        ctx.send(Command::StartInstall(DownloadComplete {
            update_id:    "1".to_string(),
            update_image: "/tmp/1".to_string(),
            signature:    "".to_string()
        }));
        assert_rx(erx, &[
            Event::InstallComplete(
                UpdateReport::single("1".to_string(), UpdateResultCode::OK, "".to_string())
            )
        ]);
    }

    #[test]
    fn install_update_failed() {
        let replies    = vec!["[]".to_string(); 10];
        let pkg_mgr    = PackageManager::new_tpm(false);
        let (ctx, erx) = new_interpreter(replies, pkg_mgr);

        ctx.send(Command::StartInstall(DownloadComplete {
            update_id:    "1".to_string(),
            update_image: "/tmp/1".to_string(),
            signature:    "".to_string()
        }));
        assert_rx(erx, &[
            Event::InstallFailed(
                UpdateReport::single("1".to_string(), UpdateResultCode::INSTALL_FAILED, "failed".to_string())
            ),
        ]);
    }
}
<commit_msg>Send InstallingUpdate Event before an install<commit_after>use chan;
use chan::{Sender, Receiver};
use std;
use std::borrow::Cow;

use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config,
               Error, Event, Package, UpdateRequestId};
use gateway::Interpret;
use http::{AuthClient, Client};
use oauth2::authenticate;
use package_manager::PackageManager;
use rvi::Services;
use sota::Sota;

/// An `Interpreter` loops over any incoming values, on receipt of which it
/// delegates to the `interpret` function which will respond with output values.
pub trait Interpreter<I, O> {
    fn interpret(&mut self, input: I, otx: &Sender<O>);

    fn run(&mut self, irx: Receiver<I>, otx: Sender<O>) {
        loop {
            self.interpret(irx.recv().expect("interpreter sender closed"), &otx);
        }
    }
}

/// The `EventInterpreter` listens for `Event`s and optionally responds with
/// `Command`s that may be sent to the `CommandInterpreter`.
pub struct EventInterpreter {
    pub package_manager: PackageManager
}

impl Interpreter<Event, Command> for EventInterpreter {
    fn interpret(&mut self, event: Event, ctx: &Sender<Command>) {
        info!("Event received: {}", event);
        match event {
            Event::Authenticated => {
                ctx.send(Command::SendSystemInfo);

                if self.package_manager != PackageManager::Off {
                    self.package_manager.installed_packages().map(|packages| {
                        ctx.send(Command::SendInstalledPackages(packages));
                    }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err));
                }
            }

            Event::NotAuthenticated => {
                info!("Trying to authenticate again...");
                ctx.send(Command::Authenticate(None));
            }

            Event::NewUpdatesReceived(ids) => {
                ctx.send(Command::StartDownload(ids));
            }

            Event::DownloadComplete(dl) => {
                if self.package_manager != PackageManager::Off {
                    ctx.send(Command::StartInstall(dl));
                }
            }

            Event::InstallComplete(report) => {
                ctx.send(Command::SendUpdateReport(report));
            }

            Event::UpdateReportSent => {
                if self.package_manager != PackageManager::Off {
                    self.package_manager.installed_packages().map(|packages| {
                        ctx.send(Command::SendInstalledPackages(packages));
                    }).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err));
                }
            }

            _ => ()
        }
    }
}

/// The `CommandInterpreter` wraps each incoming `Command` inside an `Interpret`
/// type with no response channel for sending to the `GlobalInterpreter`.
pub struct CommandInterpreter;

impl Interpreter<Command, Interpret> for CommandInterpreter {
    fn interpret(&mut self, cmd: Command, itx: &Sender<Interpret>) {
        info!("Command received: {}", cmd);
        itx.send(Interpret { command: cmd, response_tx: None });
    }
}

/// The `GlobalInterpreter` interprets the `Command` inside incoming `Interpret`
/// messages, broadcasting `Event`s globally and (optionally) sending the final
/// outcome `Event` to the `Interpret` response channel.
pub struct GlobalInterpreter<'t> {
    pub config:      Config,
    pub token:       Option<Cow<'t, AccessToken>>,
    pub http_client: Box<Client>,
    pub rvi:         Option<Services>
}

impl<'t> Interpreter<Interpret, Event> for GlobalInterpreter<'t> {
    fn interpret(&mut self, interpret: Interpret, etx: &Sender<Event>) {
        info!("Interpreter started: {}", interpret.command);

        let (multi_tx, multi_rx) = chan::async::<Event>();
        let outcome = match (self.token.as_ref(), self.config.auth.is_none()) {
            (Some(_), _) | (_, true) => self.authenticated(interpret.command, multi_tx),
            _                        => self.unauthenticated(interpret.command, multi_tx)
        };

        let mut response_ev: Option<Event> = None;
        match outcome {
            Ok(_) => {
                for ev in multi_rx {
                    etx.send(ev.clone());
                    response_ev = Some(ev);
                }
                info!("Interpreter finished.");
            }

            Err(Error::Authorization(_)) => {
                let ev = Event::NotAuthenticated;
                etx.send(ev.clone());
                response_ev = Some(ev);
                error!("Interpreter authentication failed");
            }

            Err(err) => {
                let ev = Event::Error(format!("{}", err));
                etx.send(ev.clone());
                response_ev = Some(ev);
                error!("Interpreter failed: {}", err);
            }
        }

        let ev = response_ev.expect("no response event to send back");
        interpret.response_tx.map(|tx| tx.lock().unwrap().send(ev));
    }
}

impl<'t> GlobalInterpreter<'t> {
    fn authenticated(&self, cmd: Command, etx: Sender<Event>) -> Result<(), Error> {
        let mut sota = Sota::new(&self.config, self.http_client.as_ref());

        // always send at least one Event response
        match cmd {
            Command::Authenticate(_) => etx.send(Event::Authenticated),

            Command::GetNewUpdates => {
                let mut updates = try!(sota.get_pending_updates());
                if updates.is_empty() {
                    etx.send(Event::NoNewUpdates);
                } else {
                    updates.sort_by_key(|u| u.installPos);
                    let ids = updates.iter().map(|u| u.requestId.clone()).collect::<Vec<UpdateRequestId>>();
                    etx.send(Event::NewUpdatesReceived(ids))
                }
            }

            Command::ListInstalledPackages => {
                let mut packages: Vec<Package> = Vec::new();
                if self.config.device.package_manager != PackageManager::Off {
                    packages = try!(self.config.device.package_manager.installed_packages());
                }
                etx.send(Event::FoundInstalledPackages(packages));
            }

            Command::ListSystemInfo => {
                let info = try!(self.config.device.system_info.report());
                etx.send(Event::FoundSystemInfo(info));
            }

            Command::SendInstalledPackages(packages) => {
                let _ = sota.send_installed_packages(&packages)
                    .map_err(|err| error!("couldn't send installed packages: {}", err));
                etx.send(Event::InstalledPackagesSent);
            }

            Command::SendInstalledSoftware(sw) => {
                if let Some(ref rvi) = self.rvi {
                    let _ = rvi.remote.lock().unwrap().send_installed_software(sw);
                }
                etx.send(Event::InstalledSoftwareSent);
            }

            Command::SendSystemInfo => {
                let info = try!(self.config.device.system_info.report());
                let _ = sota.send_system_info(&info)
                    .map_err(|err| error!("couldn't send system info: {}", err));
                etx.send(Event::SystemInfoSent);
            }

            Command::SendUpdateReport(report) => {
                if let Some(ref rvi) = self.rvi {
                    let _ = rvi.remote.lock().unwrap().send_update_report(report);
                } else {
                    let _ = sota.send_update_report(&report)
                        .map_err(|err| error!("couldn't send update report: {}", err));
                }
                etx.send(Event::UpdateReportSent);
            }

            Command::StartDownload(ref ids) => {
                for id in ids {
                    etx.send(Event::DownloadingUpdate(id.clone()));
                    if let Some(ref rvi) = self.rvi {
                        let _ = rvi.remote.lock().unwrap().send_download_started(id.clone());
                    } else {
                        let _ = sota.download_update(id.clone())
                            .map(|dl| etx.send(Event::DownloadComplete(dl)))
                            .map_err(|err| etx.send(Event::DownloadFailed(id.clone(), format!("{}", err))));
                    }
                }
            }

            Command::StartInstall(dl) => {
                etx.send(Event::InstallingUpdate(dl.update_id.clone()));
                let _ = sota.install_update(dl)
                    .map(|report| etx.send(Event::InstallComplete(report)))
                    .map_err(|report| etx.send(Event::InstallFailed(report)));
            }

            Command::Shutdown => std::process::exit(0),
        }

        Ok(())
    }

    fn unauthenticated(&mut self, cmd: Command, etx: Sender<Event>) -> Result<(), Error> {
        match cmd {
            Command::Authenticate(_) => {
                let config = self.config.auth.clone().expect("trying to authenticate without auth config");
                self.set_client(Auth::Credentials(ClientId(config.client_id),
                                                  ClientSecret(config.client_secret)));
                let server = config.server.join("/token").expect("couldn't build authentication url");
                let token  = try!(authenticate(server, self.http_client.as_ref()));
                self.set_client(Auth::Token(token.clone()));
                self.token = Some(token.into());
                etx.send(Event::Authenticated);
            }

            Command::GetNewUpdates            |
            Command::ListInstalledPackages    |
            Command::ListSystemInfo           |
            Command::SendInstalledPackages(_) |
            Command::SendInstalledSoftware(_) |
            Command::SendUpdateReport(_)      |
            Command::SendSystemInfo           |
            Command::StartDownload(_)         |
            Command::StartInstall(_)          => etx.send(Event::NotAuthenticated),

            Command::Shutdown => std::process::exit(0),
        }

        Ok(())
    }

    fn set_client(&mut self, auth: Auth) {
        if !self.http_client.is_testing() {
            self.http_client = Box::new(AuthClient::from(auth));
        }
    }
}

#[cfg(test)]
mod tests {
    use chan;
    use chan::{Sender, Receiver};
    use std::thread;

    use super::*;
    use datatype::{AccessToken, Command, Config, DownloadComplete, Event,
                   UpdateReport, UpdateResultCode};
    use gateway::Interpret;
    use http::test_client::TestClient;
    use package_manager::PackageManager;
    use package_manager::tpm::assert_rx;

    fn new_interpreter(replies: Vec<String>, pkg_mgr: PackageManager) -> (Sender<Command>, Receiver<Event>) {
        let (etx, erx) = chan::sync::<Event>(0);
        let (ctx, crx) = chan::sync::<Command>(0);

        thread::spawn(move || {
            let mut gi = GlobalInterpreter {
                config:      Config::default(),
                token:       Some(AccessToken::default().into()),
                http_client: Box::new(TestClient::from(replies)),
                rvi:         None
            };
            gi.config.device.package_manager = pkg_mgr;

            loop {
                match crx.recv() {
                    Some(cmd) => gi.interpret(Interpret { command: cmd, response_tx: None }, &etx),
                    None      => break
                }
            }
        });

        (ctx, erx)
    }

    #[test]
    fn already_authenticated() {
        let replies    = Vec::new();
        let pkg_mgr    = PackageManager::new_tpm(true);
        let (ctx, erx) = new_interpreter(replies, pkg_mgr);

        ctx.send(Command::Authenticate(None));
        assert_rx(erx, &[Event::Authenticated]);
    }

    #[test]
    fn download_updates() {
        let replies    = vec!["[]".to_string(); 10];
        let pkg_mgr    = PackageManager::new_tpm(true);
        let (ctx, erx) = new_interpreter(replies, pkg_mgr);

        ctx.send(Command::StartDownload(vec!["1".to_string(), "2".to_string()]));
        assert_rx(erx, &[
            Event::DownloadingUpdate("1".to_string()),
            Event::DownloadComplete(DownloadComplete {
                update_id:    "1".to_string(),
                update_image: "/tmp/1".to_string(),
                signature:    "".to_string()
            }),
            Event::DownloadingUpdate("2".to_string()),
            Event::DownloadComplete(DownloadComplete {
                update_id:    "2".to_string(),
                update_image: "/tmp/2".to_string(),
                signature:    "".to_string()
            }),
        ]);
    }

    #[test]
    fn install_update_success() {
        let replies    = vec!["[]".to_string(); 10];
        let pkg_mgr    = PackageManager::new_tpm(true);
        let (ctx, erx) = new_interpreter(replies, pkg_mgr);

        ctx.send(Command::StartInstall(DownloadComplete {
            update_id:    "1".to_string(),
            update_image: "/tmp/1".to_string(),
            signature:    "".to_string()
        }));
        assert_rx(erx, &[
            Event::InstallingUpdate("1".to_string()),
            Event::InstallComplete(
                UpdateReport::single("1".to_string(), UpdateResultCode::OK, "".to_string())
            )
        ]);
    }

    #[test]
    fn install_update_failed() {
        let replies    = vec!["[]".to_string(); 10];
        let pkg_mgr    = PackageManager::new_tpm(false);
        let (ctx, erx) = new_interpreter(replies, pkg_mgr);

        ctx.send(Command::StartInstall(DownloadComplete {
            update_id:    "1".to_string(),
            update_image: "/tmp/1".to_string(),
            signature:    "".to_string()
        }));
        assert_rx(erx, &[
            Event::InstallingUpdate("1".to_string()),
            Event::InstallFailed(
                UpdateReport::single("1".to_string(), UpdateResultCode::INSTALL_FAILED, "failed".to_string())
            )
        ]);
    }
}
<|endoftext|>


<commit_before>use parser::parser::{parse};
use expr::Expr;
use expr::Expr::*;
use expr::Val::*;
use expr::UnOp::*;
use expr::BinOp::*;
use expr::Dec::*;
use state::State;
use runtime_error::RuntimeError;
use std::result;

pub type Result<T> = result::Result<T, RuntimeError>;

pub struct Interpreter {
  pub state: State,
}

impl Interpreter {
  pub fn new() -> Interpreter {
    Interpreter {
      state: State::new(),
    }
  }

  pub fn step(&mut self, e: Expr) -> Result<Expr> {
    debug!("step(e) : {:?}", e);
    debug!("step(self.state) : {:?}", self.state.mem);

    let e1 = match e {
      Var(x) => {
        match self.state.get(x.clone()) {
          Some(e) => e,
          None => return Err(RuntimeError::VariableNotFound(x)),
        }
      },
      /**
       * Values are ineligible for step
       */
      Val(_) => {
        debug!("stepping on a value {:?}", e);
        return Err(RuntimeError::SteppingOnValue(e));
      },
      /**
       * Base cases
       */
      Uop(Not, box Val(Bool(b))) => {
        Val(Bool(!b))
      },
      Uop(Neg, box Val(Int(n))) => {
        Val(Int(-1 * n))
      },
      Bop(And, box Val(Bool(b1)), box Val(Bool(b2))) => {
        Val(Bool(b1 && b2))
      },
      Bop(Or, box Val(Bool(b1)), box Val(Bool(b2))) => {
        Val(Bool(b1 || b2))
      },
      Bop(Eq, ref e1, ref e2) if e1.is_value() && e2.is_value() => {
        Val(Bool(*e1 == *e2))
      },
      Bop(Ne, ref e1, ref e2) if e1.is_value() && e2.is_value() => {
        Val(Bool(*e1 != *e2))
      },
      Bop(Mod, box Val(Int(n1)), box Val(Int(n2))) => {
        // rust % gives the remainder, not modulus
        // let result = ((n1 % n2) + n2) % n2;
        let result = n1.checked_rem(n2)
          .ok_or(RuntimeError::IntegerUnderflow)?
          .checked_add(n2)
          .ok_or(RuntimeError::IntegerOverflow)?
          .checked_rem(n2)
          .ok_or(RuntimeError::IntegerUnderflow)?;

        Val(Int(result))
      },
      Bop(Lt, box Val(Int(n1)), box Val(Int(n2))) => {
        Val(Bool(n1 < n2))
      },
      Bop(Gt, box Val(Int(n1)), box Val(Int(n2))) => {
        Val(Bool(n1 > n2))
      },
      Bop(Leq, box Val(Int(n1)), box Val(Int(n2))) => {
        Val(Bool(n1 <= n2))
      },
      Bop(Geq, box Val(Int(n1)), box Val(Int(n2))) => {
        Val(Bool(n1 >= n2))
      },
      Bop(Plus, box Val(Int(n1)), box Val(Int(n2))) => {
        match n1.checked_add(n2) {
          Some(n) => Val(Int(n)),
          None => return Err(RuntimeError::IntegerOverflow),
        }
      },
      Bop(Minus, box Val(Int(n1)), box Val(Int(n2))) => {
        match n1.checked_sub(n2) {
          Some(n) => Val(Int(n)),
          None => return Err(RuntimeError::IntegerUnderflow),
        }
      },
      Bop(Times, box Val(Int(n1)), box Val(Int(n2))) => {
        match n1.checked_mul(n2) {
          Some(n) => Val(Int(n)),
          None => return Err(RuntimeError::IntegerOverflow),
        }
      },
      Bop(Div, box Val(Int(n1)), box Val(Int(n2))) => {
        match n1.checked_div(n2) {
          Some(n) => Val(Int(n)),
          None => return Err(RuntimeError::IntegerUnderflow),
        }
      },
      Bop(Seq, box Val(_), box e1) => {
        e1
      },
      Bop(Assign, box Var(x), box Val(v)) => {
        self.state.assign(x, Val(v.clone()))?;
        debug!("done assigning {:?}", self.state.mem);
        Val(v)
      },
      Ternary(box Val(Bool(b)), box e1, box e2) => {
        match b {
          true => e1,
          false => e2,
        }
      },
      Decl(DConst, box Var(x), box Val(v1), box e1) => {
        self.state.alloc_const(x, Val(v1))?;
        e1
      },
      Decl(DVar, box Var(x), box Val(v1), box e1) => {
        debug!("allocing {:?}", v1);
        self.state.alloc(x, Val(v1))?;
        e1
      },
      // lambda lift so we can use iter() in guard
      // https://github.com/rust-lang/rfcs/issues/1006
      FnCall(ref v1, ref es) if v1.is_func() && (|| es.iter().all(|v| v.is_value()))() => {
        match **v1 {
          Val(Func(ref name, ref e1, ref xs)) => {
            self.state.begin_scope();

            // alloc the params
            for (xn, en) in xs.iter().zip(es.iter()) {
              self.state.alloc(xn.to_var()?, en.clone())?;
            }

            // alloc the fn body for named functions
            match *name {
              Some(ref s) => self.state.alloc(s.to_var()?, *v1.clone())?,
              _ => {},
            };

            Scope(Box::new(*e1.clone()))
          },
          _ => return Err(RuntimeError::UnexpectedExpr("expected Func".to_string(), *v1.clone()))
        }
      },
      Scope(box Val(v)) => {
        self.state.end_scope();
        Val(v)
      },
      While(box Val(Bool(b)), e1o, _, e2o, e3) => {
        match b {
          true => While(e1o.clone(), e1o, e2o.clone(), e2o, e3),
          false => *e3,
        }
      },
      Print(box Val(v)) => {
        println!("{}", v);
        Val(Undefined)
      },
      /**
       * Search Cases
       */
      Bop(op, box Val(v1), box e2) => {
        Bop(
          op,
          Box::new(Val(v1)),
          Box::new(self.step(e2)?)
        )
      },
      Bop(Assign, box Var(v1), box e2) => {
        Bop(
          Assign,
          Box::new(Var(v1)),
          Box::new(self.step(e2)?)
        )
      },
      Bop(op, box e1, e2) => {
        Bop(op, Box::new(self.step(e1)?), e2)
      },
      Uop(op, box e1) => {
        Uop(op, Box::new(self.step(e1)?))
      },
      Ternary(box e1, e2, e3) => {
        Ternary(Box::new(self.step(e1)?), e2, e3)
      },
      While(ref e1, ref e1o, ref v2, ref e2o, ref e3) if v2.is_value() => {
        While(Box::new(self.step(*e1.clone())?), e1o.clone(), v2.clone(), e2o.clone(), e3.clone())
      },
      While(e1, e1o, e2, e2o, e3) => {
        While(e1, e1o, Box::new(self.step(*e2)?), e2o, e3)
      },
      Decl(dt, addr, e1, e2) => {
        Decl(dt, Box::new(*addr.clone()), Box::new(self.step(*e1)?), e2)
      },
      FnCall(ref v1, ref args) if v1.is_func() => {
        let mut found_nonvalue = false;

        let stepped_args: Result<Vec<Expr>> = args.iter().map(|e| {
          if !found_nonvalue && !e.is_value() {
            found_nonvalue = true;
            self.step(e.clone())
          } else {
            Ok(e.clone())
          }
        }).collect();

        FnCall(v1.clone(), stepped_args?)
      },
      FnCall(e1, args) => {
        FnCall(Box::new(self.step(*e1)?), args)
      },
      Scope(e1) => {
        Scope(Box::new(self.step(*e1)?))
      },
      Print(e1) => {
        Print(Box::new(self.step(*e1)?))
      },
    };

    debug!("returning with mem {:?}" , self.state.mem);
    debug!("returning with e {:?}" , e1);
    Ok(e1)
  }

  pub fn eval(&mut self, input: &str) -> Result<Expr> {
    let mut e = parse(input)?;

    let mut num_iterations = 0;

    loop {
      if num_iterations > 1000000000 {
        return Err(RuntimeError::TooManyIterations(num_iterations))
      }

      debug!("-----------------");
      debug!("--- iterating on e {:?} ", e);
      debug!("--- iterating on m {:?} ", self.state.mem);
      num_iterations += 1;
      if e.is_value() {
        debug!("--- iterations: {}", num_iterations);
        return Ok(e.clone());
      } else {
        e = self.step(e.clone())?;
      }
    }
  }
}
<commit_msg>more removing refs from pattern matching<commit_after>use parser::parser::{parse};
use expr::Expr;
use expr::Expr::*;
use expr::Val::*;
use expr::UnOp::*;
use expr::BinOp::*;
use expr::Dec::*;
use state::State;
use runtime_error::RuntimeError;
use std::result;

pub type Result<T> = result::Result<T, RuntimeError>;

pub struct Interpreter {
  pub state: State,
}

impl Interpreter {
  pub fn new() -> Interpreter {
    Interpreter {
      state: State::new(),
    }
  }

  pub fn step(&mut self, e: Expr) -> Result<Expr> {
    debug!("step(e) : {:?}", e);
    debug!("step(self.state) : {:?}", self.state.mem);

    let e1 = match e {
      Var(x) => {
        match self.state.get(x.clone()) {
          Some(e) => e,
          None => return Err(RuntimeError::VariableNotFound(x)),
        }
      },
      /**
       * Values are ineligible for step
       */
      Val(_) => {
        debug!("stepping on a value {:?}", e);
        return Err(RuntimeError::SteppingOnValue(e));
      },
      /**
       * Base cases
       */
      Uop(Not, box Val(Bool(b))) => {
        Val(Bool(!b))
      },
      Uop(Neg, box Val(Int(n))) => {
        Val(Int(-1 * n))
      },
      Bop(And, box Val(Bool(b1)), box Val(Bool(b2))) => {
        Val(Bool(b1 && b2))
      },
      Bop(Or, box Val(Bool(b1)), box Val(Bool(b2))) => {
        Val(Bool(b1 || b2))
      },
      Bop(Eq, ref e1, ref e2) if e1.is_value() && e2.is_value() => {
        Val(Bool(*e1 == *e2))
      },
      Bop(Ne, ref e1, ref e2) if e1.is_value() && e2.is_value() => {
        Val(Bool(*e1 != *e2))
      },
      Bop(Mod, box Val(Int(n1)), box Val(Int(n2))) => {
        // rust % gives the remainder, not modulus
        // let result = ((n1 % n2) + n2) % n2;
        let result = n1.checked_rem(n2)
          .ok_or(RuntimeError::IntegerUnderflow)?
          .checked_add(n2)
          .ok_or(RuntimeError::IntegerOverflow)?
          .checked_rem(n2)
          .ok_or(RuntimeError::IntegerUnderflow)?;

        Val(Int(result))
      },
      Bop(Lt, box Val(Int(n1)), box Val(Int(n2))) => {
        Val(Bool(n1 < n2))
      },
      Bop(Gt, box Val(Int(n1)), box Val(Int(n2))) => {
        Val(Bool(n1 > n2))
      },
      Bop(Leq, box Val(Int(n1)), box Val(Int(n2))) => {
        Val(Bool(n1 <= n2))
      },
      Bop(Geq, box Val(Int(n1)), box Val(Int(n2))) => {
        Val(Bool(n1 >= n2))
      },
      Bop(Plus, box Val(Int(n1)), box Val(Int(n2))) => {
        match n1.checked_add(n2) {
          Some(n) => Val(Int(n)),
          None => return Err(RuntimeError::IntegerOverflow),
        }
      },
      Bop(Minus, box Val(Int(n1)), box Val(Int(n2))) => {
        match n1.checked_sub(n2) {
          Some(n) => Val(Int(n)),
          None => return Err(RuntimeError::IntegerUnderflow),
        }
      },
      Bop(Times, box Val(Int(n1)), box Val(Int(n2))) => {
        match n1.checked_mul(n2) {
          Some(n) => Val(Int(n)),
          None => return Err(RuntimeError::IntegerOverflow),
        }
      },
      Bop(Div, box Val(Int(n1)), box Val(Int(n2))) => {
        match n1.checked_div(n2) {
          Some(n) => Val(Int(n)),
          None => return Err(RuntimeError::IntegerUnderflow),
        }
      },
      Bop(Seq, box Val(_), box e1) => {
        e1
      },
      Bop(Assign, box Var(x), box Val(v)) => {
        self.state.assign(x, Val(v.clone()))?;
        debug!("done assigning {:?}", self.state.mem);
        Val(v)
      },
      Ternary(box Val(Bool(b)), box e1, box e2) => {
        match b {
          true => e1,
          false => e2,
        }
      },
      Decl(DConst, box Var(x), box Val(v1), box e1) => {
        self.state.alloc_const(x, Val(v1))?;
        e1
      },
      Decl(DVar, box Var(x), box Val(v1), box e1) => {
        debug!("allocing {:?}", v1);
        self.state.alloc(x, Val(v1))?;
        e1
      },
      // lambda lift so we can use iter() in guard
      // https://github.com/rust-lang/rfcs/issues/1006
      FnCall(ref v1, ref es) if v1.is_func() && (|| es.iter().all(|v| v.is_value()))() => {
        match **v1 {
          Val(Func(ref name, ref e1, ref xs)) => {
            self.state.begin_scope();

            // alloc the params
            for (xn, en) in xs.iter().zip(es.iter()) {
              self.state.alloc(xn.to_var()?, en.clone())?;
            }

            // alloc the fn body for named functions
            match *name {
              Some(ref s) => self.state.alloc(s.to_var()?, *v1.clone())?,
              _ => {},
            };

            Scope(Box::new(*e1.clone()))
          },
          _ => return Err(RuntimeError::UnexpectedExpr("expected Func".to_string(), *v1.clone()))
        }
      },
      Scope(box Val(v)) => {
        self.state.end_scope();
        Val(v)
      },
      While(box Val(Bool(b)), e1o, _, e2o, e3) => {
        match b {
          true => While(e1o.clone(), e1o, e2o.clone(), e2o, e3),
          false => *e3,
        }
      },
      Print(box Val(v)) => {
        println!("{}", v);
        Val(Undefined)
      },
      /**
       * Search Cases
       */
      Bop(op, box Val(v1), box e2) => {
        Bop(
          op,
          Box::new(Val(v1)),
          Box::new(self.step(e2)?)
        )
      },
      Bop(Assign, box Var(v1), box e2) => {
        Bop(
          Assign,
          Box::new(Var(v1)),
          Box::new(self.step(e2)?)
        )
      },
      Bop(op, box e1, e2) => {
        Bop(op, Box::new(self.step(e1)?), e2)
      },
      Uop(op, box e1) => {
        Uop(op, Box::new(self.step(e1)?))
      },
      Ternary(box e1, e2, e3) => {
        Ternary(Box::new(self.step(e1)?), e2, e3)
      },
      While(box e1, e1o, box Val(v), e2o, e3) => {
        While(Box::new(self.step(e1)?), e1o, Box::new(Val(v)), e2o, e3)
      },
      While(e1, e1o, box e2, e2o, e3) => {
        While(e1, e1o, Box::new(self.step(e2)?), e2o, e3)
      },
      Decl(dt, box addr, box e1, e2) => {
        Decl(dt, Box::new(addr.clone()), Box::new(self.step(e1)?), e2)
      },
      FnCall(f @ box Val(Func(_, _, _)), args) => {
        let mut found_nonvalue = false;

        let stepped_args: Result<Vec<Expr>> = args.iter().map(|e| {
          if !found_nonvalue && !e.is_value() {
            found_nonvalue = true;
            self.step(e.clone())
          } else {
            Ok(e.clone())
          }
        }).collect();

        FnCall(f, stepped_args?)
      },
      FnCall(box e1, args) => {
        FnCall(Box::new(self.step(e1)?), args)
      },
      Scope(box e1) => {
        Scope(Box::new(self.step(e1)?))
      },
      Print(box e1) => {
        Print(Box::new(self.step(e1)?))
      },
    };

    debug!("returning with mem {:?}" , self.state.mem);
    debug!("returning with e {:?}" , e1);
    Ok(e1)
  }

  pub fn eval(&mut self, input: &str) -> Result<Expr> {
    let mut e = parse(input)?;

    let mut num_iterations = 0;

    loop {
      if num_iterations > 1000000000 {
        return Err(RuntimeError::TooManyIterations(num_iterations))
      }

      debug!("-----------------");
      debug!("--- iterating on e {:?} ", e);
      debug!("--- iterating on m {:?} ", self.state.mem);
      num_iterations += 1;
      if e.is_value() {
        debug!("--- iterations: {}", num_iterations);
        return Ok(e.clone());
      } else {
        e = self.step(e.clone())?;
      }
    }
  }
}
<|endoftext|>


<commit_before>use header::*;
use constants::*;
use instructions::Instruction as Instr;
use num::FromPrimitive;
use std::fmt;

#[derive(Debug, Default)]
pub struct Interpreter {
    // File data
    header: RaptorHeader,
    bytecode: Vec<u8>,
    const_table: ConstTable,

    // Rutime stuff
    op_stack: Vec<i32>,
    memory: Vec<i32>,
    program_counter: usize,
    call_stack: Vec<StackFrame>
}

// Should this be here?

#[derive(Debug, Default)]
pub struct StackFrame {
    id: u32,
    locals: Vec<i32>,
    // The index of the first op in the op_stack that should be kept
    return_addr: usize
}

impl Interpreter {
    pub fn new(mut data: Vec<u8>) -> Interpreter {
        debug!("Bytecode length: {} bytes", data.len());
        let header = read_header(&data);
        data.drain(..HEADER_SIZE);
        let const_table: ConstTable = read_const_table(data.as_slice());
        debug!("Constant table length: {} bytes", const_table.bc_counter);
        debug!("Bytecode length: {} bytes", data.len());
        data.drain(..const_table.bc_counter);
        let mut i = Interpreter {
            header: header,
            const_table: const_table,
            bytecode: data,
            op_stack: Vec::new(),
            memory: Vec::new(),
            program_counter: 0,
            call_stack: Vec::new()
        };
        i.memory.resize(i.header.var_count as usize, 0);
        i
    }

    pub fn run(&mut self, options: &::Options) {
        use std::ops::*;
        use std::cmp::*;

        debug!("Running...");
        debug!("Bytecode: {:?}", self.bytecode);

        let debug = options.debug;

        // Main loop
        // Will break when .next() is None
        while self.program_counter != self.bytecode.len() {
            // info!("PC: {}", program_counter);

            // Use FromPrimitive trait to convert a value to its enum
            let instr = Instr::from_u8(self.bytecode[self.program_counter]);
            self.program_counter += 1;

            if instr.is_none() {
                warn!("Unimplemented instruction: {:04X}", self.bytecode[self.program_counter]);
                continue;
            }

            let instr = instr.unwrap();   // We're sure it's Some here, so unpack it.

            if options.debug {
                debug!("{:?}", instr);
            }

            macro_rules! push {
                ( $x:expr ) => {
                    self.op_stack.push($x);
                };
            }
            macro_rules! pop {
                () => {
                    self.op_stack.pop();
                };
            }
            macro_rules! operation {
                ($op:ident) => ({
                    let final_length: usize = self.op_stack.len().saturating_sub(2);
                    let val = self.op_stack.drain(final_length..).fold(
                        0, |acc, x| acc.$op(x)
                    );
                    push!(val);
                })
            }
            macro_rules! reljump {
                ($op:ident) => ({
                    let top = pop!().unwrap();
                    if top.$op(&0) {
                        reljump!();
                    } else {
                        self.get_next_4_bytes();
                        if debug {debug!("Jump not taken"); }
                    }
                });
                () => ({
                    let offset = (self.get_next_4_bytes() - 1) as i32;
                    // Need this if because you can't have negative usizes
                    if offset > 0 {
                        if debug {debug!("RELJUMP: {}", offset);}
                        self.program_counter += offset as usize;
                        if offset == 1 {debug!("RELJUMP 1 is redundant. This is a compiler bug")}
                    } else if offset < 0 {
                        if debug {debug!("RELJUMP: {}", offset);}
                        self.program_counter -= (-offset) as usize;
                    } else {
                        warn!("Invalid reljump offset: 0");
                    }
                });
            }

            macro_rules! push_frame {
                ($id:expr) => ({
                    let func_const = &self.const_table.funcs[$id as usize];
                    let mut sf = StackFrame {
                        id: $id,
                        locals: Vec::new(),
                        return_addr: 0
                    };
                    for _ in 0..func_const.arg_count {
                        sf.locals.push(pop!().unwrap());
                    }
                    sf.return_addr = self.op_stack.len();
                    sf.locals.resize((func_const.arg_count + func_const.local_count) as usize, 0);
                    debug!("Pushed new frame: {}    {:?}", $id, sf);
                    debug!("Op stack: {:?}", self.op_stack);
                    self.call_stack.push(sf);
                });
            }
            // TODO: More macros, less code

            match instr {
                Instr::NOP => {},
                Instr::HALT => {
                    println!("HALT issued, stopped execution.");
                    if debug {
                        debug!("Stack: {:?}", self.op_stack);
                        debug!("Memory: {:?}", self.memory);
                    }
                },
                Instr::ICONST => {
                    let b = self.get_next_4_bytes() as i32;
                    push!(b);
                },
                Instr::POP => { pop!(); },
                Instr::ADD =>       { operation!(add);    },
                Instr::SUB =>       { operation!(sub);    },
                Instr::MULTIPLY =>  { operation!(mul);    },
                Instr::DIVIDE =>    { operation!(div);    },
                Instr::MODULUS =>   { operation!(rem);    },
                Instr::RSHIFT =>    { operation!(shl);    },
                Instr::LSHIFT =>    { operation!(shr);    },
                Instr::AND =>       { operation!(bitand); },
                Instr::OR =>        { operation!(bitor);  },
                Instr::NOT =>       {
                    let val = pop!().unwrap();
                    push!(val.not());
                },
                Instr::COMP => {
                    let a = pop!(); let b = pop!();
                    push!(if a > b {1} else if a < b {-1} else {0});
                },
                Instr::COMP_LT => {
                    let a = pop!(); let b = pop!();
                    push!(if a < b {1} else {0});
                },
                Instr::COMP_EQ => {
                    let a = pop!(); let b = pop!();
                    push!(if a == b {1} else {0});
                },
                Instr::COMP_GT => {
                    let a = pop!(); let b = pop!();
                    push!(if a > b {1} else {0});
                },
                Instr::RELJUMP => {reljump!();},
                Instr::RELJUMP_GT => {reljump!(gt);},
                Instr::RELJUMP_LT => {reljump!(lt);},
                Instr::RELJUMP_EQ => {reljump!(eq);},
                Instr::STORE => {
                    let index = self.get_next_4_bytes() as usize;
                    self.memory[index] = pop!().unwrap();
                },
                Instr::LOAD => {
                    let index = self.get_next_4_bytes() as usize;
                    push!(self.memory[index]);
                },
                Instr::CALL => {
                    let id: u32 = self.get_next_4_bytes();
                    push_frame!(id);
                },
                Instr::RETURN => {}
                Instr::PRINT => {
                    println!("PRINT: {}", pop!().unwrap());
                },
                Instr::DUMP_STACK => {
                    println!("{:?}", self.op_stack);
                },
                Instr::DUMP_GLOBALS => {
                    println!("{:?}", self.memory);},
            }
        }
    }

    fn get_next_4_bytes(&mut self) -> u32 {
        let val = (self.bytecode[self.program_counter] as u32) << 24 |
                  (self.bytecode[self.program_counter + 1] as u32) << 16 |
                  (self.bytecode[self.program_counter + 2] as u32) << 8 |
                  (self.bytecode[self.program_counter + 3] as u32);
        self.program_counter += 4;
        debug!("get_next_4_bytes: 0x{:04X}", val);
        val
    }
}

// TODO: Add tests
<commit_msg>WIP - doesnt work currently<commit_after>use header::*;
use constants::*;
use instructions::Instruction as Instr;
use num::FromPrimitive;
use std::fmt;

#[derive(Debug, Default)]
pub struct Interpreter {
    // File data
    header: RaptorHeader,
    const_table: ConstTable,

    // Rutime stuff
    op_stack: Vec<i32>,
    memory: Vec<i32>,
    call_stack: Vec<StackFrame>,
}

// Should this be here?

#[derive(Debug, Default)]
pub struct StackFrame {
    locals: Vec<i32>,
    // The index of the first op in the op_stack that should be kept
    return_addr: usize,
    bytecode: Vec<u8>,
    bc_counter: usize
}

impl Interpreter {
    pub fn new(mut data: Vec<u8>) -> Interpreter {
        debug!("Bytecode length: {} bytes", data.len());
        let header = read_header(&data);
        data.drain(..HEADER_SIZE);
        let const_table: ConstTable = read_const_table(data.as_slice());
        debug!("Constant table length: {} bytes", const_table.bc_counter);
        debug!("Bytecode length: {} bytes", data.len());
        data.drain(..const_table.bc_counter);
        let sf = StackFrame {
            bytecode: data,
            ..Default::default()
        };
        let mut i = Interpreter {
            header: header,
            const_table: const_table,
            op_stack: Vec::new(),
            memory: Vec::new(),
            call_stack: Vec::new(),
        };
        i.memory.resize(i.header.var_count as usize, 0);
        i
    }

    pub fn run(&mut self, options: &::Options) {
        debug!("Running...");

        let debug = options.debug;

        loop {
            self.call_stack.last().unwrap().dispatch(self, debug)
        }
    }
}

impl StackFrame {

    fn get_next_4_bytes(&mut self) -> u32 {
        let val = (self.bytecode[self.bc_counter] as u32) << 24 |
        (self.bytecode[self.bc_counter + 1] as u32) << 16 |
        (self.bytecode[self.bc_counter + 2] as u32) << 8 |
        (self.bytecode[self.bc_counter + 3] as u32);
        self.bc_counter += 4;
        debug!("get_next_4_bytes: 0x{:04X}", val);
        val
    }

    fn dispatch(&mut self, mut inpr: Interpreter, debug: bool) {
        use std::ops::*;
        use std::cmp::*;

        // Main loop
        while self.bc_counter != self.bytecode.len() {
            // info!("PC: {}", bc_counter);

            // Use FromPrimitive trait to convert a value to its enum
            let instr = Instr::from_u8(self.bytecode[self.bc_counter]);
            self.bc_counter += 1;

            if instr.is_none() {
                warn!("Unimplemented instruction: {:04X}", self.bytecode[self.bc_counter]);
                continue;
            }

            let instr = instr.unwrap();   // We're sure it's Some here, so unpack it.

            if debug {
                debug!("{:?}", instr);
            }

            macro_rules! push {
                ( $x:expr ) => {
                    inpr.op_stack.push($x);
                };
            }
            macro_rules! pop {
                () => {
                    inpr.op_stack.pop();
                };
            }
            macro_rules! operation {
                ($op:ident) => ({
                    let final_length: usize = inpr.op_stack.len().saturating_sub(2);
                    let val = inpr.op_stack.drain(final_length..).fold(
                        0, |acc, x| acc.$op(x)
                    );
                    push!(val);
                })
            }
            macro_rules! reljump {
                ($op:ident) => ({
                    let top = pop!().unwrap();
                    if top.$op(&0) {
                        reljump!();
                    } else {
                        self.get_next_4_bytes();
                        if debug {debug!("Jump not taken"); }
                    }
                });
                () => ({
                    let offset = (self.get_next_4_bytes() - 1) as i32;
                    // Need this if because you can't have negative usizes
                    if offset > 0 {
                        if debug {debug!("RELJUMP: {}", offset);}
                        self.bc_counter += offset as usize;
                        if offset == 1 {debug!("RELJUMP 1 is redundant. This is a compiler bug")}
                    } else if offset < 0 {
                        if debug {debug!("RELJUMP: {}", offset);}
                        self.bc_counter -= (-offset) as usize;
                    } else {
                        warn!("Invalid reljump offset: 0");
                    }
                });
            }

            macro_rules! push_frame {
                ($id:expr) => ({
                    let func_const = &inpr.const_table.funcs[$id as usize];
                    let mut sf = StackFrame {
                        locals: Vec::new(),
                        ..Default::default()
                    };
                    for _ in 0..func_const.arg_count {
                        sf.locals.push(pop!().unwrap());
                    }
                    sf.return_addr = inpr.op_stack.len();
                    sf.locals.resize((func_const.arg_count + func_const.local_count) as usize, 0);
                    debug!("Pushed new frame: {}    {:?}", $id, sf);
                    debug!("Op stack: {:?}", inpr.op_stack);
                    inpr.call_stack.push(sf);
                });
            }
            // TODO: More macros, less code

            match instr {
                Instr::NOP => {},
                Instr::HALT => {
                    println!("HALT issued, stopped execution.");
                    if debug {
                        debug!("Stack: {:?}", inpr.op_stack);
                        debug!("Memory: {:?}", inpr.memory);
                    }
                },
                Instr::ICONST => {
                    let b = self.get_next_4_bytes() as i32;
                    push!(b);
                },
                Instr::POP => { pop!(); },
                Instr::ADD =>       { operation!(add);    },
                Instr::SUB =>       { operation!(sub);    },
                Instr::MULTIPLY =>  { operation!(mul);    },
                Instr::DIVIDE =>    { operation!(div);    },
                Instr::MODULUS =>   { operation!(rem);    },
                Instr::RSHIFT =>    { operation!(shl);    },
                Instr::LSHIFT =>    { operation!(shr);    },
                Instr::AND =>       { operation!(bitand); },
                Instr::OR =>        { operation!(bitor);  },
                Instr::NOT =>       {
                    let val = pop!().unwrap();
                    push!(val.not());
                },
                Instr::COMP => {
                    let a = pop!(); let b = pop!();
                    push!(if a > b {1} else if a < b {-1} else {0});
                },
                Instr::COMP_LT => {
                    let a = pop!(); let b = pop!();
                    push!(if a < b {1} else {0});
                },
                Instr::COMP_EQ => {
                    let a = pop!(); let b = pop!();
                    push!(if a == b {1} else {0});
                },
                Instr::COMP_GT => {
                    let a = pop!(); let b = pop!();
                    push!(if a > b {1} else {0});
                },
                Instr::RELJUMP => {reljump!();},
                Instr::RELJUMP_GT => {reljump!(gt);},
                Instr::RELJUMP_LT => {reljump!(lt);},
                Instr::RELJUMP_EQ => {reljump!(eq);},
                Instr::STORE => {
                    let index = self.get_next_4_bytes() as usize;
                    self.locals[index] = pop!().unwrap();
                },
                Instr::LOAD => {
                    let index = self.get_next_4_bytes() as usize;
                    push!(self.locals[index]);
                },
                Instr::CALL => {
                    let id: u32 = self.get_next_4_bytes();
                    push_frame!(id);
                },
                Instr::RETURN => {}
                Instr::PRINT => {
                    println!("PRINT: {}", pop!().unwrap());
                },
                Instr::DUMP_STACK => {
                    println!("{:?}", inpr.op_stack);
                },
                Instr::DUMP_GLOBALS => {
                    println!("{:?}", inpr.memory);},
            }
        }
    }
}

// TODO: Add tests
<|endoftext|>


<commit_before>use libloading::{Library, Symbol};
use regex::{Regex, RegexBuilder};
use std::collections::HashMap;
use std::fs;
use std::io;
use std::mem;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::SystemTime;
use tree_sitter::{Language, PropertySheet};

const PACKAGE_JSON_PATH: &'static str = "package.json";
const PARSER_C_PATH: &'static str = "src/parser.c";
const SCANNER_C_PATH: &'static str = "src/scanner.c";
const SCANNER_CC_PATH: &'static str = "src/scanner.cc";

#[cfg(unix)]
const DYLIB_EXTENSION: &'static str = "so";

#[cfg(windows)]
const DYLIB_EXTENSION: &'static str = "dll";

struct LanguageRepo {
    name: String,
    path: PathBuf,
    language: Option<Language>,
    configurations: Vec<LanguageConfiguration>,
}

pub struct LanguageConfiguration {
    name: String,
    content_regex: Option<Regex>,
    first_line_regex: Option<Regex>,
    file_types: Vec<String>,
    highlight_property_sheet: Option<Result<PropertySheet, PathBuf>>,
}

pub struct Loader {
    parser_lib_path: PathBuf,
    language_repos: Vec<LanguageRepo>,
    language_configuration_ids_by_file_type: HashMap<String, Vec<(usize, usize)>>,
}

unsafe impl Send for Loader {}
unsafe impl Sync for Loader {}

impl Loader {
    pub fn new(parser_lib_path: PathBuf) -> Self {
        Loader {
            parser_lib_path,
            language_repos: Vec::new(),
            language_configuration_ids_by_file_type: HashMap::new(),
        }
    }

    pub fn find_all_languages(&mut self, parser_src_paths: &Vec<PathBuf>) -> io::Result<()> {
        for parser_container_dir in parser_src_paths.iter() {
            for entry in fs::read_dir(parser_container_dir)? {
                let entry = entry?;
                if let Some(parser_dir_name) = entry.file_name().to_str() {
                    if parser_dir_name.starts_with("tree-sitter-") {
                        if self
                            .find_language_at_path(&parser_container_dir.join(parser_dir_name))
                            .is_err()
                        {
                            eprintln!("Error loading {}", parser_dir_name);
                        }
                    }
                }
            }
        }
        Ok(())
    }

    pub fn language_at_path(&mut self, path: &Path) -> io::Result<Option<Language>> {
        if let Ok(id) = self.find_language_at_path(path) {
            Ok(Some(self.language_configuration_for_id(id)?.0))
        } else {
            Ok(None)
        }
    }

    pub fn language_configuration_for_file_name(
        &mut self,
        path: &Path,
    ) -> io::Result<Option<(Language, &LanguageConfiguration)>> {
        let ids = path
            .file_name()
            .and_then(|n| n.to_str())
            .and_then(|file_name| self.language_configuration_ids_by_file_type.get(file_name))
            .or_else(|| {
                path.extension()
                    .and_then(|extension| extension.to_str())
                    .and_then(|extension| {
                        self.language_configuration_ids_by_file_type.get(extension)
                    })
            });
        if let Some(ids) = ids {
            // TODO use `content-regex` to pick one
            for (repo_id, configuration_id) in ids.iter().cloned() {
                let (language, configurations) = self.language_configuration_for_id(repo_id)?;
                return Ok(Some((language, &configurations[configuration_id])));
            }
        }
        Ok(None)
    }

    fn language_configuration_for_id(
        &mut self,
        id: usize,
    ) -> io::Result<(Language, &Vec<LanguageConfiguration>)> {
        let repo = &self.language_repos[id];
        let language = if let Some(language) = repo.language {
            language
        } else {
            let language = self.load_language_at_path(&repo.name, &repo.path)?;
            self.language_repos[id].language = Some(language);
            language
        };
        Ok((language, &self.language_repos[id].configurations))
    }

    fn load_language_at_path(&self, name: &str, language_path: &Path) -> io::Result<Language> {
        let src_path = language_path.join("src");
        let parser_c_path = src_path.join("parser.c");

        let scanner_path;
        let scanner_c_path = src_path.join("scanner.c");
        if scanner_c_path.exists() {
            scanner_path = Some(scanner_c_path);
        } else {
            let scanner_cc_path = src_path.join("scanner.cc");
            if scanner_cc_path.exists() {
                scanner_path = Some(scanner_cc_path);
            } else {
                scanner_path = None;
            }
        }

        self.load_language_from_sources(name, &src_path, &parser_c_path, &scanner_path)
    }

    pub fn load_language_from_sources(
        &self,
        name: &str,
        header_path: &Path,
        parser_path: &Path,
        scanner_path: &Option<PathBuf>,
    ) -> io::Result<Language> {
        let mut library_path = self.parser_lib_path.join(name);
        library_path.set_extension(DYLIB_EXTENSION);

        if needs_recompile(&library_path, &parser_path, &scanner_path)? {
            let mut config = cc::Build::new();
            config
                .opt_level(2)
                .cargo_metadata(false)
                .target(env!("BUILD_TARGET"))
                .host(env!("BUILD_TARGET"));
            let compiler = config.get_compiler();
            let compiler_path = compiler.path();
            let mut command = Command::new(compiler_path);

            if cfg!(windows) {
                command
                    .args(&["/nologo", "/LD", "/I"])
                    .arg(header_path)
                    .arg("/Od")
                    .arg(parser_path);
                if let Some(scanner_path) = scanner_path.as_ref() {
                    command.arg(scanner_path);
                }
                command
                    .arg("/link")
                    .arg(format!("/out:{}", library_path.to_str().unwrap()));
            } else {
                command
                    .arg("-shared")
                    .arg("-fPIC")
                    .arg("-I")
                    .arg(header_path)
                    .arg("-o")
                    .arg(&library_path)
                    .arg("-xc")
                    .arg(parser_path);
                if let Some(scanner_path) = scanner_path.as_ref() {
                    if scanner_path.extension() == Some("c".as_ref()) {
                        command.arg(scanner_path);
                    } else {
                        command.arg("-xc++").arg(scanner_path);
                    }
                }
            }

            command.output()?;
        }

        let library = Library::new(library_path)?;
        let language_fn_name = format!("tree_sitter_{}", name);
        let language = unsafe {
            let language_fn: Symbol<unsafe extern "C" fn() -> Language> =
                library.get(language_fn_name.as_bytes())?;
            language_fn()
        };
        mem::forget(library);
        Ok(language)
    }

    fn find_language_at_path<'a>(&'a mut self, parser_path: &Path) -> io::Result<usize> {
        let name = parser_path
            .file_name()
            .unwrap()
            .to_str()
            .unwrap()
            .split_at("tree-sitter-".len())
            .1;

        #[derive(Deserialize)]
        struct LanguageConfigurationJSON {
            name: String,
            #[serde(rename = "file-types")]
            file_types: Option<Vec<String>>,
            #[serde(rename = "content-regex")]
            content_regex: Option<String>,
            #[serde(rename = "first-line-regex")]
            first_line_regex: Option<String>,
            highlights: Option<String>,
        }

        #[derive(Deserialize)]
        struct PackageJSON {
            #[serde(rename = "tree-sitter")]
            tree_sitter: Option<Vec<LanguageConfigurationJSON>>,
        }

        let package_json_contents = fs::read_to_string(&parser_path.join(PACKAGE_JSON_PATH))?;
        let package_json: PackageJSON = serde_json::from_str(&package_json_contents)?;
        let configurations = package_json
            .tree_sitter
            .map_or(Vec::new(), |configurations| {
                configurations
                    .into_iter()
                    .map(|conf| LanguageConfiguration {
                        name: conf.name,
                        file_types: conf.file_types.unwrap_or(Vec::new()),
                        content_regex: conf
                            .content_regex
                            .and_then(|r| RegexBuilder::new(&r).multi_line(true).build().ok()),
                        first_line_regex: conf
                            .first_line_regex
                            .and_then(|r| RegexBuilder::new(&r).multi_line(true).build().ok()),
                        highlight_property_sheet: conf.highlights.map(|d| Err(d.into())),
                    })
                    .collect()
            });

        for (i, configuration) in configurations.iter().enumerate() {
            for file_type in &configuration.file_types {
                self.language_configuration_ids_by_file_type
                    .entry(file_type.to_string())
                    .or_insert(Vec::new())
                    .push((self.language_repos.len(), i));
            }
        }

        self.language_repos.push(LanguageRepo {
            name: name.to_string(),
            path: parser_path.to_owned(),
            language: None,
            configurations,
        });

        Ok(self.language_repos.len() - 1)
    }
}

fn needs_recompile(
    lib_path: &Path,
    parser_c_path: &Path,
    scanner_path: &Option<PathBuf>,
) -> io::Result<bool> {
    if !lib_path.exists() {
        return Ok(true);
    }
    let lib_mtime = mtime(lib_path)?;
    if mtime(parser_c_path)? > lib_mtime {
        return Ok(true);
    }
    if let Some(scanner_path) = scanner_path {
        if mtime(scanner_path)? > lib_mtime {
            return Ok(true);
        }
    }
    Ok(false)
}

fn mtime(path: &Path) -> io::Result<SystemTime> {
    Ok(fs::metadata(path)?.modified()?)
}
<commit_msg>Use the compiler environment vars computed by the cc config<commit_after>use libloading::{Library, Symbol};
use regex::{Regex, RegexBuilder};
use std::collections::HashMap;
use std::fs;
use std::io;
use std::mem;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::SystemTime;
use tree_sitter::{Language, PropertySheet};

#[cfg(unix)]
const DYLIB_EXTENSION: &'static str = "so";

#[cfg(windows)]
const DYLIB_EXTENSION: &'static str = "dll";

struct LanguageRepo {
    name: String,
    path: PathBuf,
    language: Option<Language>,
    configurations: Vec<LanguageConfiguration>,
}

pub struct LanguageConfiguration {
    name: String,
    content_regex: Option<Regex>,
    first_line_regex: Option<Regex>,
    file_types: Vec<String>,
    highlight_property_sheet: Option<Result<PropertySheet, PathBuf>>,
}

pub struct Loader {
    parser_lib_path: PathBuf,
    language_repos: Vec<LanguageRepo>,
    language_configuration_ids_by_file_type: HashMap<String, Vec<(usize, usize)>>,
}

unsafe impl Send for Loader {}
unsafe impl Sync for Loader {}

impl Loader {
    pub fn new(parser_lib_path: PathBuf) -> Self {
        Loader {
            parser_lib_path,
            language_repos: Vec::new(),
            language_configuration_ids_by_file_type: HashMap::new(),
        }
    }

    pub fn find_all_languages(&mut self, parser_src_paths: &Vec<PathBuf>) -> io::Result<()> {
        for parser_container_dir in parser_src_paths.iter() {
            for entry in fs::read_dir(parser_container_dir)? {
                let entry = entry?;
                if let Some(parser_dir_name) = entry.file_name().to_str() {
                    if parser_dir_name.starts_with("tree-sitter-") {
                        if self
                            .find_language_at_path(&parser_container_dir.join(parser_dir_name))
                            .is_err()
                        {
                            eprintln!("Error loading {}", parser_dir_name);
                        }
                    }
                }
            }
        }
        Ok(())
    }

    pub fn language_at_path(&mut self, path: &Path) -> io::Result<Option<Language>> {
        if let Ok(id) = self.find_language_at_path(path) {
            Ok(Some(self.language_configuration_for_id(id)?.0))
        } else {
            Ok(None)
        }
    }

    pub fn language_configuration_for_file_name(
        &mut self,
        path: &Path,
    ) -> io::Result<Option<(Language, &LanguageConfiguration)>> {
        let ids = path
            .file_name()
            .and_then(|n| n.to_str())
            .and_then(|file_name| self.language_configuration_ids_by_file_type.get(file_name))
            .or_else(|| {
                path.extension()
                    .and_then(|extension| extension.to_str())
                    .and_then(|extension| {
                        self.language_configuration_ids_by_file_type.get(extension)
                    })
            });
        if let Some(ids) = ids {
            // TODO use `content-regex` to pick one
            for (repo_id, configuration_id) in ids.iter().cloned() {
                let (language, configurations) = self.language_configuration_for_id(repo_id)?;
                return Ok(Some((language, &configurations[configuration_id])));
            }
        }
        Ok(None)
    }

    fn language_configuration_for_id(
        &mut self,
        id: usize,
    ) -> io::Result<(Language, &Vec<LanguageConfiguration>)> {
        let repo = &self.language_repos[id];
        let language = if let Some(language) = repo.language {
            language
        } else {
            let language = self.load_language_at_path(&repo.name, &repo.path)?;
            self.language_repos[id].language = Some(language);
            language
        };
        Ok((language, &self.language_repos[id].configurations))
    }

    fn load_language_at_path(&self, name: &str, language_path: &Path) -> io::Result<Language> {
        let src_path = language_path.join("src");
        let parser_c_path = src_path.join("parser.c");

        let scanner_path;
        let scanner_c_path = src_path.join("scanner.c");
        if scanner_c_path.exists() {
            scanner_path = Some(scanner_c_path);
        } else {
            let scanner_cc_path = src_path.join("scanner.cc");
            if scanner_cc_path.exists() {
                scanner_path = Some(scanner_cc_path);
            } else {
                scanner_path = None;
            }
        }

        self.load_language_from_sources(name, &src_path, &parser_c_path, &scanner_path)
    }

    pub fn load_language_from_sources(
        &self,
        name: &str,
        header_path: &Path,
        parser_path: &Path,
        scanner_path: &Option<PathBuf>,
    ) -> io::Result<Language> {
        let mut library_path = self.parser_lib_path.join(name);
        library_path.set_extension(DYLIB_EXTENSION);

        if needs_recompile(&library_path, &parser_path, &scanner_path)? {
            let mut config = cc::Build::new();
            config
                .opt_level(2)
                .cargo_metadata(false)
                .target(env!("BUILD_TARGET"))
                .host(env!("BUILD_TARGET"));
            let compiler = config.get_compiler();
            let mut command = Command::new(compiler.path());
            for (key, value) in compiler.env() {
                command.env(key, value);
            }

            if cfg!(windows) {
                command
                    .args(&["/nologo", "/LD", "/I"])
                    .arg(header_path)
                    .arg("/Od")
                    .arg(parser_path);
                if let Some(scanner_path) = scanner_path.as_ref() {
                    command.arg(scanner_path);
                }
                command
                    .arg("/link")
                    .arg(format!("/out:{}", library_path.to_str().unwrap()));
            } else {
                command
                    .arg("-shared")
                    .arg("-fPIC")
                    .arg("-I")
                    .arg(header_path)
                    .arg("-o")
                    .arg(&library_path)
                    .arg("-xc")
                    .arg(parser_path);
                if let Some(scanner_path) = scanner_path.as_ref() {
                    if scanner_path.extension() == Some("c".as_ref()) {
                        command.arg(scanner_path);
                    } else {
                        command.arg("-xc++").arg(scanner_path);
                    }
                }
            }

            let output = command.output()?;
            if !output.status.success() {
                return Err(io::Error::new(
                    io::ErrorKind::Other,
                    format!(
                        "Parser compilation failed.\nStdout: {}\nStderr: {}",
                        String::from_utf8_lossy(&output.stdout),
                        String::from_utf8_lossy(&output.stderr)
                    ).as_str(),
                ));
            }
        }

        let library = Library::new(library_path)?;
        let language_fn_name = format!("tree_sitter_{}", name);
        let language = unsafe {
            let language_fn: Symbol<unsafe extern "C" fn() -> Language> =
                library.get(language_fn_name.as_bytes())?;
            language_fn()
        };
        mem::forget(library);
        Ok(language)
    }

    fn find_language_at_path<'a>(&'a mut self, parser_path: &Path) -> io::Result<usize> {
        let name = parser_path
            .file_name()
            .unwrap()
            .to_str()
            .unwrap()
            .split_at("tree-sitter-".len())
            .1;

        #[derive(Deserialize)]
        struct LanguageConfigurationJSON {
            name: String,
            #[serde(rename = "file-types")]
            file_types: Option<Vec<String>>,
            #[serde(rename = "content-regex")]
            content_regex: Option<String>,
            #[serde(rename = "first-line-regex")]
            first_line_regex: Option<String>,
            highlights: Option<String>,
        }

        #[derive(Deserialize)]
        struct PackageJSON {
            #[serde(rename = "tree-sitter")]
            tree_sitter: Option<Vec<LanguageConfigurationJSON>>,
        }

        let package_json_contents = fs::read_to_string(&parser_path.join("package.json"))?;
        let package_json: PackageJSON = serde_json::from_str(&package_json_contents)?;
        let configurations = package_json
            .tree_sitter
            .map_or(Vec::new(), |configurations| {
                configurations
                    .into_iter()
                    .map(|conf| LanguageConfiguration {
                        name: conf.name,
                        file_types: conf.file_types.unwrap_or(Vec::new()),
                        content_regex: conf
                            .content_regex
                            .and_then(|r| RegexBuilder::new(&r).multi_line(true).build().ok()),
                        first_line_regex: conf
                            .first_line_regex
                            .and_then(|r| RegexBuilder::new(&r).multi_line(true).build().ok()),
                        highlight_property_sheet: conf.highlights.map(|d| Err(d.into())),
                    })
                    .collect()
            });

        for (i, configuration) in configurations.iter().enumerate() {
            for file_type in &configuration.file_types {
                self.language_configuration_ids_by_file_type
                    .entry(file_type.to_string())
                    .or_insert(Vec::new())
                    .push((self.language_repos.len(), i));
            }
        }

        self.language_repos.push(LanguageRepo {
            name: name.to_string(),
            path: parser_path.to_owned(),
            language: None,
            configurations,
        });

        Ok(self.language_repos.len() - 1)
    }
}

fn needs_recompile(
    lib_path: &Path,
    parser_c_path: &Path,
    scanner_path: &Option<PathBuf>,
) -> io::Result<bool> {
    if !lib_path.exists() {
        return Ok(true);
    }
    let lib_mtime = mtime(lib_path)?;
    if mtime(parser_c_path)? > lib_mtime {
        return Ok(true);
    }
    if let Some(scanner_path) = scanner_path {
        if mtime(scanner_path)? > lib_mtime {
            return Ok(true);
        }
    }
    Ok(false)
}

fn mtime(path: &Path) -> io::Result<SystemTime> {
    Ok(fs::metadata(path)?.modified()?)
}
<|endoftext|>