<commit_before>use crate::{
    config, db,
    models::Version,
    schema::{crates, readme_renderings, versions},
    uploaders::Uploader,
};
use anyhow::{anyhow, Context};
use std::{io::Read, path::Path, sync::Arc, thread};

use cargo_registry_markdown::text_to_html;
use chrono::{TimeZone, Utc};
use diesel::{dsl::any, prelude::*};
use flate2::read::GzDecoder;
use reqwest::{blocking::Client, header};
use tar::{self, Archive};

const CACHE_CONTROL_README: &str = "public,max-age=604800";
const USER_AGENT: &str = "crates-admin";

#[derive(clap::Parser, Debug)]
#[clap(
    name = "render-readmes",
    about = "Iterates over every crate versions ever uploaded and (re-)renders their \
        readme using the readme renderer from the cargo_registry crate.",
    after_help = "Warning: this can take a lot of time."
)]
pub struct Opts {
    /// How many versions should be queried and processed at a time.
    #[clap(long, default_value = "25")]
    page_size: usize,

    /// Only rerender readmes that are older than this date.
    #[clap(long)]
    older_than: Option<String>,

    /// Only rerender readmes for the specified crate.
    #[clap(long = "crate")]
    crate_name: Option<String>,
}

pub fn run(opts: Opts) {
    let base_config = Arc::new(config::Base::from_environment());
    let conn = db::connect_now().unwrap();

    let start_time = Utc::now();

    let older_than = if let Some(ref time) = opts.older_than {
        Utc.datetime_from_str(time, "%Y-%m-%d %H:%M:%S")
            .expect("Could not parse --older-than argument as a time")
    } else {
        start_time
    };
    let older_than = older_than.naive_utc();

    println!("Start time:                   {}", start_time);
    println!("Rendering readmes older than: {}", older_than);

    let mut query = versions::table
        .inner_join(crates::table)
        .left_outer_join(readme_renderings::table)
        .filter(
            readme_renderings::rendered_at
                .lt(older_than)
                .or(readme_renderings::version_id.is_null()),
        )
        .select(versions::id)
        .into_boxed();

    if let Some(crate_name) = opts.crate_name {
        println!("Rendering readmes for {}", crate_name);
        query = query.filter(crates::name.eq(crate_name));
    }

    let version_ids: Vec<i32> = query.load(&conn).expect("error loading version ids");

    let total_versions = version_ids.len();
    println!("Rendering {} versions", total_versions);

    let page_size = opts.page_size;

    let total_pages = total_versions / page_size;
    let total_pages = if total_versions % page_size == 0 {
        total_pages
    } else {
        total_pages + 1
    };

    let client = Client::new();

    for (page_num, version_ids_chunk) in version_ids.chunks(page_size).enumerate() {
        println!(
            "= Page {} of {} ==================================",
            page_num + 1,
            total_pages
        );

        let versions: Vec<(Version, String)> = versions::table
            .inner_join(crates::table)
            .filter(versions::id.eq(any(version_ids_chunk)))
            .select((versions::all_columns, crates::name))
            .load(&conn)
            .expect("error loading versions");

        let mut tasks = Vec::with_capacity(page_size as usize);
        for (version, krate_name) in versions {
            Version::record_readme_rendering(version.id, &conn).unwrap_or_else(|_| {
                panic!(
                    "[{}-{}] Couldn't record rendering time",
                    krate_name, version.num
                )
            });
            let client = client.clone();
            let base_config = base_config.clone();
            let handle = thread::spawn(move || {
                println!("[{}-{}] Rendering README...", krate_name, version.num);
                let readme = get_readme(base_config.uploader(), &client, &version, &krate_name);
                if readme.is_none() {
                    return;
                }
                let readme = readme.unwrap();
                let content_length = readme.len() as u64;
                let content = std::io::Cursor::new(readme);
                let readme_path = format!("readmes/{0}/{0}-{1}.html", krate_name, version.num);
                let mut extra_headers = header::HeaderMap::new();
                extra_headers.insert(
                    header::CACHE_CONTROL,
                    header::HeaderValue::from_static(CACHE_CONTROL_README),
                );
                base_config
                    .uploader()
                    .upload(
                        &client,
                        &readme_path,
                        content,
                        content_length,
                        "text/html",
                        extra_headers,
                    )
                    .unwrap_or_else(|_| {
                        panic!(
                            "[{}-{}] Couldn't upload file to S3",
                            krate_name, version.num
                        )
                    });
            });
            tasks.push(handle);
        }
        for handle in tasks {
            if let Err(err) = handle.join() {
                println!("Thread panicked: {:?}", err);
            }
        }
    }
}

/// Renders the readme of an uploaded crate version.
fn get_readme(
    uploader: &Uploader,
    client: &Client,
    version: &Version,
    krate_name: &str,
) -> Option<String> {
    let pkg_name = format!("{}-{}", krate_name, version.num);

    let location = uploader.crate_location(krate_name, &version.num.to_string());

    let location = match uploader {
        Uploader::S3 { .. } => location,
        Uploader::Local => format!("http://localhost:8888/{}", location),
    };

    let mut extra_headers = header::HeaderMap::new();
    extra_headers.insert(
        header::USER_AGENT,
        header::HeaderValue::from_static(USER_AGENT),
    );
    let response = match client.get(&location).headers(extra_headers).send() {
        Ok(r) => r,
        Err(err) => {
            println!("[{}] Unable to fetch crate: {}", pkg_name, err);
            return None;
        }
    };

    if !response.status().is_success() {
        println!(
            "[{}] Failed to get a 200 response: {}",
            pkg_name,
            response.text().unwrap()
        );
        return None;
    }

    let reader = GzDecoder::new(response);
    let archive = Archive::new(reader);
    render_pkg_readme(archive, &pkg_name)
}

fn render_pkg_readme<R: Read>(mut archive: Archive<R>, pkg_name: &str) -> Option<String> {
    let mut entries = archive
        .entries()
        .unwrap_or_else(|_| panic!("[{}] Invalid tar archive entries", pkg_name));

    let manifest: Manifest = {
        let path = format!("{}/Cargo.toml", pkg_name);
        let contents = find_file_by_path(&mut entries, Path::new(&path))
            .unwrap_or_else(|_| panic!("[{}] couldn't open file: Cargo.toml", pkg_name));
        toml::from_str(&contents)
            .unwrap_or_else(|_| panic!("[{}] Syntax error in manifest file", pkg_name))
    };

    let rendered = {
        let readme_path = manifest
            .package
            .readme
            .clone()
            .unwrap_or_else(|| "README.md".into());
        let path = format!("{}/{}", pkg_name, readme_path);
        let contents = find_file_by_path(&mut entries, Path::new(&path)).ok()?;
        text_to_html(
            &contents,
            &readme_path,
            manifest.package.repository.as_deref(),
        )
    };
    return Some(rendered);

    #[derive(Debug, Deserialize)]
    struct Package {
        readme: Option<String>,
        repository: Option<String>,
    }

    #[derive(Debug, Deserialize)]
    struct Manifest {
        package: Package,
    }
}

/// Search an entry by its path in a Tar archive.
fn find_file_by_path<R: Read>(
    entries: &mut tar::Entries<'_, R>,
    path: &Path,
) -> anyhow::Result<String> {
    let mut file = entries
        .filter_map(|entry| entry.ok())
        .find(|file| match file.path() {
            Ok(p) => p == path,
            Err(_) => false,
        })
        .ok_or_else(|| anyhow!("Failed to find tarball entry: {}", path.display()))?;

    let mut contents = String::new();
    file.read_to_string(&mut contents)
        .context("Failed to read file contents")?;

    Ok(contents)
}

#[cfg(test)]
pub mod tests {
    use std::io::Write;
    use tar;

    use super::render_pkg_readme;

    pub fn add_file<W: Write>(pkg: &mut tar::Builder<W>, path: &str, content: &[u8]) {
        let mut header = tar::Header::new_gnu();
        header.set_size(content.len() as u64);
        header.set_cksum();
        pkg.append_data(&mut header, path, content).unwrap();
    }

    #[test]
    fn test_render_pkg_readme() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
readme = "README.md"
"#,
        );
        add_file(&mut pkg, "foo-0.0.1/README.md", b"readme");
        let serialized_archive = pkg.into_inner().unwrap();
        let result =
            render_pkg_readme(tar::Archive::new(&*serialized_archive), "foo-0.0.1").unwrap();
        assert!(result.contains("readme"))
    }

    #[test]
    fn test_render_pkg_no_readme() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
"#,
        );
        let serialized_archive = pkg.into_inner().unwrap();
        assert!(render_pkg_readme(tar::Archive::new(&*serialized_archive), "foo-0.0.1").is_none());
    }

    #[test]
    fn test_render_pkg_implicit_readme() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
"#,
        );
        add_file(&mut pkg, "foo-0.0.1/README.md", b"readme");
        let serialized_archive = pkg.into_inner().unwrap();
        let result =
            render_pkg_readme(tar::Archive::new(&*serialized_archive), "foo-0.0.1").unwrap();
        assert!(result.contains("readme"))
    }

    #[test]
    fn test_render_pkg_readme_w_link() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
readme = "README.md"
repository = "https://github.com/foo/foo"
"#,
        );
        add_file(
            &mut pkg,
            "foo-0.0.1/README.md",
            b"readme [link](./Other.md)",
        );
        let serialized_archive = pkg.into_inner().unwrap();
        let result =
            render_pkg_readme(tar::Archive::new(&*serialized_archive), "foo-0.0.1").unwrap();
        assert!(result.contains("\"https://github.com/foo/foo/blob/HEAD/./Other.md\""))
    }

    #[test]
    fn test_render_pkg_readme_not_at_root() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
readme = "docs/README.md"
repository = "https://github.com/foo/foo"
"#,
        );
        add_file(
            &mut pkg,
            "foo-0.0.1/docs/README.md",
            b"docs/readme [link](./Other.md)",
        );
        let serialized_archive = pkg.into_inner().unwrap();
        let result =
            render_pkg_readme(tar::Archive::new(&*serialized_archive), "foo-0.0.1").unwrap();
        assert!(result.contains("docs/readme"));
        assert!(result.contains("\"https://github.com/foo/foo/blob/HEAD/docs/./Other.md\""))
    }
}
<commit_msg>admin::render_readmes: Return `Result` from `render_pkg_readme()`<commit_after>use crate::{
    config, db,
    models::Version,
    schema::{crates, readme_renderings, versions},
    uploaders::Uploader,
};
use anyhow::{anyhow, Context};
use std::{io::Read, path::Path, sync::Arc, thread};

use cargo_registry_markdown::text_to_html;
use chrono::{TimeZone, Utc};
use diesel::{dsl::any, prelude::*};
use flate2::read::GzDecoder;
use reqwest::{blocking::Client, header};
use tar::{self, Archive};

const CACHE_CONTROL_README: &str = "public,max-age=604800";
const USER_AGENT: &str = "crates-admin";

#[derive(clap::Parser, Debug)]
#[clap(
    name = "render-readmes",
    about = "Iterates over every crate versions ever uploaded and (re-)renders their \
        readme using the readme renderer from the cargo_registry crate.",
    after_help = "Warning: this can take a lot of time."
)]
pub struct Opts {
    /// How many versions should be queried and processed at a time.
    #[clap(long, default_value = "25")]
    page_size: usize,

    /// Only rerender readmes that are older than this date.
    #[clap(long)]
    older_than: Option<String>,

    /// Only rerender readmes for the specified crate.
    #[clap(long = "crate")]
    crate_name: Option<String>,
}

pub fn run(opts: Opts) {
    let base_config = Arc::new(config::Base::from_environment());
    let conn = db::connect_now().unwrap();

    let start_time = Utc::now();

    let older_than = if let Some(ref time) = opts.older_than {
        Utc.datetime_from_str(time, "%Y-%m-%d %H:%M:%S")
            .expect("Could not parse --older-than argument as a time")
    } else {
        start_time
    };
    let older_than = older_than.naive_utc();

    println!("Start time:                   {}", start_time);
    println!("Rendering readmes older than: {}", older_than);

    let mut query = versions::table
        .inner_join(crates::table)
        .left_outer_join(readme_renderings::table)
        .filter(
            readme_renderings::rendered_at
                .lt(older_than)
                .or(readme_renderings::version_id.is_null()),
        )
        .select(versions::id)
        .into_boxed();

    if let Some(crate_name) = opts.crate_name {
        println!("Rendering readmes for {}", crate_name);
        query = query.filter(crates::name.eq(crate_name));
    }

    let version_ids: Vec<i32> = query.load(&conn).expect("error loading version ids");

    let total_versions = version_ids.len();
    println!("Rendering {} versions", total_versions);

    let page_size = opts.page_size;

    let total_pages = total_versions / page_size;
    let total_pages = if total_versions % page_size == 0 {
        total_pages
    } else {
        total_pages + 1
    };

    let client = Client::new();

    for (page_num, version_ids_chunk) in version_ids.chunks(page_size).enumerate() {
        println!(
            "= Page {} of {} ==================================",
            page_num + 1,
            total_pages
        );

        let versions: Vec<(Version, String)> = versions::table
            .inner_join(crates::table)
            .filter(versions::id.eq(any(version_ids_chunk)))
            .select((versions::all_columns, crates::name))
            .load(&conn)
            .expect("error loading versions");

        let mut tasks = Vec::with_capacity(page_size as usize);
        for (version, krate_name) in versions {
            Version::record_readme_rendering(version.id, &conn).unwrap_or_else(|_| {
                panic!(
                    "[{}-{}] Couldn't record rendering time",
                    krate_name, version.num
                )
            });
            let client = client.clone();
            let base_config = base_config.clone();
            let handle = thread::spawn(move || {
                println!("[{}-{}] Rendering README...", krate_name, version.num);
                let readme = get_readme(base_config.uploader(), &client, &version, &krate_name);
                if readme.is_none() {
                    return;
                }
                let readme = readme.unwrap();
                let content_length = readme.len() as u64;
                let content = std::io::Cursor::new(readme);
                let readme_path = format!("readmes/{0}/{0}-{1}.html", krate_name, version.num);
                let mut extra_headers = header::HeaderMap::new();
                extra_headers.insert(
                    header::CACHE_CONTROL,
                    header::HeaderValue::from_static(CACHE_CONTROL_README),
                );
                base_config
                    .uploader()
                    .upload(
                        &client,
                        &readme_path,
                        content,
                        content_length,
                        "text/html",
                        extra_headers,
                    )
                    .unwrap_or_else(|_| {
                        panic!(
                            "[{}-{}] Couldn't upload file to S3",
                            krate_name, version.num
                        )
                    });
            });
            tasks.push(handle);
        }
        for handle in tasks {
            if let Err(err) = handle.join() {
                println!("Thread panicked: {:?}", err);
            }
        }
    }
}

/// Renders the readme of an uploaded crate version.
fn get_readme(
    uploader: &Uploader,
    client: &Client,
    version: &Version,
    krate_name: &str,
) -> Option<String> {
    let pkg_name = format!("{}-{}", krate_name, version.num);

    let location = uploader.crate_location(krate_name, &version.num.to_string());

    let location = match uploader {
        Uploader::S3 { .. } => location,
        Uploader::Local => format!("http://localhost:8888/{}", location),
    };

    let mut extra_headers = header::HeaderMap::new();
    extra_headers.insert(
        header::USER_AGENT,
        header::HeaderValue::from_static(USER_AGENT),
    );
    let response = match client.get(&location).headers(extra_headers).send() {
        Ok(r) => r,
        Err(err) => {
            println!("[{}] Unable to fetch crate: {}", pkg_name, err);
            return None;
        }
    };

    if !response.status().is_success() {
        println!(
            "[{}] Failed to get a 200 response: {}",
            pkg_name,
            response.text().unwrap()
        );
        return None;
    }

    let reader = GzDecoder::new(response);
    let archive = Archive::new(reader);
    render_pkg_readme(archive, &pkg_name).ok()
}

fn render_pkg_readme<R: Read>(mut archive: Archive<R>, pkg_name: &str) -> anyhow::Result<String> {
    let mut entries = archive.entries().context("Invalid tar archive entries")?;

    let manifest: Manifest = {
        let path = format!("{}/Cargo.toml", pkg_name);
        let contents = find_file_by_path(&mut entries, Path::new(&path))
            .context("Failed to read Cargo.toml file")?;

        toml::from_str(&contents).context("Failed to parse manifest file")?
    };

    let rendered = {
        let readme_path = manifest
            .package
            .readme
            .clone()
            .unwrap_or_else(|| "README.md".into());
        let path = format!("{}/{}", pkg_name, readme_path);
        let contents = find_file_by_path(&mut entries, Path::new(&path))
            .with_context(|| format!("Failed to read {} file", readme_path))?;

        text_to_html(
            &contents,
            &readme_path,
            manifest.package.repository.as_deref(),
        )
    };
    return Ok(rendered);

    #[derive(Debug, Deserialize)]
    struct Package {
        readme: Option<String>,
        repository: Option<String>,
    }

    #[derive(Debug, Deserialize)]
    struct Manifest {
        package: Package,
    }
}

/// Search an entry by its path in a Tar archive.
fn find_file_by_path<R: Read>(
    entries: &mut tar::Entries<'_, R>,
    path: &Path,
) -> anyhow::Result<String> {
    let mut file = entries
        .filter_map(|entry| entry.ok())
        .find(|file| match file.path() {
            Ok(p) => p == path,
            Err(_) => false,
        })
        .ok_or_else(|| anyhow!("Failed to find tarball entry: {}", path.display()))?;

    let mut contents = String::new();
    file.read_to_string(&mut contents)
        .context("Failed to read file contents")?;

    Ok(contents)
}

#[cfg(test)]
pub mod tests {
    use std::io::Write;
    use tar;

    use super::render_pkg_readme;

    pub fn add_file<W: Write>(pkg: &mut tar::Builder<W>, path: &str, content: &[u8]) {
        let mut header = tar::Header::new_gnu();
        header.set_size(content.len() as u64);
        header.set_cksum();
        pkg.append_data(&mut header, path, content).unwrap();
    }

    #[test]
    fn test_render_pkg_readme() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
readme = "README.md"
"#,
        );
        add_file(&mut pkg, "foo-0.0.1/README.md", b"readme");
        let serialized_archive = pkg.into_inner().unwrap();
        let result =
            render_pkg_readme(tar::Archive::new(&*serialized_archive), "foo-0.0.1").unwrap();
        assert!(result.contains("readme"))
    }

    #[test]
    fn test_render_pkg_no_readme() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
"#,
        );
        let serialized_archive = pkg.into_inner().unwrap();
        assert_err!(render_pkg_readme(
            tar::Archive::new(&*serialized_archive),
            "foo-0.0.1"
        ));
    }

    #[test]
    fn test_render_pkg_implicit_readme() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
"#,
        );
        add_file(&mut pkg, "foo-0.0.1/README.md", b"readme");
        let serialized_archive = pkg.into_inner().unwrap();
        let result =
            render_pkg_readme(tar::Archive::new(&*serialized_archive), "foo-0.0.1").unwrap();
        assert!(result.contains("readme"))
    }

    #[test]
    fn test_render_pkg_readme_w_link() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
readme = "README.md"
repository = "https://github.com/foo/foo"
"#,
        );
        add_file(
            &mut pkg,
            "foo-0.0.1/README.md",
            b"readme [link](./Other.md)",
        );
        let serialized_archive = pkg.into_inner().unwrap();
        let result =
            render_pkg_readme(tar::Archive::new(&*serialized_archive), "foo-0.0.1").unwrap();
        assert!(result.contains("\"https://github.com/foo/foo/blob/HEAD/./Other.md\""))
    }

    #[test]
    fn test_render_pkg_readme_not_at_root() {
        let mut pkg = tar::Builder::new(vec![]);
        add_file(
            &mut pkg,
            "foo-0.0.1/Cargo.toml",
            br#"
[package]
readme = "docs/README.md"
repository = "https://github.com/foo/foo"
"#,
        );
        add_file(
            &mut pkg,
            "foo-0.0.1/docs/README.md",
            b"docs/readme [link](./Other.md)",
        );
        let serialized_archive = pkg.into_inner().unwrap();
        let result =
            render_pkg_readme(tar::Archive::new(&*serialized_archive), "foo-0.0.1").unwrap();
        assert!(result.contains("docs/readme"));
        assert!(result.contains("\"https://github.com/foo/foo/blob/HEAD/docs/./Other.md\""))
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright 2017 Sreejith Krishnan R
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

use std::any::Any;

use super::super::{Layout, PresentationLayout, ConcreteLayout};
use ::draw::{Drawable, BoundingBox, MeasureMode};
use ::platform::Context;
use ::paint::{Canvas, Point, MathRuler, Rect};
use ::props::{DisplayStyle, MathSize};

pub struct MmultiscriptLayout {
    pub(crate) base_layout: Box<Layout>,
    pub(crate) prescript_layout: Vec<(Box<Layout>, Box<Layout>)>,
    pub(crate) postscript_layout: Vec<(Box<Layout>, Box<Layout>)>,
    pub(crate) subscript_shift: f32,
    pub(crate) superscript_shift: f32,

    pub(crate) presentation_layout: PresentationLayout,
}

impl Layout for MmultiscriptLayout {
    fn layout<'a>(&'a self, context: &Context) -> Box<Drawable + 'a> {
        let mut wrapper = self.presentation_layout.layout(context);

        wrapper.wrap(MultiscriptDrawable {
            base: self.base_layout.layout(context),
            base_pos: Point::new(0f32, 0f32),
            prescripts: self.prescript_layout.iter().map(|&(ref superscript, ref subscript)| {
                MultiscriptDrawableChild {
                    subscript: subscript.layout(context),
                    subscript_pos: Point::new(0f32, 0f32),
                    superscript: superscript.layout(context),
                    superscript_pos: Point::new(0f32, 0f32),
                }
            }).collect(),
            postscripts: self.postscript_layout.iter().map(|&(ref superscript, ref subscript)| {
                MultiscriptDrawableChild {
                    subscript: subscript.layout(context),
                    subscript_pos: Point::new(0f32, 0f32),
                    superscript: superscript.layout(context),
                    superscript_pos: Point::new(0f32, 0f32),
                }
            }).collect(),
            superscript_shift: self.superscript_shift,
            subscript_shift: self.subscript_shift,
            display_style: self.presentation_layout.display_style,
            base_size: self.presentation_layout.script_level.get_font_size(context, &MathSize::NORMAL),
            bounding_box: BoundingBox::default(),
        });

        wrapper.calculate(context, &MeasureMode::Wrap, &MeasureMode::Wrap);

        Box::new(wrapper)
    }

    fn as_any(&self) -> &Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut Any {
        self
    }
}

struct MultiscriptDrawable<'a> {
    base: Box<Drawable + 'a>,
    base_pos: Point,

    prescripts: Vec<MultiscriptDrawableChild<'a>>,
    postscripts: Vec<MultiscriptDrawableChild<'a>>,

    superscript_shift: f32,
    subscript_shift: f32,
    display_style: DisplayStyle,
    base_size: f32,

    bounding_box: BoundingBox,
}

struct MultiscriptDrawableChild<'a> {
    subscript: Box<Drawable + 'a>,
    subscript_pos: Point,
    superscript: Box<Drawable + 'a>,
    superscript_pos: Point,
}

struct ScriptPosition {
    base_baseline_pos: f32,
    superscript_baseline_pos: f32,
    subscript_baseline_pos: f32
}

impl ScriptPosition {
    fn max(&self, rhs: &ScriptPosition) -> ScriptPosition {
        ScriptPosition {
            base_baseline_pos: self.base_baseline_pos.max(rhs.base_baseline_pos),
            superscript_baseline_pos: self.superscript_baseline_pos.max(rhs.superscript_baseline_pos),
            subscript_baseline_pos: self.subscript_baseline_pos.max(rhs.subscript_baseline_pos),
        }
    }
}

impl<'a> MultiscriptDrawable<'a> {
    fn find_script_y_pos(&self, child: &MultiscriptDrawableChild<'a>, ruler: &MathRuler) -> ScriptPosition {

        let base = &self.base;
        let subscript = &child.subscript;
        let superscript = &child.superscript;

        let has_subscript = subscript.bounding_box().width() > 0f32 || subscript.bounding_box().height() > 0f32;
        let has_superscript = superscript.bounding_box().width() > 0f32 || superscript.bounding_box().height() > 0f32;

        // We will fix the position of superscript first
        let superscript_y_pos = 0f32;

        let base_y_pos = if has_superscript {
            // Recommended shift between baseline of base and baseline of superscript
            let superscript_shift = if self.superscript_shift.is_nan() {
                if self.display_style {
                    ruler.superscript_shift_up()
                } else {
                    ruler.superscript_shift_up_cramped()
                }
            } else {
                self.superscript_shift
            };

            let mut base_y_pos = superscript.bounding_box().baseline_pos() +
                superscript_shift -
                base.bounding_box().baseline_pos();

            // Minimum shift between superscript bottom and baseline of base
            let superscript_bottom_min = ruler.superscript_bottom_min();

            base_y_pos = base_y_pos.max(
                superscript.bounding_box().height() +
                    superscript_bottom_min -
                    base.bounding_box().baseline_pos()
            );

            // Maximum drop of baseline of superscript from top of base
            let superscript_baseline_drop_max = ruler.superscript_baseline_drop_max();

            base_y_pos = base_y_pos.max(
                superscript.bounding_box().baseline_pos() - superscript_baseline_drop_max
            );

            if has_subscript {
                // Maximum shift between superscript bottom and baseline of base when subscript is present
                let superscript_bottom_max_with_subscript = ruler.superscript_bottom_max_with_subscript();
                base_y_pos = base_y_pos.min(
                    superscript.bounding_box().height() +
                        superscript_bottom_max_with_subscript -
                        base.bounding_box().baseline_pos()
                );
            }

            base_y_pos
        } else {
            0f32
        };

        let subscript_y_pos = if has_subscript {
            // Recommended shift between baseline of base and baseline of subscript
            let subscript_shift = if self.subscript_shift.is_nan() {
                ruler.subscript_shift_down()
            } else {
                self.subscript_shift
            };

            let mut subscript_y_pos = base_y_pos + base.bounding_box().baseline_pos() +
                subscript_shift -
                subscript.bounding_box().baseline_pos();

            // Minimum shift between baseline of subscript and bottom of base
            let subscript_baseline_drop_min = ruler.subscript_baseline_drop_min();

            subscript_y_pos = subscript_y_pos.max(
                base_y_pos + base.bounding_box().height() +
                    subscript_baseline_drop_min -
                    subscript.bounding_box().baseline_pos()
            );

            // Maximum shift between top of subscript and baseline of base
            let subscript_top_max = ruler.subscript_top_max();

            subscript_y_pos = subscript_y_pos.max(
                base_y_pos +
                    base.bounding_box().baseline_pos() -
                    subscript_top_max
            );

            if has_superscript {
                // Minimum gap between bottom of superscript and top of subscript
                let sub_superscript_gap_min = ruler.sub_superscript_gap_min();

                subscript_y_pos = subscript_y_pos.max(
                    superscript_y_pos +
                        superscript.bounding_box().height() +
                        sub_superscript_gap_min
                );
            }

            subscript_y_pos
        } else {
            base_y_pos + base.bounding_box().height()
        };

        return ScriptPosition {
            base_baseline_pos: base_y_pos + base.bounding_box().baseline_pos(),
            superscript_baseline_pos: superscript_y_pos + superscript.bounding_box().baseline_pos(),
            subscript_baseline_pos: subscript_y_pos + subscript.bounding_box().baseline_pos(),
        }
    }

    fn find_max_script_y_pos(&self, scripts: &Vec<MultiscriptDrawableChild<'a>>, ruler: &MathRuler) -> ScriptPosition {
        let mut script_pos = ScriptPosition {
            base_baseline_pos: 0.0,
            superscript_baseline_pos: 0.0,
            subscript_baseline_pos: 0.0,
        };

        for prescript in scripts.iter() {
            let current_script_pos =
                self.find_script_y_pos(prescript, ruler);

            script_pos = script_pos.max(&current_script_pos);
        }

        return script_pos;
    }

    fn draw_scripts(&self, scripts: &Vec<MultiscriptDrawableChild<'a>>, canvas: &Canvas, pen_pos: &Point) {
        for script in scripts.iter() {
            script.superscript.draw(canvas, &(pen_pos+&script.superscript_pos));
            script.subscript.draw(canvas, &(pen_pos+&script.subscript_pos));
        }
    }

    fn set_script_positions(scripts: &mut Vec<MultiscriptDrawableChild<'a>>,
                            script_pos: &ScriptPosition, current_pen_x: f32) -> Rect {
        let mut pen_x = current_pen_x;
        let mut y_max = 0f32;
        for script in scripts.iter_mut() {
            let width = script.subscript.bounding_box().width()
                .max(script.superscript.bounding_box().width());

            script.subscript_pos = Point::new(
                pen_x+width-script.subscript.bounding_box().width(),
                script_pos.subscript_baseline_pos-script.subscript.bounding_box().baseline_pos());

            script.superscript_pos = Point::new(
                pen_x+width-script.superscript.bounding_box().width(),
                script_pos.superscript_baseline_pos-script.superscript.bounding_box().baseline_pos());

            pen_x += width;
            y_max = y_max.max(script_pos.subscript_baseline_pos+script.subscript.bounding_box().baseline())
        }

        return Rect::new(pen_x-current_pen_x, y_max);
    }
}

impl<'a> Drawable for MultiscriptDrawable<'a> {
    fn draw(&self, canvas: &Canvas, pen_pos: &Point) {
        self.draw_scripts(&self.prescripts, canvas, pen_pos);
        self.base.draw(canvas, &(pen_pos+&self.base_pos));
        self.draw_scripts(&self.postscripts, canvas, pen_pos);
    }

    fn calculate(&mut self, context: &Context, width_mode: &MeasureMode, height_mode: &MeasureMode) {
        self.base.calculate(context, width_mode, height_mode);

        let ruler = context.platform().get_math_ruler(self.base_size);

        let script_pos = self.find_max_script_y_pos(&self.prescripts, ruler)
            .max(&self.find_max_script_y_pos(&self.postscripts, ruler));

        let subscript_baseline_pos = script_pos.subscript_baseline_pos;

        let base_baseline_pos = script_pos.base_baseline_pos
            .max(self.base.bounding_box().baseline_pos());

        let superscript_baseline_pos = script_pos.superscript_baseline_pos;

        let mut pen_x = 0f32;

        let prescript_bounds = MultiscriptDrawable::set_script_positions(
            &mut self.prescripts, &script_pos, pen_x);
        pen_x += prescript_bounds.width();

        self.base_pos = Point::new(pen_x, base_baseline_pos-self.base.bounding_box().baseline_pos());
        pen_x += self.base.bounding_box().width();

        let postscript_bounds = MultiscriptDrawable::set_script_positions(
            &mut self.postscripts, &script_pos, pen_x);
        pen_x += postscript_bounds.width();

        let height = prescript_bounds.height()
            .max(postscript_bounds.height())
            .max(self.base.bounding_box().height());

        self.bounding_box = BoundingBox::new(
            Rect::new(pen_x, height),
            height - (base_baseline_pos),
            height - (self.base.bounding_box().axis_pos() - self.base.bounding_box().baseline_pos() + base_baseline_pos),
        );
    }

    fn bounding_box(&self) -> &BoundingBox {
        &self.bounding_box
    }
}<commit_msg>Postscript should be aligned to left and prescript should be aligned to right<commit_after>/*
 * Copyright 2017 Sreejith Krishnan R
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

use std::any::Any;

use super::super::{Layout, PresentationLayout, ConcreteLayout};
use ::draw::{Drawable, BoundingBox, MeasureMode};
use ::platform::Context;
use ::paint::{Canvas, Point, MathRuler, Rect};
use ::props::{DisplayStyle, MathSize};

pub struct MmultiscriptLayout {
    pub(crate) base_layout: Box<Layout>,
    pub(crate) prescript_layout: Vec<(Box<Layout>, Box<Layout>)>,
    pub(crate) postscript_layout: Vec<(Box<Layout>, Box<Layout>)>,
    pub(crate) subscript_shift: f32,
    pub(crate) superscript_shift: f32,

    pub(crate) presentation_layout: PresentationLayout,
}

impl Layout for MmultiscriptLayout {
    fn layout<'a>(&'a self, context: &Context) -> Box<Drawable + 'a> {
        let mut wrapper = self.presentation_layout.layout(context);

        wrapper.wrap(MultiscriptDrawable {
            base: self.base_layout.layout(context),
            base_pos: Point::new(0f32, 0f32),
            prescripts: self.prescript_layout.iter().map(|&(ref superscript, ref subscript)| {
                MultiscriptDrawableChild {
                    subscript: subscript.layout(context),
                    subscript_pos: Point::new(0f32, 0f32),
                    superscript: superscript.layout(context),
                    superscript_pos: Point::new(0f32, 0f32),
                }
            }).collect(),
            postscripts: self.postscript_layout.iter().map(|&(ref superscript, ref subscript)| {
                MultiscriptDrawableChild {
                    subscript: subscript.layout(context),
                    subscript_pos: Point::new(0f32, 0f32),
                    superscript: superscript.layout(context),
                    superscript_pos: Point::new(0f32, 0f32),
                }
            }).collect(),
            superscript_shift: self.superscript_shift,
            subscript_shift: self.subscript_shift,
            display_style: self.presentation_layout.display_style,
            base_size: self.presentation_layout.script_level.get_font_size(context, &MathSize::NORMAL),
            bounding_box: BoundingBox::default(),
        });

        wrapper.calculate(context, &MeasureMode::Wrap, &MeasureMode::Wrap);

        Box::new(wrapper)
    }

    fn as_any(&self) -> &Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut Any {
        self
    }
}

struct MultiscriptDrawable<'a> {
    base: Box<Drawable + 'a>,
    base_pos: Point,

    prescripts: Vec<MultiscriptDrawableChild<'a>>,
    postscripts: Vec<MultiscriptDrawableChild<'a>>,

    superscript_shift: f32,
    subscript_shift: f32,
    display_style: DisplayStyle,
    base_size: f32,

    bounding_box: BoundingBox,
}

struct MultiscriptDrawableChild<'a> {
    subscript: Box<Drawable + 'a>,
    subscript_pos: Point,
    superscript: Box<Drawable + 'a>,
    superscript_pos: Point,
}

struct ScriptPosition {
    base_baseline_pos: f32,
    superscript_baseline_pos: f32,
    subscript_baseline_pos: f32
}

impl ScriptPosition {
    fn max(&self, rhs: &ScriptPosition) -> ScriptPosition {
        ScriptPosition {
            base_baseline_pos: self.base_baseline_pos.max(rhs.base_baseline_pos),
            superscript_baseline_pos: self.superscript_baseline_pos.max(rhs.superscript_baseline_pos),
            subscript_baseline_pos: self.subscript_baseline_pos.max(rhs.subscript_baseline_pos),
        }
    }
}

impl<'a> MultiscriptDrawable<'a> {
    fn find_script_y_pos(&self, child: &MultiscriptDrawableChild<'a>, ruler: &MathRuler) -> ScriptPosition {

        let base = &self.base;
        let subscript = &child.subscript;
        let superscript = &child.superscript;

        let has_subscript = subscript.bounding_box().width() > 0f32 || subscript.bounding_box().height() > 0f32;
        let has_superscript = superscript.bounding_box().width() > 0f32 || superscript.bounding_box().height() > 0f32;

        // We will fix the position of superscript first
        let superscript_y_pos = 0f32;

        let base_y_pos = if has_superscript {
            // Recommended shift between baseline of base and baseline of superscript
            let superscript_shift = if self.superscript_shift.is_nan() {
                if self.display_style {
                    ruler.superscript_shift_up()
                } else {
                    ruler.superscript_shift_up_cramped()
                }
            } else {
                self.superscript_shift
            };

            let mut base_y_pos = superscript.bounding_box().baseline_pos() +
                superscript_shift -
                base.bounding_box().baseline_pos();

            // Minimum shift between superscript bottom and baseline of base
            let superscript_bottom_min = ruler.superscript_bottom_min();

            base_y_pos = base_y_pos.max(
                superscript.bounding_box().height() +
                    superscript_bottom_min -
                    base.bounding_box().baseline_pos()
            );

            // Maximum drop of baseline of superscript from top of base
            let superscript_baseline_drop_max = ruler.superscript_baseline_drop_max();

            base_y_pos = base_y_pos.max(
                superscript.bounding_box().baseline_pos() - superscript_baseline_drop_max
            );

            if has_subscript {
                // Maximum shift between superscript bottom and baseline of base when subscript is present
                let superscript_bottom_max_with_subscript = ruler.superscript_bottom_max_with_subscript();
                base_y_pos = base_y_pos.min(
                    superscript.bounding_box().height() +
                        superscript_bottom_max_with_subscript -
                        base.bounding_box().baseline_pos()
                );
            }

            base_y_pos
        } else {
            0f32
        };

        let subscript_y_pos = if has_subscript {
            // Recommended shift between baseline of base and baseline of subscript
            let subscript_shift = if self.subscript_shift.is_nan() {
                ruler.subscript_shift_down()
            } else {
                self.subscript_shift
            };

            let mut subscript_y_pos = base_y_pos + base.bounding_box().baseline_pos() +
                subscript_shift -
                subscript.bounding_box().baseline_pos();

            // Minimum shift between baseline of subscript and bottom of base
            let subscript_baseline_drop_min = ruler.subscript_baseline_drop_min();

            subscript_y_pos = subscript_y_pos.max(
                base_y_pos + base.bounding_box().height() +
                    subscript_baseline_drop_min -
                    subscript.bounding_box().baseline_pos()
            );

            // Maximum shift between top of subscript and baseline of base
            let subscript_top_max = ruler.subscript_top_max();

            subscript_y_pos = subscript_y_pos.max(
                base_y_pos +
                    base.bounding_box().baseline_pos() -
                    subscript_top_max
            );

            if has_superscript {
                // Minimum gap between bottom of superscript and top of subscript
                let sub_superscript_gap_min = ruler.sub_superscript_gap_min();

                subscript_y_pos = subscript_y_pos.max(
                    superscript_y_pos +
                        superscript.bounding_box().height() +
                        sub_superscript_gap_min
                );
            }

            subscript_y_pos
        } else {
            base_y_pos + base.bounding_box().height()
        };

        return ScriptPosition {
            base_baseline_pos: base_y_pos + base.bounding_box().baseline_pos(),
            superscript_baseline_pos: superscript_y_pos + superscript.bounding_box().baseline_pos(),
            subscript_baseline_pos: subscript_y_pos + subscript.bounding_box().baseline_pos(),
        }
    }

    fn find_max_script_y_pos(&self, scripts: &Vec<MultiscriptDrawableChild<'a>>, ruler: &MathRuler) -> ScriptPosition {
        let mut script_pos = ScriptPosition {
            base_baseline_pos: 0.0,
            superscript_baseline_pos: 0.0,
            subscript_baseline_pos: 0.0,
        };

        for prescript in scripts.iter() {
            let current_script_pos =
                self.find_script_y_pos(prescript, ruler);

            script_pos = script_pos.max(&current_script_pos);
        }

        return script_pos;
    }

    fn draw_scripts(&self, scripts: &Vec<MultiscriptDrawableChild<'a>>, canvas: &Canvas, pen_pos: &Point) {
        for script in scripts.iter() {
            script.superscript.draw(canvas, &(pen_pos+&script.superscript_pos));
            script.subscript.draw(canvas, &(pen_pos+&script.subscript_pos));
        }
    }

    fn set_script_positions(scripts: &mut Vec<MultiscriptDrawableChild<'a>>,
                            script_pos: &ScriptPosition, current_pen_x: f32, is_post_script: bool) -> Rect {
        let mut pen_x = current_pen_x;
        let mut y_max = 0f32;
        for script in scripts.iter_mut() {
            let width = script.subscript.bounding_box().width()
                .max(script.superscript.bounding_box().width());

            let subscript_x_pos = if is_post_script {
                pen_x
            } else {
                pen_x+width-script.subscript.bounding_box().width()
            };

            script.subscript_pos = Point::new(
                subscript_x_pos,
                script_pos.subscript_baseline_pos-script.subscript.bounding_box().baseline_pos());

            let superscript_x_pos = if is_post_script {
                pen_x
            } else {
                pen_x+width-script.superscript.bounding_box().width()
            };

            script.superscript_pos = Point::new(
                superscript_x_pos,
                script_pos.superscript_baseline_pos-script.superscript.bounding_box().baseline_pos());

            pen_x += width;
            y_max = y_max.max(script_pos.subscript_baseline_pos+script.subscript.bounding_box().baseline())
        }

        return Rect::new(pen_x-current_pen_x, y_max);
    }
}

impl<'a> Drawable for MultiscriptDrawable<'a> {
    fn draw(&self, canvas: &Canvas, pen_pos: &Point) {
        self.draw_scripts(&self.prescripts, canvas, pen_pos);
        self.base.draw(canvas, &(pen_pos+&self.base_pos));
        self.draw_scripts(&self.postscripts, canvas, pen_pos);
    }

    fn calculate(&mut self, context: &Context, width_mode: &MeasureMode, height_mode: &MeasureMode) {
        self.base.calculate(context, width_mode, height_mode);

        let ruler = context.platform().get_math_ruler(self.base_size);

        let script_pos = self.find_max_script_y_pos(&self.prescripts, ruler)
            .max(&self.find_max_script_y_pos(&self.postscripts, ruler));

        let subscript_baseline_pos = script_pos.subscript_baseline_pos;

        let base_baseline_pos = script_pos.base_baseline_pos
            .max(self.base.bounding_box().baseline_pos());

        let superscript_baseline_pos = script_pos.superscript_baseline_pos;

        let mut pen_x = 0f32;

        let prescript_bounds = MultiscriptDrawable::set_script_positions(
            &mut self.prescripts, &script_pos, pen_x, false);
        pen_x += prescript_bounds.width();

        self.base_pos = Point::new(pen_x, base_baseline_pos-self.base.bounding_box().baseline_pos());
        pen_x += self.base.bounding_box().width();

        let postscript_bounds = MultiscriptDrawable::set_script_positions(
            &mut self.postscripts, &script_pos, pen_x, true);
        pen_x += postscript_bounds.width();

        let height = prescript_bounds.height()
            .max(postscript_bounds.height())
            .max(self.base.bounding_box().height());

        self.bounding_box = BoundingBox::new(
            Rect::new(pen_x, height),
            height - (base_baseline_pos),
            height - (self.base.bounding_box().axis_pos() - self.base.bounding_box().baseline_pos() + base_baseline_pos),
        );
    }

    fn bounding_box(&self) -> &BoundingBox {
        &self.bounding_box
    }
}<|endoftext|>


<commit_before>pub mod ll {

    use libc::{uint32_t, uint64_t};

    //SDL_timer.h
    extern "C" {
        pub fn SDL_GetTicks() -> uint32_t;
        pub fn SDL_GetPerformanceCounter() -> uint64_t;
        pub fn SDL_GetPerformanceFrequency() -> uint64_t;
        pub fn SDL_Delay(ms: uint32_t);
    }
    //TODO: Figure out what to do with the timer callback functions
}

pub fn get_ticks() -> uint {
    unsafe { ll::SDL_GetTicks() as uint }
}

pub fn get_performance_counter() -> u64 {
    unsafe { ll::SDL_GetPerformanceCounter() }
}

pub fn get_performance_frequency() -> u64 {
    unsafe { ll::SDL_GetPerformanceFrequency() }
}

pub fn delay(ms: uint) {
    unsafe { ll::SDL_Delay(ms as u32) }
}
<commit_msg>add Timer implementation<commit_after>use std::cast;
use libc::{uint32_t, c_void};
use std::raw;

#[allow(non_camel_case_types)]
pub mod ll {
    use libc::{uint32_t, uint64_t, c_void, c_int};

    //SDL_timer.h
    pub type SDL_TimerCallback =
        ::std::option::Option<extern "C" fn(arg1: uint32_t, arg2: *c_void)
                                            -> uint32_t>;
    pub type SDL_TimerID = c_int;
    extern "C" {
        pub fn SDL_GetTicks() -> uint32_t;
        pub fn SDL_GetPerformanceCounter() -> uint64_t;
        pub fn SDL_GetPerformanceFrequency() -> uint64_t;
        pub fn SDL_Delay(ms: uint32_t);

        pub fn SDL_AddTimer(interval: uint32_t, callback: SDL_TimerCallback,
                            param: *c_void) -> SDL_TimerID;
        pub fn SDL_RemoveTimer(id: SDL_TimerID) -> c_int;
    }
}

pub fn get_ticks() -> uint {
    unsafe { ll::SDL_GetTicks() as uint }
}

pub fn get_performance_counter() -> u64 {
    unsafe { ll::SDL_GetPerformanceCounter() }
}

pub fn get_performance_frequency() -> u64 {
    unsafe { ll::SDL_GetPerformanceFrequency() }
}

pub fn delay(ms: uint) {
    unsafe { ll::SDL_Delay(ms as u32) }
}

pub struct Timer<'a> {
    delay: uint,
    raw: ll::SDL_TimerID,
    closure: raw::Closure,
    remove_on_drop: bool,
}

impl<'a> Timer<'a> {
    pub fn new<'a>(delay: uint, callback: ||: 'a -> uint, remove_on_drop: bool) -> Timer<'a> {
        unsafe {
            let c_param = cast::transmute::<_, raw::Closure>(callback);
            Timer { delay: delay, raw: 0, closure: c_param, remove_on_drop: remove_on_drop }
        }
    }

    pub fn start(&mut self) {
        unsafe {
            let timer_id = ll::SDL_AddTimer(self.delay as u32, Some(c_timer_callback), cast::transmute(&self.closure));
            self.raw = timer_id;
        }
    }

    pub fn remove(&mut self) -> bool {
        let ret = unsafe { ll::SDL_RemoveTimer(self.raw) };
        if self.raw != 0 {
            self.raw = 0
        }
        ret == 1
    }
}

impl<'a> Drop for Timer<'a> {
    fn drop(&mut self) {
        if self.remove_on_drop {
            let ret = unsafe { ll::SDL_RemoveTimer(self.raw) };
            if ret != 1 {
                println!("error dropping timer {}, maybe already removed.", self.raw);
            }
        }
    }
}

extern "C" fn c_timer_callback(_interval: uint32_t, param: *c_void) -> uint32_t {
    let f : &|| -> uint = unsafe { cast::transmute(param) };
    (*f)() as uint32_t
}
<|endoftext|>


<commit_before>use std::rc::Rc;
use std::cell::RefCell;
use std::usize;

use ast::*;
use value::*;
use operations;
use environment::Environment;
use function::*;
use runtime::*;
use typechecker::ConstraintType;

pub struct AstWalkInterpreter {
    pub root_env: Rc<RefCell<Environment>>,
}

impl AstWalkInterpreter {
    pub fn new() -> AstWalkInterpreter {
        AstWalkInterpreter { root_env: Environment::new_root() }
    }
}

fn interpret_program(program: &[StmtNode],
                     env: Rc<RefCell<Environment>>)
                     -> Result<Option<StmtResult>, RuntimeErrorWithPosition> {
    let result = interpret_statements(program, env.clone())?;
    Ok(result)
}

fn interpret_statements(statements: &[StmtNode],
                        env: Rc<RefCell<Environment>>)
                        -> Result<Option<StmtResult>, RuntimeErrorWithPosition> {
    let mut last_result = None;
    for statement in statements.iter() {
        last_result = Some(interpret_statement(statement, env.clone())?);
    }
    Ok(last_result)
}

fn interpret_statement(s: &StmtNode,
                       env: Rc<RefCell<Environment>>)
                       -> Result<StmtResult, RuntimeErrorWithPosition> {
    match s.data {
        Stmt::VarDecl(ref variable, ref expr) => {
            let possible_val = interpret_expr(expr, env.clone())?;
            let val = check_val_for_none_error(&possible_val, expr)?;
            match *variable {
                Variable::Identifier(_, ref name) => {
                    env.borrow_mut().declare(name, &val);
                }
            };
            Ok(StmtResult::None)
        }
        Stmt::Assign(ref lhs_expr, ref expr) => {
            let possible_val = interpret_expr(expr, env.clone())?;
            let val = check_val_for_none_error(&possible_val, expr)?;
            match lhs_expr.data {
                LhsExpr::Identifier(ref id) => {
                    if !env.borrow_mut().set(id, val) {
                        return Err((RuntimeError::UndeclaredAssignment(id.clone()), lhs_expr.pos));
                    }
                }
            };
            Ok(StmtResult::None)
        }
        Stmt::Block(ref statements) => {
            let child_env = Environment::create_child(env.clone());
            let mut last_result = StmtResult::None;
            for statement in statements.iter() {
                last_result = interpret_statement(statement, child_env.clone())?;
                if let StmtResult::Break = last_result {
                    return Ok(StmtResult::Break);
                } else if let StmtResult::Return(_) = last_result {
                    return Ok(last_result);
                }
            }
            Ok(last_result)
        }
        Stmt::Expr(ref expr) => {
            let val = interpret_expr(expr, env.clone())?;
            match val {
                None => Ok(StmtResult::None),
                Some(x) => Ok(StmtResult::Value(x)),
            }
        }
        Stmt::IfThen(IfThenStmt {
                         ref cond,
                         ref then_block,
                         ref maybe_else_block,
                     }) => {
            let possible_val = interpret_expr(cond, env.clone())?;
            let val = check_val_for_none_error(&possible_val, cond)?;
            if val.is_truthy() {
                let result = interpret_statement(then_block, env.clone())?;
                if let StmtResult::Break = result {
                    return Ok(StmtResult::Break);
                } else if let StmtResult::Return(_) = result {
                    return Ok(result);
                }
            } else if maybe_else_block.is_some() {
                let else_block = maybe_else_block.clone().unwrap();
                let result = interpret_statement(&else_block, env.clone())?;
                if let StmtResult::Break = result {
                    return Ok(StmtResult::Break);
                } else if let StmtResult::Return(_) = result {
                    return Ok(result);
                }
            }
            Ok(StmtResult::None)
        }
        Stmt::Loop(ref block) => {
            let child_env = Environment::create_child(env.clone());
            loop {
                let result = interpret_statement(block, child_env.clone())?;
                if let StmtResult::Break = result {
                    break;
                } else if let StmtResult::Return(_) = result {
                    return Ok(result);
                }
            }
            Ok(StmtResult::None)
        }
        Stmt::Return(ref possible_expr) => {
            match *possible_expr {
                Some(ref expr) => {
                    let possible_val = interpret_expr(expr, env.clone())?;
                    let val = check_val_for_none_error(&possible_val, expr)?;
                    Ok(StmtResult::Return(Some(val)))
                }
                None => Ok(StmtResult::Return(None)),
            }
        }
        Stmt::Break => Ok(StmtResult::Break),
        Stmt::Empty => Ok(StmtResult::None),
    }
}
fn interpret_expr(e: &ExprNode,
                  env: Rc<RefCell<Environment>>)
                  -> Result<Option<Value>, RuntimeErrorWithPosition> {
    match e.data {
        Expr::Literal(ref x) => Ok(Some(Value::from(x.data.clone()))),
        Expr::Identifier(ref id) => {
            match env.borrow_mut().get_value(id) {
                Some(v) => Ok(Some(v)),
                None => Err((RuntimeError::ReferenceError(id.clone()), e.pos)),
            }
        }
        Expr::Tuple(ref elems) => {
            let mut values = Vec::new();
            for elem_expr in elems {
                let possible_val = interpret_expr(elem_expr, env.clone())?;
                let val = check_val_for_none_error(&possible_val, elem_expr)?;
                values.push(val);
            }
            Ok(Some(Value::Tuple(values)))
        }
        Expr::Unary(ref op, ref expr) => {
            let possible_val = interpret_expr(expr, env.clone())?;
            let val = check_val_for_none_error(&possible_val, expr)?;
            match *op {
                UnOp::Neg => {
                    match operations::unary_minus(val) {
                        Ok(v) => Ok(Some(v)),
                        Err(err) => Err((err, e.pos)),
                    }
                }
            }
        }
        Expr::UnaryLogical(ref op, ref expr) => {
            let possible_val = interpret_expr(expr, env.clone())?;
            let val = check_val_for_none_error(&possible_val, expr)?;
            match *op {
                LogicalUnOp::Not => Ok(Some(Value::Bool(!val.is_truthy()))),
            }
        }
        Expr::Binary(ref expr1, ref op, ref expr2) => {
            let possible_val_1 = interpret_expr(expr1, env.clone())?;
            let val1 = check_val_for_none_error(&possible_val_1, expr1)?;
            let possible_val_2 = interpret_expr(expr2, env.clone())?;
            let val2 = check_val_for_none_error(&possible_val_2, expr2)?;
            let retval = match *op {
                BinOp::Add => operations::add(val1, val2),
                BinOp::Sub => operations::subtract(val1, val2),
                BinOp::Mul => operations::multiply(val1, val2),
                BinOp::Div => operations::divide(val1, val2),
                BinOp::Lt => operations::less_than(val1, val2),
                BinOp::Lte => operations::less_than_or_equal(val1, val2),
                BinOp::Gt => operations::greater_than(val1, val2),
                BinOp::Gte => operations::greater_than_or_equal(val1, val2),
                BinOp::Eq => Ok(Value::Bool(val1 == val2)),
            };
            match retval {
                Ok(v) => Ok(Some(v)),
                Err(err) => Err((err, e.pos)),
            }
        }
        Expr::BinaryLogical(ref expr1, ref op, ref expr2) => {
            match *op {
                LogicalBinOp::And => {
                    let possible_val_1 = interpret_expr(expr1, env.clone())?;
                    let val1 = check_val_for_none_error(&possible_val_1, expr1)?;
                    if !val1.is_truthy() {
                        return Ok(Some(Value::Bool(false)));
                    }
                    let possible_val_2 = interpret_expr(expr2, env.clone())?;
                    let val2 = check_val_for_none_error(&possible_val_2, expr2)?;
                    Ok(Some(Value::Bool(val2.is_truthy())))
                }
                LogicalBinOp::Or => {
                    let possible_val_1 = interpret_expr(expr1, env.clone())?;
                    let val1 = check_val_for_none_error(&possible_val_1, expr1)?;
                    if val1.is_truthy() {
                        return Ok(Some(Value::Bool(true)));
                    }
                    let possible_val_2 = interpret_expr(expr2, env.clone())?;
                    let val2 = check_val_for_none_error(&possible_val_2, expr2)?;
                    Ok(Some(Value::Bool(val2.is_truthy())))
                }
            }
        }
        Expr::MemberByIdx(ref object_expr, ref index_expr) => {
            let possible_object = interpret_expr(object_expr, env.clone())?;
            let object = check_val_for_none_error(&possible_object, object_expr)?;
            let possible_index = interpret_expr(index_expr, env.clone())?;
            let index = check_val_for_none_error(&possible_index, index_expr)?;
            match object {
                Value::Tuple(ref v) => {
                    match index {
                        Value::Number(Number::Integer(i)) => {
                            if i < 0 {
                                return Err((RuntimeError::IndexOutOfBounds(i), e.pos));
                            }
                            match v.get(i as usize) {
                                Some(x) => Ok(Some(x.clone())),
                                None => Err((RuntimeError::IndexOutOfBounds(i), e.pos)),
                            }
                        }
                        non_int_index => {
                            Err((RuntimeError::NonIntegralSubscript(non_int_index.get_type()),
                                 index_expr.pos))
                        }
                    }
                }
                obj => {
                    Err((RuntimeError::SubscriptOnNonSubscriptable(obj.get_type()),
                         object_expr.pos))
                }
            }
        }
        Expr::FnDef(FnDefExpr {
                        ref maybe_id,
                        ref params,
                        ref body,
                        ref maybe_ret_type,
                    }) => {
            let (param_names, param_types): (Vec<String>, Vec<Option<ConstraintType>>) =
                params.iter().cloned().unzip();
            let func = Function::User {
                ret_type: maybe_ret_type.clone(),
                call_sign: CallSign {
                    num_params: params.len(),
                    variadic: false,
                    param_types: param_types,
                },
                param_names: param_names.clone(),
                body: body.clone(),
                env: env.clone(),
            };
            let func_val = Value::Function(Box::new(func));
            if let Some(ref id) = *maybe_id {
                env.borrow_mut().declare(id, &func_val);
            }
            Ok(Some(func_val))
        }
        Expr::FnCall(ref expr, ref args) => {
            let possible_val = interpret_expr(expr, env.clone())?;
            let val = check_val_for_none_error(&possible_val, expr)?;
            let func = match val {
                Value::Function(f) => f,
                v => {
                    if let Expr::Identifier(ref id) = expr.data {
                        return Err((RuntimeError::CallToNonFunction(Some(id.clone()),
                                                                    v.get_type()),
                                    expr.pos));
                    }
                    return Err((RuntimeError::CallToNonFunction(None, v.get_type()), expr.pos));
                }
            };
            let mut arg_vals = Vec::new();
            for arg in args.iter() {
                let possible_val = interpret_expr(arg, env.clone())?;
                let val = check_val_for_none_error(&possible_val, arg)?;
                arg_vals.push(val);
            }

            let call_sign = func.get_call_sign();
            check_args_compat(&arg_vals, &call_sign, e)?;

            let call_func_result = call_func(&func, &arg_vals);
            match call_func_result {
                Ok(possible_val) => Ok(possible_val),
                Err(runtime_error) => Err((runtime_error, e.pos)),
            }
        }
    }
}

pub fn call_func(func: &Function, arg_vals: &[Value]) -> Result<Option<Value>, RuntimeError> {
    match *func {
        Function::NativeVoid(_, ref native_fn) => {
            native_fn(arg_vals.to_vec())?;
            Ok(None)
        }
        Function::NativeReturning(_, ref native_fn) => Ok(Some(native_fn(arg_vals.to_vec())?)),
        Function::User {
            ref param_names,
            ref body,
            ref env,
            ..
        } => {
            // TODO: returning
            let function_env = Environment::create_child(env.clone());
            for (param, arg) in param_names.iter().zip(arg_vals.iter()) {
                function_env.borrow_mut().declare(param, arg);
            }
            let inner_env = Environment::create_child(function_env);
            let result = interpret_statement(body, inner_env);
            match result {
                Err(error_with_position) => {
                    Err(RuntimeError::InsideFunctionCall(Box::new(error_with_position)))
                }
                Ok(statement_result) => {
                    if let StmtResult::Return(possible_val) = statement_result {
                        match possible_val {
                            Some(val) => Ok(Some(val)),
                            None => Ok(None),
                        }
                    } else {
                        Ok(None)
                    }
                }
            }
        }
    }
}

fn check_val_for_none_error(val: &Option<Value>,
                            expr: &ExprNode)
                            -> Result<Value, RuntimeErrorWithPosition> {
    if val.is_none() {
        if let Expr::FnCall(ref f_expr, _) = expr.data {
            if let Expr::Identifier(ref id) = f_expr.data {
                return Err((RuntimeError::NoneError(Some(id.clone())), expr.pos));
            }
            return Err((RuntimeError::NoneError(None), expr.pos));
        }
    }
    Ok(val.clone().unwrap())
}

fn check_args_compat(arg_vals: &[Value],
                     call_sign: &CallSign,
                     expr: &ExprNode)
                     -> Result<(), RuntimeErrorWithPosition> {
    if !call_sign.variadic && call_sign.num_params != arg_vals.len() {
        if let Expr::Identifier(ref id) = expr.data {
            return Err((RuntimeError::ArgumentLength(Some(id.clone())), expr.pos));
        }
        return Err((RuntimeError::ArgumentLength(None), expr.pos));
    }
    Ok(())
}

impl Interpreter for AstWalkInterpreter {
    fn run_ast_as_statements(&mut self,
                             statements: &[StmtNode])
                             -> Result<Option<StmtResult>, RuntimeErrorWithPosition> {
        interpret_statements(statements, self.root_env.clone())
    }

    fn run_ast_as_program(&mut self,
                          program: &[StmtNode])
                          -> Result<Option<StmtResult>, RuntimeErrorWithPosition> {
        interpret_program(program, self.root_env.clone())
    }
}
<commit_msg>Refactor check_val_for_none_error usage to use interpret_expr_as_value<commit_after>use std::rc::Rc;
use std::cell::RefCell;
use std::usize;

use ast::*;
use value::*;
use operations;
use environment::Environment;
use function::*;
use runtime::*;
use typechecker::ConstraintType;

pub struct AstWalkInterpreter {
    pub root_env: Rc<RefCell<Environment>>,
}

impl AstWalkInterpreter {
    pub fn new() -> AstWalkInterpreter {
        AstWalkInterpreter { root_env: Environment::new_root() }
    }
}

fn interpret_program(program: &[StmtNode],
                     env: Rc<RefCell<Environment>>)
                     -> Result<Option<StmtResult>, RuntimeErrorWithPosition> {
    let result = interpret_statements(program, env.clone())?;
    Ok(result)
}

fn interpret_statements(statements: &[StmtNode],
                        env: Rc<RefCell<Environment>>)
                        -> Result<Option<StmtResult>, RuntimeErrorWithPosition> {
    let mut last_result = None;
    for statement in statements.iter() {
        last_result = Some(interpret_statement(statement, env.clone())?);
    }
    Ok(last_result)
}

fn interpret_statement(s: &StmtNode,
                       env: Rc<RefCell<Environment>>)
                       -> Result<StmtResult, RuntimeErrorWithPosition> {
    match s.data {
        Stmt::VarDecl(ref variable, ref expr) => {
            let val = interpret_expr_as_value(expr, env.clone())?;
            match *variable {
                Variable::Identifier(_, ref name) => {
                    env.borrow_mut().declare(name, &val);
                }
            };
            Ok(StmtResult::None)
        }
        Stmt::Assign(ref lhs_expr, ref expr) => {
            let val = interpret_expr_as_value(expr, env.clone())?;
            match lhs_expr.data {
                LhsExpr::Identifier(ref id) => {
                    if !env.borrow_mut().set(id, val) {
                        return Err((RuntimeError::UndeclaredAssignment(id.clone()), lhs_expr.pos));
                    }
                }
            };
            Ok(StmtResult::None)
        }
        Stmt::Block(ref statements) => {
            let child_env = Environment::create_child(env.clone());
            let mut last_result = StmtResult::None;
            for statement in statements.iter() {
                last_result = interpret_statement(statement, child_env.clone())?;
                if let StmtResult::Break = last_result {
                    return Ok(StmtResult::Break);
                } else if let StmtResult::Return(_) = last_result {
                    return Ok(last_result);
                }
            }
            Ok(last_result)
        }
        Stmt::Expr(ref expr) => {
            let val = interpret_expr(expr, env.clone())?;
            match val {
                None => Ok(StmtResult::None),
                Some(x) => Ok(StmtResult::Value(x)),
            }
        }
        Stmt::IfThen(IfThenStmt {
                         ref cond,
                         ref then_block,
                         ref maybe_else_block,
                     }) => {
            let val = interpret_expr_as_value(cond, env.clone())?;
            if val.is_truthy() {
                let result = interpret_statement(then_block, env.clone())?;
                if let StmtResult::Break = result {
                    return Ok(StmtResult::Break);
                } else if let StmtResult::Return(_) = result {
                    return Ok(result);
                }
            } else if maybe_else_block.is_some() {
                let else_block = maybe_else_block.clone().unwrap();
                let result = interpret_statement(&else_block, env.clone())?;
                if let StmtResult::Break = result {
                    return Ok(StmtResult::Break);
                } else if let StmtResult::Return(_) = result {
                    return Ok(result);
                }
            }
            Ok(StmtResult::None)
        }
        Stmt::Loop(ref block) => {
            let child_env = Environment::create_child(env.clone());
            loop {
                let result = interpret_statement(block, child_env.clone())?;
                if let StmtResult::Break = result {
                    break;
                } else if let StmtResult::Return(_) = result {
                    return Ok(result);
                }
            }
            Ok(StmtResult::None)
        }
        Stmt::Return(ref possible_expr) => {
            match *possible_expr {
                Some(ref expr) => {
                    let val = interpret_expr_as_value(expr, env.clone())?;
                    Ok(StmtResult::Return(Some(val)))
                }
                None => Ok(StmtResult::Return(None)),
            }
        }
        Stmt::Break => Ok(StmtResult::Break),
        Stmt::Empty => Ok(StmtResult::None),
    }
}

fn interpret_expr_as_value(expr: &ExprNode,
                           env: Rc<RefCell<Environment>>)
                           -> Result<Value, RuntimeErrorWithPosition> {
    let possible_val = interpret_expr(expr, env.clone())?;
    if possible_val.is_none() {
        if let Expr::FnCall(ref f_expr, _) = expr.data {
            if let Expr::Identifier(ref id) = f_expr.data {
                return Err((RuntimeError::NoneError(Some(id.clone())), expr.pos));
            }
            return Err((RuntimeError::NoneError(None), expr.pos));
        } else {
            unreachable!();
        }
    }
    Ok(possible_val.clone().unwrap())
}

fn interpret_expr(e: &ExprNode,
                  env: Rc<RefCell<Environment>>)
                  -> Result<Option<Value>, RuntimeErrorWithPosition> {
    match e.data {
        Expr::Literal(ref x) => Ok(Some(Value::from(x.data.clone()))),
        Expr::Identifier(ref id) => {
            match env.borrow_mut().get_value(id) {
                Some(v) => Ok(Some(v)),
                None => Err((RuntimeError::ReferenceError(id.clone()), e.pos)),
            }
        }
        Expr::Tuple(ref elems) => {
            let mut values = Vec::new();
            for elem_expr in elems {
                let val = interpret_expr_as_value(elem_expr, env.clone())?;
                values.push(val);
            }
            Ok(Some(Value::Tuple(values)))
        }
        Expr::Unary(ref op, ref expr) => {
            let val = interpret_expr_as_value(expr, env.clone())?;
            match *op {
                UnOp::Neg => {
                    match operations::unary_minus(val) {
                        Ok(v) => Ok(Some(v)),
                        Err(err) => Err((err, e.pos)),
                    }
                }
            }
        }
        Expr::UnaryLogical(ref op, ref expr) => {
            let val = interpret_expr_as_value(expr, env.clone())?;
            match *op {
                LogicalUnOp::Not => Ok(Some(Value::Bool(!val.is_truthy()))),
            }
        }
        Expr::Binary(ref expr1, ref op, ref expr2) => {
            let val1 = interpret_expr_as_value(expr1, env.clone())?;
            let val2 = interpret_expr_as_value(expr2, env.clone())?;
            let retval = match *op {
                BinOp::Add => operations::add(val1, val2),
                BinOp::Sub => operations::subtract(val1, val2),
                BinOp::Mul => operations::multiply(val1, val2),
                BinOp::Div => operations::divide(val1, val2),
                BinOp::Lt => operations::less_than(val1, val2),
                BinOp::Lte => operations::less_than_or_equal(val1, val2),
                BinOp::Gt => operations::greater_than(val1, val2),
                BinOp::Gte => operations::greater_than_or_equal(val1, val2),
                BinOp::Eq => Ok(Value::Bool(val1 == val2)),
            };
            match retval {
                Ok(v) => Ok(Some(v)),
                Err(err) => Err((err, e.pos)),
            }
        }
        Expr::BinaryLogical(ref expr1, ref op, ref expr2) => {
            match *op {
                LogicalBinOp::And => {
                    let val1 = interpret_expr_as_value(expr1, env.clone())?;
                    if !val1.is_truthy() {
                        return Ok(Some(Value::Bool(false)));
                    }
                    let val2 = interpret_expr_as_value(expr2, env.clone())?;
                    Ok(Some(Value::Bool(val2.is_truthy())))
                }
                LogicalBinOp::Or => {
                    let val1 = interpret_expr_as_value(expr1, env.clone())?;
                    if val1.is_truthy() {
                        return Ok(Some(Value::Bool(true)));
                    }
                    let val2 = interpret_expr_as_value(expr2, env.clone())?;
                    Ok(Some(Value::Bool(val2.is_truthy())))
                }
            }
        }
        Expr::MemberByIdx(ref object_expr, ref index_expr) => {
            let object = interpret_expr_as_value(object_expr, env.clone())?;
            let index = interpret_expr_as_value(index_expr, env.clone())?;
            match object {
                Value::Tuple(ref v) => {
                    match index {
                        Value::Number(Number::Integer(i)) => {
                            if i < 0 {
                                return Err((RuntimeError::IndexOutOfBounds(i), e.pos));
                            }
                            match v.get(i as usize) {
                                Some(x) => Ok(Some(x.clone())),
                                None => Err((RuntimeError::IndexOutOfBounds(i), e.pos)),
                            }
                        }
                        non_int_index => {
                            Err((RuntimeError::NonIntegralSubscript(non_int_index.get_type()),
                                 index_expr.pos))
                        }
                    }
                }
                obj => {
                    Err((RuntimeError::SubscriptOnNonSubscriptable(obj.get_type()),
                         object_expr.pos))
                }
            }
        }
        Expr::FnDef(FnDefExpr {
                        ref maybe_id,
                        ref params,
                        ref body,
                        ref maybe_ret_type,
                    }) => {
            let (param_names, param_types): (Vec<String>, Vec<Option<ConstraintType>>) =
                params.iter().cloned().unzip();
            let func = Function::User {
                ret_type: maybe_ret_type.clone(),
                call_sign: CallSign {
                    num_params: params.len(),
                    variadic: false,
                    param_types: param_types,
                },
                param_names: param_names.clone(),
                body: body.clone(),
                env: env.clone(),
            };
            let func_val = Value::Function(Box::new(func));
            if let Some(ref id) = *maybe_id {
                env.borrow_mut().declare(id, &func_val);
            }
            Ok(Some(func_val))
        }
        Expr::FnCall(ref expr, ref args) => {
            let val = interpret_expr_as_value(expr, env.clone())?;
            let func = match val {
                Value::Function(f) => f,
                v => {
                    if let Expr::Identifier(ref id) = expr.data {
                        return Err((RuntimeError::CallToNonFunction(Some(id.clone()),
                                                                    v.get_type()),
                                    expr.pos));
                    }
                    return Err((RuntimeError::CallToNonFunction(None, v.get_type()), expr.pos));
                }
            };
            let mut arg_vals = Vec::new();
            for arg in args.iter() {
                let val = interpret_expr_as_value(arg, env.clone())?;
                arg_vals.push(val);
            }

            let call_sign = func.get_call_sign();
            check_args_compat(&arg_vals, &call_sign, e)?;

            let call_func_result = call_func(&func, &arg_vals);
            match call_func_result {
                Ok(possible_val) => Ok(possible_val),
                Err(runtime_error) => Err((runtime_error, e.pos)),
            }
        }
    }
}

pub fn call_func(func: &Function, arg_vals: &[Value]) -> Result<Option<Value>, RuntimeError> {
    match *func {
        Function::NativeVoid(_, ref native_fn) => {
            native_fn(arg_vals.to_vec())?;
            Ok(None)
        }
        Function::NativeReturning(_, ref native_fn) => Ok(Some(native_fn(arg_vals.to_vec())?)),
        Function::User {
            ref param_names,
            ref body,
            ref env,
            ..
        } => {
            // TODO: returning
            let function_env = Environment::create_child(env.clone());
            for (param, arg) in param_names.iter().zip(arg_vals.iter()) {
                function_env.borrow_mut().declare(param, arg);
            }
            let inner_env = Environment::create_child(function_env);
            let result = interpret_statement(body, inner_env);
            match result {
                Err(error_with_position) => {
                    Err(RuntimeError::InsideFunctionCall(Box::new(error_with_position)))
                }
                Ok(statement_result) => {
                    if let StmtResult::Return(possible_val) = statement_result {
                        match possible_val {
                            Some(val) => Ok(Some(val)),
                            None => Ok(None),
                        }
                    } else {
                        Ok(None)
                    }
                }
            }
        }
    }
}

fn check_args_compat(arg_vals: &[Value],
                     call_sign: &CallSign,
                     expr: &ExprNode)
                     -> Result<(), RuntimeErrorWithPosition> {
    if !call_sign.variadic && call_sign.num_params != arg_vals.len() {
        if let Expr::Identifier(ref id) = expr.data {
            return Err((RuntimeError::ArgumentLength(Some(id.clone())), expr.pos));
        }
        return Err((RuntimeError::ArgumentLength(None), expr.pos));
    }
    Ok(())
}

impl Interpreter for AstWalkInterpreter {
    fn run_ast_as_statements(&mut self,
                             statements: &[StmtNode])
                             -> Result<Option<StmtResult>, RuntimeErrorWithPosition> {
        interpret_statements(statements, self.root_env.clone())
    }

    fn run_ast_as_program(&mut self,
                          program: &[StmtNode])
                          -> Result<Option<StmtResult>, RuntimeErrorWithPosition> {
        interpret_program(program, self.root_env.clone())
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

//! A bounded tokio [`Handle`]. Only a bounded number of tasks can run
//! concurrently when spawned through this executor, defined by the initial
//! `capacity`.

use futures::future::{Future, FutureExt};
use futures_semaphore::Semaphore;
use tokio::{runtime::Handle, task::JoinHandle};

#[derive(Clone, Debug)]
pub struct BoundedExecutor {
    semaphore: Semaphore,
    executor: Handle,
}

/// Returned by [`BoundedExecutor::try_spawn`] if it is at capacity.
pub enum SpawnError {
    AtCapacity,
}

impl BoundedExecutor {
    /// Create a new `BoundedExecutor` from an existing tokio [`Handle`]
    /// with a maximum concurrent task capacity of `capacity`.
    pub fn new(capacity: usize, executor: Handle) -> Self {
        let semaphore = Semaphore::new(capacity);
        Self {
            semaphore,
            executor,
        }
    }

    /// Spawn a [`Future`] on the `BoundedExecutor`. This function is async and
    /// will block if the executor is at capacity.
    pub async fn spawn<F>(&self, f: F) -> JoinHandle<F::Output>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        let spawn_permit = self.semaphore.acquire().await;
        let f = f.map(move |ret| {
            // Release the permit back to the semaphore when this task completes.
            drop(spawn_permit);
            ret
        });
        self.executor.spawn(f)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use futures::{executor::block_on, future::Future};
    use std::{
        sync::atomic::{AtomicU32, Ordering},
        time::Duration,
    };
    use tokio::{runtime::Runtime, time::delay_for};

    fn yield_task() -> impl Future<Output = ()> {
        delay_for(Duration::from_millis(1)).map(|_| ())
    }

    // spawn NUM_TASKS futures on a BoundedExecutor, ensuring that no more than
    // MAX_WORKERS ever enter the critical section.
    #[test]
    fn concurrent_bounded_executor() {
        const MAX_WORKERS: u32 = 20;
        const NUM_TASKS: u32 = 1000;
        static WORKERS: AtomicU32 = AtomicU32::new(0);
        static COMPLETED_TASKS: AtomicU32 = AtomicU32::new(0);

        let rt = Runtime::new().unwrap();
        let executor = rt.handle().clone();
        let executor = BoundedExecutor::new(MAX_WORKERS as usize, executor);

        for _ in 0..NUM_TASKS {
            block_on(executor.spawn(async move {
                // acquired permit, there should only ever be MAX_WORKERS in this
                // critical section

                let prev_workers = WORKERS.fetch_add(1, Ordering::SeqCst);
                assert!(prev_workers < MAX_WORKERS);

                // yield back to the tokio scheduler
                yield_task().await;

                let prev_workers = WORKERS.fetch_sub(1, Ordering::SeqCst);
                assert!(prev_workers > 0 && prev_workers <= MAX_WORKERS);

                COMPLETED_TASKS.fetch_add(1, Ordering::Relaxed);
            }));
        }

        // spin until completed
        loop {
            let completed = COMPLETED_TASKS.load(Ordering::Relaxed);
            if completed == NUM_TASKS {
                break;
            } else {
                ::std::sync::atomic::spin_loop_hint();
            }
        }
    }
}
<commit_msg>[bounded-executor] Add `BoundedExecutor::try_spawn()`<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]

//! A bounded tokio [`Handle`]. Only a bounded number of tasks can run
//! concurrently when spawned through this executor, defined by the initial
//! `capacity`.

use futures::future::{Future, FutureExt};
use futures_semaphore::{Permit, Semaphore};
use tokio::{runtime::Handle, task::JoinHandle};

#[derive(Clone, Debug)]
pub struct BoundedExecutor {
    semaphore: Semaphore,
    executor: Handle,
}

impl BoundedExecutor {
    /// Create a new `BoundedExecutor` from an existing tokio [`Handle`]
    /// with a maximum concurrent task capacity of `capacity`.
    pub fn new(capacity: usize, executor: Handle) -> Self {
        let semaphore = Semaphore::new(capacity);
        Self {
            semaphore,
            executor,
        }
    }

    /// Spawn a [`Future`] on the `BoundedExecutor`. This function is async and
    /// will block if the executor is at capacity until one of the other spawned
    /// futures completes. This function returns a [`JoinHandle`] that the caller
    /// can `.await` on for the results of the [`Future`].
    pub async fn spawn<F>(&self, f: F) -> JoinHandle<F::Output>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        let permit = self.semaphore.acquire().await;
        self.spawn_with_permit(f, permit)
    }

    /// Try to spawn a [`Future`] on the `BoundedExecutor`. If the `BoundedExecutor`
    /// is at capacity, this will return an `Err(F)`, passing back the future the
    /// caller attempted to spawn. Otherwise, this will spawn the future on the
    /// executor and send back a [`JoinHandle`] that the caller can `.await` on
    /// for the results of the [`Future`].
    pub fn try_spawn<F>(&self, f: F) -> Result<JoinHandle<F::Output>, F>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        match self.semaphore.try_acquire() {
            Some(permit) => Ok(self.spawn_with_permit(f, permit)),
            None => Err(f),
        }
    }

    fn spawn_with_permit<F>(&self, f: F, spawn_permit: Permit) -> JoinHandle<F::Output>
    where
        F: Future + Send + 'static,
        F::Output: Send + 'static,
    {
        // Release the permit back to the semaphore when this task completes.
        let f = f.map(move |ret| {
            drop(spawn_permit);
            ret
        });
        self.executor.spawn(f)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use futures::{channel::oneshot, executor::block_on, future::Future};
    use std::{
        sync::atomic::{AtomicU32, Ordering},
        time::Duration,
    };
    use tokio::{runtime::Runtime, time::delay_for};

    #[test]
    fn try_spawn() {
        let rt = Runtime::new().unwrap();
        let executor = rt.handle().clone();
        let executor = BoundedExecutor::new(1, executor);

        let (tx1, rx1) = oneshot::channel();
        let (tx2, rx2) = oneshot::channel();

        // executor has a free slot, spawn should succeed

        let f1 = executor.try_spawn(rx1).unwrap();

        // executor is full, try_spawn should return err and give back the task
        // we attempted to spawn

        let rx2 = executor.try_spawn(rx2).unwrap_err();

        // complete f1 future, should open a free slot in executor

        tx1.send(()).unwrap();
        block_on(f1).unwrap().unwrap();

        // should successfully spawn a new task now that the first is complete

        let f2 = executor.try_spawn(rx2).unwrap();

        // cleanup

        tx2.send(()).unwrap();
        block_on(f2).unwrap().unwrap();
    }

    fn yield_task() -> impl Future<Output = ()> {
        delay_for(Duration::from_millis(1)).map(|_| ())
    }

    // spawn NUM_TASKS futures on a BoundedExecutor, ensuring that no more than
    // MAX_WORKERS ever enter the critical section.
    #[test]
    fn concurrent_bounded_executor() {
        const MAX_WORKERS: u32 = 20;
        const NUM_TASKS: u32 = 1000;
        static WORKERS: AtomicU32 = AtomicU32::new(0);
        static COMPLETED_TASKS: AtomicU32 = AtomicU32::new(0);

        let rt = Runtime::new().unwrap();
        let executor = rt.handle().clone();
        let executor = BoundedExecutor::new(MAX_WORKERS as usize, executor);

        for _ in 0..NUM_TASKS {
            block_on(executor.spawn(async move {
                // acquired permit, there should only ever be MAX_WORKERS in this
                // critical section

                let prev_workers = WORKERS.fetch_add(1, Ordering::SeqCst);
                assert!(prev_workers < MAX_WORKERS);

                // yield back to the tokio scheduler
                yield_task().await;

                let prev_workers = WORKERS.fetch_sub(1, Ordering::SeqCst);
                assert!(prev_workers > 0 && prev_workers <= MAX_WORKERS);

                COMPLETED_TASKS.fetch_add(1, Ordering::Relaxed);
            }));
        }

        // spin until completed
        loop {
            let completed = COMPLETED_TASKS.load(Ordering::Relaxed);
            if completed == NUM_TASKS {
                break;
            } else {
                ::std::sync::atomic::spin_loop_hint();
            }
        }
    }
}
<|endoftext|>