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

use crate::{errors::JsonRpcError, views::AccountView, JsonRpcResponse};
use anyhow::{ensure, format_err, Error, Result};
use libra_types::{account_address::AccountAddress, transaction::SignedTransaction};
use reqwest::{Client, ClientBuilder, Url};
use serde_json::{json, Value};
use std::{collections::HashSet, convert::TryFrom, fmt, time::Duration};

#[derive(Clone, Default)]
pub struct JsonRpcBatch {
    pub requests: Vec<(String, Vec<Value>)>,
}

impl JsonRpcBatch {
    pub fn new() -> Self {
        Self { requests: vec![] }
    }

    pub fn json_request(&self) -> Value {
        let requests = self
            .requests
            .iter()
            .enumerate()
            .map(|(id, (method, params))| {
                serde_json::json!({"jsonrpc": "2.0", "method": method, "params": params, "id": id})
            })
            .collect();
        serde_json::Value::Array(requests)
    }

    pub fn add_request(&mut self, method_name: String, parameters: Vec<Value>) {
        self.requests.push((method_name, parameters));
    }

    pub fn add_submit_request(&mut self, transaction: SignedTransaction) -> Result<()> {
        let txn_payload = hex::encode(lcs::to_bytes(&transaction)?);
        self.add_request("submit".to_string(), vec![Value::String(txn_payload)]);
        Ok(())
    }

    pub fn add_get_account_request(&mut self, address: AccountAddress) {
        self.add_request(
            "get_account".to_string(),
            vec![Value::String(address.to_string())],
        );
    }

    pub fn add_get_metadata_request(&mut self, version: Option<u64>) {
        self.add_request("get_metadata".to_string(), vec![json!(version)]);
    }

    pub fn add_get_currencies_info(&mut self) {
        self.add_request("get_currencies".to_string(), vec![]);
    }

    pub fn add_get_transactions_request(
        &mut self,
        start_version: u64,
        limit: u64,
        include_events: bool,
    ) {
        self.add_request(
            "get_transactions".to_string(),
            vec![json!(start_version), json!(limit), json!(include_events)],
        );
    }

    pub fn add_get_account_transaction_request(
        &mut self,
        account: AccountAddress,
        sequence: u64,
        include_events: bool,
    ) {
        self.add_request(
            "get_account_transaction".to_string(),
            vec![
                json!(account.to_string()),
                json!(sequence),
                json!(include_events),
            ],
        );
    }

    pub fn add_get_events_request(&mut self, event_key: String, start: u64, limit: u64) {
        self.add_request(
            "get_events".to_string(),
            vec![json!(event_key), json!(start), json!(limit)],
        );
    }

    pub fn add_get_state_proof_request(&mut self, known_version: u64) {
        self.add_request("get_state_proof".to_string(), vec![json!(known_version)]);
    }

    pub fn add_get_account_state_with_proof_request(
        &mut self,
        account: AccountAddress,
        version: Option<u64>,
        ledger_version: Option<u64>,
    ) {
        self.add_request(
            "get_account_state_with_proof".to_string(),
            vec![
                json!(account.to_string()),
                json!(version),
                json!(ledger_version),
            ],
        );
    }

    pub fn add_get_network_status_request(&mut self) {
        self.add_request("get_network_status".to_string(), vec![]);
    }
}

#[derive(Debug)]
pub enum JsonRpcAsyncClientError {
    ClientError(reqwest::Error),
    InvalidArgument(String),
    InvalidServerResponse(String),
}

impl JsonRpcAsyncClientError {
    pub fn is_retriable(&self) -> bool {
        match self {
            JsonRpcAsyncClientError::ClientError(e) => {
                // TODO: Add following condition when is_request is added to reqwest stable release
                // // Error sending request
                //  if e.is_request() {
                //      return true;
                //  }
                if let Some(status) = e.status() {
                    // Returned status code indicates a server error
                    return status.is_server_error();
                }
                false
            }
            JsonRpcAsyncClientError::InvalidServerResponse(_)
            | JsonRpcAsyncClientError::InvalidArgument(_) => false,
        }
    }
}

impl std::convert::From<JsonRpcAsyncClientError> for anyhow::Error {
    fn from(e: JsonRpcAsyncClientError) -> Self {
        anyhow::Error::msg(e.to_string())
    }
}

impl std::fmt::Display for JsonRpcAsyncClientError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "JsonRpcAsyncClientError {}", self)
    }
}

#[derive(Clone)]
pub struct JsonRpcAsyncClient {
    address: String,
    client: Client,
}

impl JsonRpcAsyncClient {
    /// Pass in full url for endpoint, supports HTTPS
    pub fn new(url: Url) -> Self {
        Self {
            address: url.to_string(),
            client: ClientBuilder::new()
                .timeout(Duration::from_secs(30))
                .build()
                .expect("Unable to build Client."),
        }
    }

    pub fn new_with_client(client: Client, url: Url) -> Self {
        Self {
            address: url.to_string(),
            client,
        }
    }

    pub async fn get_accounts(
        &self,
        accounts: &[AccountAddress],
    ) -> Result<Vec<Option<AccountView>>, JsonRpcAsyncClientError> {
        let mut batch = JsonRpcBatch::new();
        for account in accounts {
            batch.add_get_account_request(*account);
        }
        let exec_results = self.execute(batch).await?;
        let mut results = vec![];
        for exec_result in exec_results {
            let exec_result = exec_result
                .map_err(|e| JsonRpcAsyncClientError::InvalidServerResponse(e.to_string()))?;
            if let JsonRpcResponse::AccountResponse(r) = exec_result {
                results.push(r);
            } else {
                panic!("Unexpected response for get_accounts {:?}", exec_result)
            }
        }

        if results.len() != accounts.len() {
            return Err(JsonRpcAsyncClientError::InvalidServerResponse(format!(
                "Received unexpected number of JSON RPC responses ({}) for {} requests",
                results.len(),
                accounts.len()
            )));
        }

        Ok(results)
    }

    pub async fn submit_transaction(
        &self,
        txn: SignedTransaction,
    ) -> Result<(), JsonRpcAsyncClientError> {
        let mut batch = JsonRpcBatch::new();
        batch
            .add_submit_request(txn)
            .map_err(|e| JsonRpcAsyncClientError::InvalidArgument(e.to_string()))?;
        let mut exec_result = self.execute(batch).await?;
        assert!(exec_result.len() == 1);
        exec_result
            .remove(0)
            .map(|_| ())
            .map_err(|e| JsonRpcAsyncClientError::InvalidServerResponse(e.to_string()))
    }

    pub async fn execute(
        &self,
        batch: JsonRpcBatch,
    ) -> Result<Vec<Result<JsonRpcResponse>>, JsonRpcAsyncClientError> {
        let requests = batch.json_request();
        let resp = self
            .client
            .post(&self.address)
            .json(&requests)
            .send()
            .await
            .map_err(JsonRpcAsyncClientError::ClientError)?;

        if resp.status() != 200 {
            return Err(JsonRpcAsyncClientError::InvalidServerResponse(format!(
                "Http error code {}",
                resp.status()
            )));
        }

        let responses: Vec<Value> = resp
            .json()
            .await
            .map_err(|e| JsonRpcAsyncClientError::InvalidServerResponse(e.to_string()))?;
        process_batch_response(batch, responses)
            .map_err(|e| JsonRpcAsyncClientError::InvalidServerResponse(e.to_string()))
    }
}

impl fmt::Debug for JsonRpcAsyncClient {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.address)
    }
}

////////////////////////////////////////////////////
/// Helper methods for basic payload processing ///
///////////////////////////////////////////////////

pub fn process_batch_response(
    batch: JsonRpcBatch,
    responses: Vec<Value>,
) -> Result<Vec<Result<JsonRpcResponse>>> {
    let mut result = batch
        .requests
        .iter()
        .map(|_| Err(format_err!("response is missing")))
        .collect::<Vec<_>>();

    let mut seen_ids = HashSet::new();
    for response in responses {
        // check JSON RPC protocol
        let json_rpc_protocol = response.get("jsonrpc");
        ensure!(
            json_rpc_protocol == Some(&json!("2.0")),
            "JSON RPC response with incorrect protocol: {:?}",
            json_rpc_protocol
        );

        if let Ok(req_id) = fetch_id(&response) {
            ensure!(
                !seen_ids.contains(&req_id),
                "received JSON RPC response with duplicate response ID"
            );
            seen_ids.insert(req_id);
            if req_id < result.len() {
                let resp = if let Some(err_data) = response.get("error") {
                    let err_json: JsonRpcError = serde_json::from_value(err_data.clone())?;
                    Err(Error::new(err_json))
                } else if let Some(data) = response.get("result") {
                    let method = batch.requests[req_id].0.clone();
                    Ok(JsonRpcResponse::try_from((method, data.clone()))?)
                } else {
                    continue;
                };
                result[req_id] = resp;
            }
        }
    }

    Ok(result)
}

pub fn get_response_from_batch(
    index: usize,
    batch: &[Result<JsonRpcResponse>],
) -> Result<&Result<JsonRpcResponse>> {
    batch.get(index).ok_or_else(|| {
        format_err!(
            "[JSON RPC client] response missing in batch at index {}",
            index
        )
    })
}

fn fetch_id(response: &Value) -> Result<usize> {
    match response.get("id") {
        Some(id) => Ok(serde_json::from_value::<usize>(id.clone())?),
        None => Err(format_err!("request id is missing")),
    }
}
<commit_msg>[json-rpc] Fix display stackoverflow and add unit test<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{errors::JsonRpcError, views::AccountView, JsonRpcResponse};
use anyhow::{ensure, format_err, Error, Result};
use libra_types::{account_address::AccountAddress, transaction::SignedTransaction};
use reqwest::{Client, ClientBuilder, Url};
use serde_json::{json, Value};
use std::{collections::HashSet, convert::TryFrom, fmt, time::Duration};

#[derive(Clone, Default)]
pub struct JsonRpcBatch {
    pub requests: Vec<(String, Vec<Value>)>,
}

impl JsonRpcBatch {
    pub fn new() -> Self {
        Self { requests: vec![] }
    }

    pub fn json_request(&self) -> Value {
        let requests = self
            .requests
            .iter()
            .enumerate()
            .map(|(id, (method, params))| {
                serde_json::json!({"jsonrpc": "2.0", "method": method, "params": params, "id": id})
            })
            .collect();
        serde_json::Value::Array(requests)
    }

    pub fn add_request(&mut self, method_name: String, parameters: Vec<Value>) {
        self.requests.push((method_name, parameters));
    }

    pub fn add_submit_request(&mut self, transaction: SignedTransaction) -> Result<()> {
        let txn_payload = hex::encode(lcs::to_bytes(&transaction)?);
        self.add_request("submit".to_string(), vec![Value::String(txn_payload)]);
        Ok(())
    }

    pub fn add_get_account_request(&mut self, address: AccountAddress) {
        self.add_request(
            "get_account".to_string(),
            vec![Value::String(address.to_string())],
        );
    }

    pub fn add_get_metadata_request(&mut self, version: Option<u64>) {
        self.add_request("get_metadata".to_string(), vec![json!(version)]);
    }

    pub fn add_get_currencies_info(&mut self) {
        self.add_request("get_currencies".to_string(), vec![]);
    }

    pub fn add_get_transactions_request(
        &mut self,
        start_version: u64,
        limit: u64,
        include_events: bool,
    ) {
        self.add_request(
            "get_transactions".to_string(),
            vec![json!(start_version), json!(limit), json!(include_events)],
        );
    }

    pub fn add_get_account_transaction_request(
        &mut self,
        account: AccountAddress,
        sequence: u64,
        include_events: bool,
    ) {
        self.add_request(
            "get_account_transaction".to_string(),
            vec![
                json!(account.to_string()),
                json!(sequence),
                json!(include_events),
            ],
        );
    }

    pub fn add_get_events_request(&mut self, event_key: String, start: u64, limit: u64) {
        self.add_request(
            "get_events".to_string(),
            vec![json!(event_key), json!(start), json!(limit)],
        );
    }

    pub fn add_get_state_proof_request(&mut self, known_version: u64) {
        self.add_request("get_state_proof".to_string(), vec![json!(known_version)]);
    }

    pub fn add_get_account_state_with_proof_request(
        &mut self,
        account: AccountAddress,
        version: Option<u64>,
        ledger_version: Option<u64>,
    ) {
        self.add_request(
            "get_account_state_with_proof".to_string(),
            vec![
                json!(account.to_string()),
                json!(version),
                json!(ledger_version),
            ],
        );
    }

    pub fn add_get_network_status_request(&mut self) {
        self.add_request("get_network_status".to_string(), vec![]);
    }
}

#[derive(Debug)]
pub enum JsonRpcAsyncClientError {
    ClientError(reqwest::Error),
    InvalidArgument(String),
    InvalidServerResponse(String),
}

impl JsonRpcAsyncClientError {
    pub fn is_retriable(&self) -> bool {
        match self {
            JsonRpcAsyncClientError::ClientError(e) => {
                // TODO: Add following condition when is_request is added to reqwest stable release
                // // Error sending request
                //  if e.is_request() {
                //      return true;
                //  }
                if e.is_timeout() {
                    return true;
                }

                if let Some(status) = e.status() {
                    // Returned status code indicates a server error
                    return status.is_server_error();
                }
                false
            }
            JsonRpcAsyncClientError::InvalidServerResponse(_)
            | JsonRpcAsyncClientError::InvalidArgument(_) => false,
        }
    }
}

impl std::convert::From<JsonRpcAsyncClientError> for anyhow::Error {
    fn from(e: JsonRpcAsyncClientError) -> Self {
        anyhow::Error::msg(e.to_string())
    }
}

impl std::fmt::Display for JsonRpcAsyncClientError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            JsonRpcAsyncClientError::InvalidArgument(e) => write!(f, "InvalidArgument: {}", e),
            JsonRpcAsyncClientError::ClientError(e) => write!(f, "ClientError: {}", e.to_string()),
            JsonRpcAsyncClientError::InvalidServerResponse(e) => {
                write!(f, "InvalidServerResponse {}", e)
            }
        }
    }
}

#[derive(Clone)]
pub struct JsonRpcAsyncClient {
    address: String,
    client: Client,
}

impl JsonRpcAsyncClient {
    /// Pass in full url for endpoint, supports HTTPS
    pub fn new(url: Url) -> Self {
        Self {
            address: url.to_string(),
            client: ClientBuilder::new()
                .timeout(Duration::from_secs(30))
                .build()
                .expect("Unable to build Client."),
        }
    }

    pub fn new_with_client(client: Client, url: Url) -> Self {
        Self {
            address: url.to_string(),
            client,
        }
    }

    pub async fn get_accounts(
        &self,
        accounts: &[AccountAddress],
    ) -> Result<Vec<Option<AccountView>>, JsonRpcAsyncClientError> {
        let mut batch = JsonRpcBatch::new();
        for account in accounts {
            batch.add_get_account_request(*account);
        }
        let exec_results = self.execute(batch).await?;
        let mut results = vec![];
        for exec_result in exec_results {
            let exec_result = exec_result
                .map_err(|e| JsonRpcAsyncClientError::InvalidServerResponse(e.to_string()))?;
            if let JsonRpcResponse::AccountResponse(r) = exec_result {
                results.push(r);
            } else {
                panic!("Unexpected response for get_accounts {:?}", exec_result)
            }
        }

        if results.len() != accounts.len() {
            return Err(JsonRpcAsyncClientError::InvalidServerResponse(format!(
                "Received unexpected number of JSON RPC responses ({}) for {} requests",
                results.len(),
                accounts.len()
            )));
        }

        Ok(results)
    }

    pub async fn submit_transaction(
        &self,
        txn: SignedTransaction,
    ) -> Result<(), JsonRpcAsyncClientError> {
        let mut batch = JsonRpcBatch::new();
        batch
            .add_submit_request(txn)
            .map_err(|e| JsonRpcAsyncClientError::InvalidArgument(e.to_string()))?;
        let mut exec_result = self.execute(batch).await?;
        assert!(exec_result.len() == 1);
        exec_result
            .remove(0)
            .map(|_| ())
            .map_err(|e| JsonRpcAsyncClientError::InvalidServerResponse(e.to_string()))
    }

    pub async fn execute(
        &self,
        batch: JsonRpcBatch,
    ) -> Result<Vec<Result<JsonRpcResponse>>, JsonRpcAsyncClientError> {
        let requests = batch.json_request();
        let resp = self
            .client
            .post(&self.address)
            .json(&requests)
            .send()
            .await
            .map_err(JsonRpcAsyncClientError::ClientError)?;

        if resp.status() != 200 {
            return Err(JsonRpcAsyncClientError::InvalidServerResponse(format!(
                "Http error code {}",
                resp.status()
            )));
        }

        let responses: Vec<Value> = resp
            .json()
            .await
            .map_err(|e| JsonRpcAsyncClientError::InvalidServerResponse(e.to_string()))?;
        process_batch_response(batch, responses)
            .map_err(|e| JsonRpcAsyncClientError::InvalidServerResponse(e.to_string()))
    }
}

impl fmt::Debug for JsonRpcAsyncClient {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.address)
    }
}

////////////////////////////////////////////////////
/// Helper methods for basic payload processing ///
///////////////////////////////////////////////////

pub fn process_batch_response(
    batch: JsonRpcBatch,
    responses: Vec<Value>,
) -> Result<Vec<Result<JsonRpcResponse>>> {
    let mut result = batch
        .requests
        .iter()
        .map(|_| Err(format_err!("response is missing")))
        .collect::<Vec<_>>();

    let mut seen_ids = HashSet::new();
    for response in responses {
        // check JSON RPC protocol
        let json_rpc_protocol = response.get("jsonrpc");
        ensure!(
            json_rpc_protocol == Some(&json!("2.0")),
            "JSON RPC response with incorrect protocol: {:?}",
            json_rpc_protocol
        );

        if let Ok(req_id) = fetch_id(&response) {
            ensure!(
                !seen_ids.contains(&req_id),
                "received JSON RPC response with duplicate response ID"
            );
            seen_ids.insert(req_id);
            if req_id < result.len() {
                let resp = if let Some(err_data) = response.get("error") {
                    let err_json: JsonRpcError = serde_json::from_value(err_data.clone())?;
                    Err(Error::new(err_json))
                } else if let Some(data) = response.get("result") {
                    let method = batch.requests[req_id].0.clone();
                    Ok(JsonRpcResponse::try_from((method, data.clone()))?)
                } else {
                    continue;
                };
                result[req_id] = resp;
            }
        }
    }

    Ok(result)
}

pub fn get_response_from_batch(
    index: usize,
    batch: &[Result<JsonRpcResponse>],
) -> Result<&Result<JsonRpcResponse>> {
    batch.get(index).ok_or_else(|| {
        format_err!(
            "[JSON RPC client] response missing in batch at index {}",
            index
        )
    })
}

fn fetch_id(response: &Value) -> Result<usize> {
    match response.get("id") {
        Some(id) => Ok(serde_json::from_value::<usize>(id.clone())?),
        None => Err(format_err!("request id is missing")),
    }
}

#[cfg(test)]
pub mod tests {
    use super::*;
    use reqwest::{blocking::ClientBuilder, Url};

    #[test]
    fn test_error_is_retriable() {
        let server_err = JsonRpcAsyncClientError::InvalidServerResponse(
            "test invalid server response".to_string(),
        );
        // Reqwest error's builder is private to the crate, so send out a
        // fake request that should fail to get an error
        let test_client = ClientBuilder::new()
            .timeout(Duration::from_millis(1))
            .build()
            .unwrap();
        let req_err = test_client
            .get(Url::parse("http://192.108.0.1").unwrap())
            .send()
            .unwrap_err();
        let client_err = JsonRpcAsyncClientError::ClientError(req_err);
        let arg_err = JsonRpcAsyncClientError::InvalidArgument("test invalid argument".to_string());
        // Make sure display is implemented correctly for error enum
        println!("{}", client_err);
        assert_eq!(server_err.is_retriable(), false);
        assert_eq!(arg_err.is_retriable(), false);
        assert_eq!(client_err.is_retriable(), true);
    }
}
<|endoftext|>


<commit_before>#![deny(warnings)]
#[macro_use]
extern crate clap;

extern crate svg;
extern crate svgbob;

use svgbob::Grid;
use svgbob::Settings;

use clap::ArgMatches;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use std::error::Error;
use std::io::Read;
use std::process::exit;
use std::str::FromStr;

fn main() {
    use clap::{App, Arg, SubCommand};

    let args = App::new("svgbob")
        .version(crate_version!())
        .about("SvgBobRus is an ascii to svg converter")
        .arg(Arg::with_name("inline").short("s").help("parse an inline string"))
        .arg(Arg::with_name("input").index(1).help("svgbob text file or inline string to parse [default: STDIN]"))
        .arg(Arg::with_name("output")
            .short("o")
            .long("output")
            .takes_value(true)
            .help("where to write svg output [default: STDOUT]"))
        .arg(Arg::with_name("font-family")
             .long("font-family")
             .takes_value(true)
             .help("text will be rendered with this font (default: 'arial')"))
        .arg(Arg::with_name("font-size")
             .long("font-size")
             .takes_value(true)
             .help("text will be rendered with this font size (default: 14)"))
        .arg(Arg::with_name("stroke-width")
             .long("stroke-width")
             .takes_value(true)
             .help("stroke width for all lines (default: 2)"))
        .arg(Arg::with_name("scale")
             .long("scale")
             .takes_value(true)
             .help("scale the entire svg (dimensions, font size, stroke width) by this factor (default: 1)"))
        .subcommand(SubCommand::with_name("build")
            .about("Batch convert files to svg.")
            .version("0.0.1")
            .arg(Arg::with_name("input")
                .short("i")
                .long("input")
                .takes_value(true)
                .help("set input file pattern like: *.bob or dir/*.bob"))
            .arg(Arg::with_name("outdir")
                .short("o")
                .long("outdir")
                .takes_value(true)
                .help("set dir of svg files")))
        .get_matches();

    if let Some(sub_build) = args.subcommand_matches("build") {
        match build(sub_build) {
            Ok(_) => {}
            Err(e) => println!("{}", e),
        };
        exit(1);
    }

    let mut bob = String::new();

    if args.is_present("inline") {
        bob = args.value_of("input")
            .unwrap()
            .replace("\\n", "\n")
            .to_string();
    } else {
        if let Some(file) = args.value_of("input") {
            match File::open(file) {
                Ok(mut f) => {
                    f.read_to_string(&mut bob).unwrap();
                }
                Err(e) => {
                    use std::io::Write;
                    use std::process::exit;

                    writeln!(
                        &mut std::io::stderr(),
                        "Failed to open input file {}: {}",
                        file,
                        e
                    ).unwrap();
                    exit(1);
                }
            }
        } else {
            use std::io;
            io::stdin().read_to_string(&mut bob).unwrap();
        }
    }

    let mut settings = Settings::default();

    if let Some(font_family) = args.value_of("font-family") {
        settings.font_family = font_family.to_string();
    }

    if let Some(font_size) = parse_value_of(&args, "font-size") {
        settings.font_size = font_size;
    }

    if let Some(stroke_width) = parse_value_of(&args, "stroke-width") {
        settings.stroke_width = stroke_width;
    }

    if let Some(scale) = parse_value_of(&args, "scale") {
        settings.scale(scale);
    }

    let g = Grid::from_str(&*bob, &settings);
    let svg = g.get_svg();

    if let Some(file) = args.value_of("output") {
        if let Err(e) = svg::save(file, &svg) {
            use std::io::Write;
            use std::process::exit;

            writeln!(
                &mut std::io::stderr(),
                "Failed to write to output file {}: {}",
                file,
                e
            ).unwrap();
            exit(2);
        }
    } else {
        println!("{}", svg);
    }
}

fn parse_value_of<T: FromStr>(args: &ArgMatches, arg_name: &str) -> Option<T>
where
    <T as std::str::FromStr>::Err: std::fmt::Display,
{
    return args.value_of(arg_name)
        .and_then(|arg| match arg.parse::<T>() {
            Ok(a) => Some(a),
            Err(e) => {
                use std::io::Write;
                use std::process::exit;

                writeln!(
                    &mut std::io::stderr(),
                    "Illegal value for argument {}: {}",
                    arg_name,
                    e
                ).unwrap();
                exit(1);
            }
        });
}

// Batch convert files to svg
// use svgbob build -i inputdir/*.bob -o outdir/
fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
    let files_pattern = args.value_of("input").unwrap_or("*.bob");
    let outdir = args.value_of("outdir").unwrap_or("");
    let input_path = Path::new(files_pattern);
    let ext = input_path
        .extension()
        .unwrap_or(&"bob".as_ref())
        .to_str()
        .unwrap();

    let input_dir = if input_path.is_dir() {
        input_path.clone()
    } else {
        input_path.parent().unwrap()
    };

    if !input_dir.is_dir() {
        return Err(Box::from(format!(
            "[Error]: No such dir name is {} !",
            input_dir.to_string_lossy()
        )));
    }

    let mut out_path = PathBuf::new();
    if outdir == "" {
        out_path = input_dir.to_path_buf();
    } else {
        out_path.push(outdir)
    }

    if !out_path.is_dir() {
        try!(fs::create_dir_all(out_path.clone()));
    }

    let paths = fs::read_dir(input_dir).unwrap();
    for path in paths {
        let tmp_path = path.unwrap().path();
        if tmp_path.is_file() {
            let tmp_ext = tmp_path
                .extension()
                .unwrap_or(&"".as_ref())
                .to_str()
                .unwrap();
            if tmp_ext == ext {
                let name = tmp_path.file_stem().unwrap().to_str().unwrap();
                let mut tmp = out_path.clone();
                tmp.push(format!("{}.svg", name));
                println!("{} => {}", tmp_path.display(), tmp.display());
                match convert_file(tmp_path.clone(), tmp) {
                    Ok(_) => {}
                    Err(e) => {
                        println!("{}", e);
                    }
                }
            }
        }
    }

    Ok(())
}

fn convert_file(input: PathBuf, output: PathBuf) -> Result<(), Box<Error>> {
    let mut bob = String::new();
    let mut f = try!(File::open(&input));
    f.read_to_string(&mut bob).unwrap();
    let g = Grid::from_str(&*bob, &Settings::default());
    let svg = g.get_svg();
    try!(svg::save(&output, &svg));
    Ok(())
}
<commit_msg>removing redundant imports<commit_after>#![deny(warnings)]
#[macro_use]
extern crate clap;

extern crate svg;
extern crate svgbob;

use svgbob::Grid;
use svgbob::Settings;

use clap::ArgMatches;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use std::error::Error;
use std::io::Read;
use std::process::exit;
use std::str::FromStr;

fn main() {
    use clap::{App, Arg, SubCommand};

    let args = App::new("svgbob")
        .version(crate_version!())
        .about("SvgBobRus is an ascii to svg converter")
        .arg(Arg::with_name("inline").short("s").help("parse an inline string"))
        .arg(Arg::with_name("input").index(1).help("svgbob text file or inline string to parse [default: STDIN]"))
        .arg(Arg::with_name("output")
            .short("o")
            .long("output")
            .takes_value(true)
            .help("where to write svg output [default: STDOUT]"))
        .arg(Arg::with_name("font-family")
             .long("font-family")
             .takes_value(true)
             .help("text will be rendered with this font (default: 'arial')"))
        .arg(Arg::with_name("font-size")
             .long("font-size")
             .takes_value(true)
             .help("text will be rendered with this font size (default: 14)"))
        .arg(Arg::with_name("stroke-width")
             .long("stroke-width")
             .takes_value(true)
             .help("stroke width for all lines (default: 2)"))
        .arg(Arg::with_name("scale")
             .long("scale")
             .takes_value(true)
             .help("scale the entire svg (dimensions, font size, stroke width) by this factor (default: 1)"))
        .subcommand(SubCommand::with_name("build")
            .about("Batch convert files to svg.")
            .version("0.0.1")
            .arg(Arg::with_name("input")
                .short("i")
                .long("input")
                .takes_value(true)
                .help("set input file pattern like: *.bob or dir/*.bob"))
            .arg(Arg::with_name("outdir")
                .short("o")
                .long("outdir")
                .takes_value(true)
                .help("set dir of svg files")))
        .get_matches();

    if let Some(sub_build) = args.subcommand_matches("build") {
        match build(sub_build) {
            Ok(_) => {}
            Err(e) => println!("{}", e),
        };
        exit(1);
    }

    let mut bob = String::new();

    if args.is_present("inline") {
        bob = args.value_of("input")
            .unwrap()
            .replace("\\n", "\n")
            .to_string();
    } else {
        if let Some(file) = args.value_of("input") {
            match File::open(file) {
                Ok(mut f) => {
                    f.read_to_string(&mut bob).unwrap();
                }
                Err(e) => {
                    use std::io::Write;

                    writeln!(
                        &mut std::io::stderr(),
                        "Failed to open input file {}: {}",
                        file,
                        e
                    ).unwrap();
                    exit(1);
                }
            }
        } else {
            use std::io;
            io::stdin().read_to_string(&mut bob).unwrap();
        }
    }

    let mut settings = Settings::default();

    if let Some(font_family) = args.value_of("font-family") {
        settings.font_family = font_family.to_string();
    }

    if let Some(font_size) = parse_value_of(&args, "font-size") {
        settings.font_size = font_size;
    }

    if let Some(stroke_width) = parse_value_of(&args, "stroke-width") {
        settings.stroke_width = stroke_width;
    }

    if let Some(scale) = parse_value_of(&args, "scale") {
        settings.scale(scale);
    }

    let g = Grid::from_str(&*bob, &settings);
    let svg = g.get_svg();

    if let Some(file) = args.value_of("output") {
        if let Err(e) = svg::save(file, &svg) {
            use std::io::Write;

            writeln!(
                &mut std::io::stderr(),
                "Failed to write to output file {}: {}",
                file,
                e
            ).unwrap();
            exit(2);
        }
    } else {
        println!("{}", svg);
    }
}

fn parse_value_of<T: FromStr>(args: &ArgMatches, arg_name: &str) -> Option<T>
where
    <T as std::str::FromStr>::Err: std::fmt::Display,
{
    return args.value_of(arg_name)
        .and_then(|arg| match arg.parse::<T>() {
            Ok(a) => Some(a),
            Err(e) => {
                use std::io::Write;

                writeln!(
                    &mut std::io::stderr(),
                    "Illegal value for argument {}: {}",
                    arg_name,
                    e
                ).unwrap();
                exit(1);
            }
        });
}

// Batch convert files to svg
// use svgbob build -i inputdir/*.bob -o outdir/
fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
    let files_pattern = args.value_of("input").unwrap_or("*.bob");
    let outdir = args.value_of("outdir").unwrap_or("");
    let input_path = Path::new(files_pattern);
    let ext = input_path
        .extension()
        .unwrap_or(&"bob".as_ref())
        .to_str()
        .unwrap();

    let input_dir = if input_path.is_dir() {
        input_path.clone()
    } else {
        input_path.parent().unwrap()
    };

    if !input_dir.is_dir() {
        return Err(Box::from(format!(
            "[Error]: No such dir name is {} !",
            input_dir.to_string_lossy()
        )));
    }

    let mut out_path = PathBuf::new();
    if outdir == "" {
        out_path = input_dir.to_path_buf();
    } else {
        out_path.push(outdir)
    }

    if !out_path.is_dir() {
        try!(fs::create_dir_all(out_path.clone()));
    }

    let paths = fs::read_dir(input_dir).unwrap();
    for path in paths {
        let tmp_path = path.unwrap().path();
        if tmp_path.is_file() {
            let tmp_ext = tmp_path
                .extension()
                .unwrap_or(&"".as_ref())
                .to_str()
                .unwrap();
            if tmp_ext == ext {
                let name = tmp_path.file_stem().unwrap().to_str().unwrap();
                let mut tmp = out_path.clone();
                tmp.push(format!("{}.svg", name));
                println!("{} => {}", tmp_path.display(), tmp.display());
                match convert_file(tmp_path.clone(), tmp) {
                    Ok(_) => {}
                    Err(e) => {
                        println!("{}", e);
                    }
                }
            }
        }
    }

    Ok(())
}

fn convert_file(input: PathBuf, output: PathBuf) -> Result<(), Box<Error>> {
    let mut bob = String::new();
    let mut f = try!(File::open(&input));
    f.read_to_string(&mut bob).unwrap();
    let g = Grid::from_str(&*bob, &Settings::default());
    let svg = g.get_svg();
    try!(svg::save(&output, &svg));
    Ok(())
}
<|endoftext|>


<commit_before>//! The `TypeWalk` trait (probably to be replaced by Chalk's `Fold` and
//! `Visit`).

use std::mem;

use chalk_ir::{interner::HasInterner, DebruijnIndex};

use crate::{
    utils::make_mut_slice, AliasEq, AliasTy, Binders, CallableSig, FnSubst, GenericArg,
    GenericArgData, Interner, OpaqueTy, ProjectionTy, Substitution, TraitRef, Ty, TyKind,
    WhereClause,
};

/// This allows walking structures that contain types to do something with those
/// types, similar to Chalk's `Fold` trait.
pub trait TypeWalk {
    fn walk(&self, f: &mut impl FnMut(&Ty));
    fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) {
        self.walk_mut_binders(&mut |ty, _binders| f(ty), DebruijnIndex::INNERMOST);
    }
    /// Walk the type, counting entered binders.
    ///
    /// `TyKind::Bound` variables use DeBruijn indexing, which means that 0 refers
    /// to the innermost binder, 1 to the next, etc.. So when we want to
    /// substitute a certain bound variable, we can't just walk the whole type
    /// and blindly replace each instance of a certain index; when we 'enter'
    /// things that introduce new bound variables, we have to keep track of
    /// that. Currently, the only thing that introduces bound variables on our
    /// side are `TyKind::Dyn` and `TyKind::Opaque`, which each introduce a bound
    /// variable for the self type.
    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    );

    fn fold_binders(
        mut self,
        f: &mut impl FnMut(Ty, DebruijnIndex) -> Ty,
        binders: DebruijnIndex,
    ) -> Self
    where
        Self: Sized,
    {
        self.walk_mut_binders(
            &mut |ty_mut, binders| {
                let ty = mem::replace(ty_mut, TyKind::Error.intern(&Interner));
                *ty_mut = f(ty, binders);
            },
            binders,
        );
        self
    }

    fn fold(mut self, f: &mut impl FnMut(Ty) -> Ty) -> Self
    where
        Self: Sized,
    {
        self.walk_mut(&mut |ty_mut| {
            let ty = mem::replace(ty_mut, TyKind::Error.intern(&Interner));
            *ty_mut = f(ty);
        });
        self
    }

    /// Substitutes `TyKind::Bound` vars with the given substitution.
    fn subst_bound_vars(self, substs: &Substitution) -> Self
    where
        Self: Sized,
    {
        self.subst_bound_vars_at_depth(substs, DebruijnIndex::INNERMOST)
    }

    /// Substitutes `TyKind::Bound` vars with the given substitution.
    fn subst_bound_vars_at_depth(mut self, substs: &Substitution, depth: DebruijnIndex) -> Self
    where
        Self: Sized,
    {
        self.walk_mut_binders(
            &mut |ty, binders| {
                if let &mut TyKind::BoundVar(bound) = ty.interned_mut() {
                    if bound.debruijn >= binders {
                        *ty = substs.interned()[bound.index]
                            .assert_ty_ref(&Interner)
                            .clone()
                            .shifted_in_from(binders);
                    }
                }
            },
            depth,
        );
        self
    }

    fn shifted_in(self, _interner: &Interner) -> Self
    where
        Self: Sized,
    {
        self.shifted_in_from(DebruijnIndex::ONE)
    }

    /// Shifts up debruijn indices of `TyKind::Bound` vars by `n`.
    fn shifted_in_from(self, n: DebruijnIndex) -> Self
    where
        Self: Sized,
    {
        self.fold_binders(
            &mut |ty, binders| match ty.kind(&Interner) {
                TyKind::BoundVar(bound) if bound.debruijn >= binders => {
                    TyKind::BoundVar(bound.shifted_in_from(n)).intern(&Interner)
                }
                _ => ty,
            },
            DebruijnIndex::INNERMOST,
        )
    }

    /// Shifts debruijn indices of `TyKind::Bound` vars out (down) by `n`.
    fn shifted_out_to(self, n: DebruijnIndex) -> Option<Self>
    where
        Self: Sized + std::fmt::Debug,
    {
        Some(self.fold_binders(
            &mut |ty, binders| {
                match ty.kind(&Interner) {
                    TyKind::BoundVar(bound) if bound.debruijn >= binders => {
                        TyKind::BoundVar(bound.shifted_out_to(n).unwrap_or(bound.clone()))
                            .intern(&Interner)
                    }
                    _ => ty,
                }
            },
            DebruijnIndex::INNERMOST,
        ))
    }
}

impl TypeWalk for Ty {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        match self.kind(&Interner) {
            TyKind::Alias(AliasTy::Projection(p_ty)) => {
                for t in p_ty.substitution.iter(&Interner) {
                    t.walk(f);
                }
            }
            TyKind::Alias(AliasTy::Opaque(o_ty)) => {
                for t in o_ty.substitution.iter(&Interner) {
                    t.walk(f);
                }
            }
            TyKind::Dyn(dyn_ty) => {
                for p in dyn_ty.bounds.skip_binders().interned().iter() {
                    p.walk(f);
                }
            }
            TyKind::Slice(ty)
            | TyKind::Array(ty, _)
            | TyKind::Ref(_, _, ty)
            | TyKind::Raw(_, ty) => {
                ty.walk(f);
            }
            TyKind::Function(fn_pointer) => {
                fn_pointer.substitution.0.walk(f);
            }
            TyKind::Adt(_, substs)
            | TyKind::FnDef(_, substs)
            | TyKind::Tuple(_, substs)
            | TyKind::OpaqueType(_, substs)
            | TyKind::AssociatedType(_, substs)
            | TyKind::Closure(.., substs) => {
                substs.walk(f);
            }
            _ => {}
        }
        f(self);
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        match self.interned_mut() {
            TyKind::Alias(AliasTy::Projection(p_ty)) => {
                p_ty.substitution.walk_mut_binders(f, binders);
            }
            TyKind::Dyn(dyn_ty) => {
                for p in make_mut_slice(dyn_ty.bounds.skip_binders_mut().interned_mut()) {
                    p.walk_mut_binders(f, binders.shifted_in());
                }
            }
            TyKind::Alias(AliasTy::Opaque(o_ty)) => {
                o_ty.substitution.walk_mut_binders(f, binders);
            }
            TyKind::Slice(ty)
            | TyKind::Array(ty, _)
            | TyKind::Ref(_, _, ty)
            | TyKind::Raw(_, ty) => {
                ty.walk_mut_binders(f, binders);
            }
            TyKind::Function(fn_pointer) => {
                fn_pointer.substitution.0.walk_mut_binders(f, binders.shifted_in());
            }
            TyKind::Adt(_, substs)
            | TyKind::FnDef(_, substs)
            | TyKind::Tuple(_, substs)
            | TyKind::OpaqueType(_, substs)
            | TyKind::AssociatedType(_, substs)
            | TyKind::Closure(.., substs) => {
                substs.walk_mut_binders(f, binders);
            }
            _ => {}
        }
        f(self, binders);
    }
}

impl<T: TypeWalk> TypeWalk for Vec<T> {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        for t in self {
            t.walk(f);
        }
    }
    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        for t in self {
            t.walk_mut_binders(f, binders);
        }
    }
}

impl TypeWalk for OpaqueTy {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.substitution.walk(f);
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        self.substitution.walk_mut_binders(f, binders);
    }
}

impl TypeWalk for ProjectionTy {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.substitution.walk(f);
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        self.substitution.walk_mut_binders(f, binders);
    }
}

impl TypeWalk for AliasTy {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        match self {
            AliasTy::Projection(it) => it.walk(f),
            AliasTy::Opaque(it) => it.walk(f),
        }
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        match self {
            AliasTy::Projection(it) => it.walk_mut_binders(f, binders),
            AliasTy::Opaque(it) => it.walk_mut_binders(f, binders),
        }
    }
}

impl TypeWalk for GenericArg {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        match &self.interned() {
            GenericArgData::Ty(ty) => {
                ty.walk(f);
            }
        }
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        match self.interned_mut() {
            GenericArgData::Ty(ty) => {
                ty.walk_mut_binders(f, binders);
            }
        }
    }
}

impl TypeWalk for Substitution {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        for t in self.iter(&Interner) {
            t.walk(f);
        }
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        for t in self.interned_mut() {
            t.walk_mut_binders(f, binders);
        }
    }
}

impl<T: TypeWalk + HasInterner<Interner = Interner>> TypeWalk for Binders<T> {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.skip_binders().walk(f);
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        self.skip_binders_mut().walk_mut_binders(f, binders.shifted_in())
    }
}

impl TypeWalk for TraitRef {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.substitution.walk(f);
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        self.substitution.walk_mut_binders(f, binders);
    }
}

impl TypeWalk for WhereClause {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        match self {
            WhereClause::Implemented(trait_ref) => trait_ref.walk(f),
            WhereClause::AliasEq(alias_eq) => alias_eq.walk(f),
        }
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        match self {
            WhereClause::Implemented(trait_ref) => trait_ref.walk_mut_binders(f, binders),
            WhereClause::AliasEq(alias_eq) => alias_eq.walk_mut_binders(f, binders),
        }
    }
}

impl TypeWalk for CallableSig {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        for t in self.params_and_return.iter() {
            t.walk(f);
        }
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        for t in make_mut_slice(&mut self.params_and_return) {
            t.walk_mut_binders(f, binders);
        }
    }
}

impl TypeWalk for AliasEq {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.ty.walk(f);
        match &self.alias {
            AliasTy::Projection(projection_ty) => projection_ty.walk(f),
            AliasTy::Opaque(opaque) => opaque.walk(f),
        }
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        self.ty.walk_mut_binders(f, binders);
        match &mut self.alias {
            AliasTy::Projection(projection_ty) => projection_ty.walk_mut_binders(f, binders),
            AliasTy::Opaque(opaque) => opaque.walk_mut_binders(f, binders),
        }
    }
}

impl TypeWalk for FnSubst<Interner> {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.0.walk(f)
    }

    fn walk_mut_binders(
        &mut self,
        f: &mut impl FnMut(&mut Ty, DebruijnIndex),
        binders: DebruijnIndex,
    ) {
        self.0.walk_mut_binders(f, binders)
    }
}
<commit_msg>Get rid of walk_mut [not compiling]<commit_after>//! The `TypeWalk` trait (probably to be replaced by Chalk's `Fold` and
//! `Visit`).

use std::mem;

use chalk_ir::{interner::HasInterner, DebruijnIndex};

use crate::{
    utils::make_mut_slice, AliasEq, AliasTy, Binders, CallableSig, FnSubst, GenericArg,
    GenericArgData, Interner, OpaqueTy, ProjectionTy, Substitution, TraitRef, Ty, TyKind,
    WhereClause,
};

/// This allows walking structures that contain types to do something with those
/// types, similar to Chalk's `Fold` trait.
pub trait TypeWalk {
    fn walk(&self, f: &mut impl FnMut(&Ty));
}

impl TypeWalk for Ty {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        match self.kind(&Interner) {
            TyKind::Alias(AliasTy::Projection(p_ty)) => {
                for t in p_ty.substitution.iter(&Interner) {
                    t.walk(f);
                }
            }
            TyKind::Alias(AliasTy::Opaque(o_ty)) => {
                for t in o_ty.substitution.iter(&Interner) {
                    t.walk(f);
                }
            }
            TyKind::Dyn(dyn_ty) => {
                for p in dyn_ty.bounds.skip_binders().interned().iter() {
                    p.walk(f);
                }
            }
            TyKind::Slice(ty)
            | TyKind::Array(ty, _)
            | TyKind::Ref(_, _, ty)
            | TyKind::Raw(_, ty) => {
                ty.walk(f);
            }
            TyKind::Function(fn_pointer) => {
                fn_pointer.substitution.0.walk(f);
            }
            TyKind::Adt(_, substs)
            | TyKind::FnDef(_, substs)
            | TyKind::Tuple(_, substs)
            | TyKind::OpaqueType(_, substs)
            | TyKind::AssociatedType(_, substs)
            | TyKind::Closure(.., substs) => {
                substs.walk(f);
            }
            _ => {}
        }
        f(self);
    }
}

impl<T: TypeWalk> TypeWalk for Vec<T> {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        for t in self {
            t.walk(f);
        }
    }
}

impl TypeWalk for OpaqueTy {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.substitution.walk(f);
    }
}

impl TypeWalk for ProjectionTy {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.substitution.walk(f);
    }
}

impl TypeWalk for AliasTy {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        match self {
            AliasTy::Projection(it) => it.walk(f),
            AliasTy::Opaque(it) => it.walk(f),
        }
    }
}

impl TypeWalk for GenericArg {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        match &self.interned() {
            GenericArgData::Ty(ty) => {
                ty.walk(f);
            }
        }
    }
}

impl TypeWalk for Substitution {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        for t in self.iter(&Interner) {
            t.walk(f);
        }
    }
}

impl<T: TypeWalk + HasInterner<Interner = Interner>> TypeWalk for Binders<T> {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.skip_binders().walk(f);
    }
}

impl TypeWalk for TraitRef {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.substitution.walk(f);
    }
}

impl TypeWalk for WhereClause {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        match self {
            WhereClause::Implemented(trait_ref) => trait_ref.walk(f),
            WhereClause::AliasEq(alias_eq) => alias_eq.walk(f),
        }
    }
}

impl TypeWalk for CallableSig {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        for t in self.params_and_return.iter() {
            t.walk(f);
        }
    }
}

impl TypeWalk for AliasEq {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.ty.walk(f);
        match &self.alias {
            AliasTy::Projection(projection_ty) => projection_ty.walk(f),
            AliasTy::Opaque(opaque) => opaque.walk(f),
        }
    }
}

impl TypeWalk for FnSubst<Interner> {
    fn walk(&self, f: &mut impl FnMut(&Ty)) {
        self.0.walk(f)
    }
}
<|endoftext|>


<commit_before>use std::fmt;
use std::fmt::{Debug, Formatter};
use track::{Span, Posn, Untrack};
use word::{Reserved, Name};

#[derive(Debug, PartialEq, Clone)]
pub struct Token {
    pub location: Span,
    pub newline: bool,    // was there a newline between the preceding token and this one?
    pub value: TokenData
}

impl Token {
    pub fn new(start: Posn, end: Posn, value: TokenData) -> Token {
        Token {
            location: Span { start: start, end: end },
            newline: false,
            value: value
        }
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum TokenData {
    Reserved(Reserved),

    // 11.7 Punctuators
    LBrace,
    RBrace,
    LParen,
    RParen,
    LBrack,
    RBrack,
    Dot,
    Ellipsis,
    Semi,
    Comma,
    LAngle,
    RAngle,
    LEq,
    GEq,
    Eq,
    NEq,
    StrictEq,
    StrictNEq,
    Plus,
    Minus,
    Star,
    Mod,
    Slash,
    Inc,
    Dec,
    LShift,
    RShift,
    URShift,
    BitAnd,
    BitOr,
    BitXor,
    Bang,
    Tilde,
    LogicalAnd,
    LogicalOr,
    Question,
    Colon,
    Assign,
    PlusAssign,
    MinusAssign,
    StarAssign,
    SlashAssign,
    ModAssign,
    LShiftAssign,
    RShiftAssign,
    URShiftAssign,
    BitAndAssign,
    BitOrAssign,
    BitXorAssign,
    Arrow,

    Number(NumberLiteral),
    String(StringLiteral),
    RegExp(RegExpLiteral),

    Identifier(Name),

    EOF
}

impl TokenData {
    pub fn is_string(&self) -> bool {
        match *self {
            TokenData::String(_) => true,
            _ => false
        }
    }
}

#[derive(Clone)]
pub struct RegExpLiteral {
    pub pattern: String,
    pub flags: Vec<char>
}

impl Untrack for RegExpLiteral {
    fn untrack(&mut self) {}
}

trait CharsEx {
    fn alphabetize(&self) -> Vec<char>;
}

impl CharsEx for Vec<char> {
    fn alphabetize(&self) -> Vec<char> {
        let mut x: Vec<char> = self.to_vec();
        x.sort();
        x
    }
}

impl Debug for RegExpLiteral {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        fmt.debug_struct("RegExpLiteral")
            .field("pattern", &self.pattern)
            .field("flags", &self.flags.alphabetize())
            .finish()
    }
}

impl PartialEq for RegExpLiteral {
    fn eq(&self, other: &Self) -> bool {
        (self.pattern == other.pattern) &&
        (self.flags.alphabetize() == other.flags.alphabetize())
    }
}

#[derive(Clone)]
pub struct StringLiteral {
    pub source: Option<String>,
    pub value: String
}

impl Untrack for StringLiteral {
    fn untrack(&mut self) {}
}

impl Debug for StringLiteral {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        fmt.debug_struct("StringLiteral")
            .field("value", &self.value)
            .finish()
    }
}

impl PartialEq for StringLiteral {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}

#[derive(Clone)]
pub struct NumberLiteral {
    pub source: Option<NumberSource>,
    pub value: f64
}

impl Untrack for NumberLiteral {
    fn untrack(&mut self) {}
}

impl Debug for NumberLiteral {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        fmt.debug_struct("NumberLiteral")
            .field("value", &self.value)
            .finish()
    }
}

impl PartialEq for NumberLiteral {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum NumberSource {
    DecimalInt(String, Option<Exp>),
    RadixInt(Radix, String),
    Float(Option<String>, Option<String>, Option<Exp>)
}

fn format_sign(sign: &Option<Sign>) -> String {
    match *sign {
        Some(Sign::Minus) => "-",
        _ => ""
    }.to_string()
}

fn format_int(src: &Option<String>) -> String {
    match *src {
        None        => "".to_string(),
        Some(ref s) => s.to_string()
    }
}

impl NumberSource {
    pub fn value(&self) -> f64 {
        match *self {
            NumberSource::DecimalInt(ref mantissa, None) => {
                mantissa.parse().unwrap()
            }
            NumberSource::DecimalInt(ref mantissa, Some(Exp { ref sign, ref value, .. })) => {
                format!("{}e{}{}", mantissa, format_sign(sign), value).parse().unwrap()
            }
            NumberSource::RadixInt(ref radix, ref src) => {
                let i = i64::from_str_radix(&src[..], radix.value()).unwrap();
                i as f64
            }
            NumberSource::Float(ref ip, ref fp, None) => {
                format!("{}.{}", format_int(ip), format_int(fp)).parse().unwrap()
            }
            NumberSource::Float(ref ip, ref fp, Some(Exp { ref sign, ref value, .. })) => {
                format!("{}.{}e{}{}", format_int(ip), format_int(fp), format_sign(sign), value).parse().unwrap()
            }
        }
    }

    pub fn into_token_data(self) -> TokenData {
        let value = self.value();
        TokenData::Number(NumberLiteral {
            source: Some(self),
            value: value
        })
    }
}

#[derive(Debug, PartialEq, Clone)]
pub struct Exp {
    pub e: CharCase,
    pub sign: Option<Sign>,
    pub value: String
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Radix {
    Bin(CharCase),
    Oct(Option<CharCase>),
    Hex(CharCase)
}

impl Radix {
    pub fn value(&self) -> u32 {
        match *self {
            Radix::Bin(_) => 2,
            Radix::Oct(_) => 8,
            Radix::Hex(_) => 16
        }
    }
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum CharCase {
    LowerCase,
    UpperCase
}

#[derive(Debug, PartialEq, Copy, Clone)]
#[cfg_attr(test, derive(Deserialize))]
pub enum Sign {
    #[cfg_attr(test, serde(rename = "+"))]
    Plus,

    #[cfg_attr(test, serde(rename = "-"))]
    Minus
}
<commit_msg>Add manual radix integer decoding<commit_after>use std::fmt;
use std::fmt::{Debug, Formatter};
use track::{Span, Posn, Untrack};
use word::{Reserved, Name};

#[derive(Debug, PartialEq, Clone)]
pub struct Token {
    pub location: Span,
    pub newline: bool,    // was there a newline between the preceding token and this one?
    pub value: TokenData
}

impl Token {
    pub fn new(start: Posn, end: Posn, value: TokenData) -> Token {
        Token {
            location: Span { start: start, end: end },
            newline: false,
            value: value
        }
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum TokenData {
    Reserved(Reserved),

    // 11.7 Punctuators
    LBrace,
    RBrace,
    LParen,
    RParen,
    LBrack,
    RBrack,
    Dot,
    Ellipsis,
    Semi,
    Comma,
    LAngle,
    RAngle,
    LEq,
    GEq,
    Eq,
    NEq,
    StrictEq,
    StrictNEq,
    Plus,
    Minus,
    Star,
    Mod,
    Slash,
    Inc,
    Dec,
    LShift,
    RShift,
    URShift,
    BitAnd,
    BitOr,
    BitXor,
    Bang,
    Tilde,
    LogicalAnd,
    LogicalOr,
    Question,
    Colon,
    Assign,
    PlusAssign,
    MinusAssign,
    StarAssign,
    SlashAssign,
    ModAssign,
    LShiftAssign,
    RShiftAssign,
    URShiftAssign,
    BitAndAssign,
    BitOrAssign,
    BitXorAssign,
    Arrow,

    Number(NumberLiteral),
    String(StringLiteral),
    RegExp(RegExpLiteral),

    Identifier(Name),

    EOF
}

impl TokenData {
    pub fn is_string(&self) -> bool {
        match *self {
            TokenData::String(_) => true,
            _ => false
        }
    }
}

#[derive(Clone)]
pub struct RegExpLiteral {
    pub pattern: String,
    pub flags: Vec<char>
}

impl Untrack for RegExpLiteral {
    fn untrack(&mut self) {}
}

trait CharsEx {
    fn alphabetize(&self) -> Vec<char>;
}

impl CharsEx for Vec<char> {
    fn alphabetize(&self) -> Vec<char> {
        let mut x: Vec<char> = self.to_vec();
        x.sort();
        x
    }
}

impl Debug for RegExpLiteral {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        fmt.debug_struct("RegExpLiteral")
            .field("pattern", &self.pattern)
            .field("flags", &self.flags.alphabetize())
            .finish()
    }
}

impl PartialEq for RegExpLiteral {
    fn eq(&self, other: &Self) -> bool {
        (self.pattern == other.pattern) &&
        (self.flags.alphabetize() == other.flags.alphabetize())
    }
}

#[derive(Clone)]
pub struct StringLiteral {
    pub source: Option<String>,
    pub value: String
}

impl Untrack for StringLiteral {
    fn untrack(&mut self) {}
}

impl Debug for StringLiteral {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        fmt.debug_struct("StringLiteral")
            .field("value", &self.value)
            .finish()
    }
}

impl PartialEq for StringLiteral {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}

#[derive(Clone)]
pub struct NumberLiteral {
    pub source: Option<NumberSource>,
    pub value: f64
}

impl Untrack for NumberLiteral {
    fn untrack(&mut self) {}
}

impl Debug for NumberLiteral {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        fmt.debug_struct("NumberLiteral")
            .field("value", &self.value)
            .finish()
    }
}

impl PartialEq for NumberLiteral {
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum NumberSource {
    DecimalInt(String, Option<Exp>),
    RadixInt(Radix, String),
    Float(Option<String>, Option<String>, Option<Exp>)
}

fn format_sign(sign: &Option<Sign>) -> String {
    match *sign {
        Some(Sign::Minus) => "-",
        _ => ""
    }.to_string()
}

fn format_int(src: &Option<String>) -> String {
    match *src {
        None        => "".to_string(),
        Some(ref s) => s.to_string()
    }
}

impl NumberSource {
    pub fn value(&self) -> f64 {
        match *self {
            NumberSource::DecimalInt(ref mantissa, None) => {
                mantissa.parse().unwrap()
            }
            NumberSource::DecimalInt(ref mantissa, Some(Exp { ref sign, ref value, .. })) => {
                format!("{}e{}{}", mantissa, format_sign(sign), value).parse().unwrap()
            }
            NumberSource::RadixInt(ref radix, ref src) => {
                let radix = radix.value();
                let mult = radix as f64;

                src.chars()
                .map(|c| c.to_digit(radix).unwrap() as f64)
                .fold(0f64, |res, digit| res.mul_add(mult, digit))
            }
            NumberSource::Float(ref ip, ref fp, None) => {
                format!("{}.{}", format_int(ip), format_int(fp)).parse().unwrap()
            }
            NumberSource::Float(ref ip, ref fp, Some(Exp { ref sign, ref value, .. })) => {
                format!("{}.{}e{}{}", format_int(ip), format_int(fp), format_sign(sign), value).parse().unwrap()
            }
        }
    }

    pub fn into_token_data(self) -> TokenData {
        let value = self.value();
        TokenData::Number(NumberLiteral {
            source: Some(self),
            value: value
        })
    }
}

#[derive(Debug, PartialEq, Clone)]
pub struct Exp {
    pub e: CharCase,
    pub sign: Option<Sign>,
    pub value: String
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Radix {
    Bin(CharCase),
    Oct(Option<CharCase>),
    Hex(CharCase)
}

impl Radix {
    pub fn value(&self) -> u32 {
        match *self {
            Radix::Bin(_) => 2,
            Radix::Oct(_) => 8,
            Radix::Hex(_) => 16
        }
    }
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum CharCase {
    LowerCase,
    UpperCase
}

#[derive(Debug, PartialEq, Copy, Clone)]
#[cfg_attr(test, derive(Deserialize))]
pub enum Sign {
    #[cfg_attr(test, serde(rename = "+"))]
    Plus,

    #[cfg_attr(test, serde(rename = "-"))]
    Minus
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BlobBinding;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
use net_traits::blob_url_store::{get_blob_origin, BlobBuf};
use net_traits::filemanager_thread::{FileManagerThreadMsg, ReadFileProgress, RelativePos};
use net_traits::{CoreResourceMsg, IpcSend};
use profile_traits::ipc;
use std::mem;
use std::ops::Index;
use std::path::PathBuf;
use uuid::Uuid;

/// File-based blob
#[derive(JSTraceable)]
pub struct FileBlob {
    id: Uuid,
    name: Option<PathBuf>,
    cache: DomRefCell<Option<Vec<u8>>>,
    size: u64,
}

/// Different backends of Blob
#[must_root]
#[derive(JSTraceable)]
pub enum BlobImpl {
    /// File-based blob, whose content lives in the net process
    File(FileBlob),
    /// Memory-based blob, whose content lives in the script process
    Memory(Vec<u8>),
    /// Sliced blob, including parent blob reference and
    /// relative positions of current slicing range,
    /// IMPORTANT: The depth of tree is only two, i.e. the parent Blob must be
    /// either File-based or Memory-based
    Sliced(Dom<Blob>, RelativePos),
}

impl BlobImpl {
    /// Construct memory-backed BlobImpl
    #[allow(unrooted_must_root)]
    pub fn new_from_bytes(bytes: Vec<u8>) -> BlobImpl {
        BlobImpl::Memory(bytes)
    }

    /// Construct file-backed BlobImpl from File ID
    pub fn new_from_file(file_id: Uuid, name: PathBuf, size: u64) -> BlobImpl {
        BlobImpl::File(FileBlob {
            id: file_id,
            name: Some(name),
            cache: DomRefCell::new(None),
            size: size,
        })
    }
}

// https://w3c.github.io/FileAPI/#blob
#[dom_struct]
pub struct Blob {
    reflector_: Reflector,
    #[ignore_malloc_size_of = "No clear owner"]
    blob_impl: DomRefCell<BlobImpl>,
    /// content-type string
    type_string: String,
}

impl Blob {
    #[allow(unrooted_must_root)]
    pub fn new(global: &GlobalScope, blob_impl: BlobImpl, typeString: String) -> DomRoot<Blob> {
        let boxed_blob = Box::new(Blob::new_inherited(blob_impl, typeString));
        reflect_dom_object(boxed_blob, global, BlobBinding::Wrap)
    }

    #[allow(unrooted_must_root)]
    pub fn new_inherited(blob_impl: BlobImpl, type_string: String) -> Blob {
        Blob {
            reflector_: Reflector::new(),
            blob_impl: DomRefCell::new(blob_impl),
            // NOTE: Guarding the format correctness here,
            // https://w3c.github.io/FileAPI/#dfn-type
            type_string: normalize_type_string(&type_string),
        }
    }

    #[allow(unrooted_must_root)]
    fn new_sliced(
        parent: &Blob,
        rel_pos: RelativePos,
        relative_content_type: DOMString,
    ) -> DomRoot<Blob> {
        let blob_impl = match *parent.blob_impl.borrow() {
            BlobImpl::File(_) => {
                // Create new parent node
                BlobImpl::Sliced(Dom::from_ref(parent), rel_pos)
            },
            BlobImpl::Memory(_) => {
                // Create new parent node
                BlobImpl::Sliced(Dom::from_ref(parent), rel_pos)
            },
            BlobImpl::Sliced(ref grandparent, ref old_rel_pos) => {
                // Adjust the slicing position, using same parent
                BlobImpl::Sliced(grandparent.clone(), old_rel_pos.slice_inner(&rel_pos))
            },
        };

        Blob::new(&parent.global(), blob_impl, relative_content_type.into())
    }

    // https://w3c.github.io/FileAPI/#constructorBlob
    pub fn Constructor(
        global: &GlobalScope,
        blobParts: Option<Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>>,
        blobPropertyBag: &BlobBinding::BlobPropertyBag,
    ) -> Fallible<DomRoot<Blob>> {
        // TODO: accept other blobParts types - ArrayBuffer or ArrayBufferView
        let bytes: Vec<u8> = match blobParts {
            None => Vec::new(),
            Some(blobparts) => match blob_parts_to_bytes(blobparts) {
                Ok(bytes) => bytes,
                Err(_) => return Err(Error::InvalidCharacter),
            },
        };

        Ok(Blob::new(
            global,
            BlobImpl::new_from_bytes(bytes),
            blobPropertyBag.type_.to_string(),
        ))
    }

    /// Get a slice to inner data, this might incur synchronous read and caching
    pub fn get_bytes(&self) -> Result<Vec<u8>, ()> {
        match *self.blob_impl.borrow() {
            BlobImpl::File(ref f) => {
                let (buffer, is_new_buffer) = match *f.cache.borrow() {
                    Some(ref bytes) => (bytes.clone(), false),
                    None => {
                        let bytes = read_file(&self.global(), f.id.clone())?;
                        (bytes, true)
                    },
                };

                // Cache
                if is_new_buffer {
                    *f.cache.borrow_mut() = Some(buffer.clone());
                }

                Ok(buffer)
            },
            BlobImpl::Memory(ref s) => Ok(s.clone()),
            BlobImpl::Sliced(ref parent, ref rel_pos) => parent.get_bytes().map(|v| {
                let range = rel_pos.to_abs_range(v.len());
                v.index(range).to_vec()
            }),
        }
    }

    /// Get a copy of the type_string
    pub fn type_string(&self) -> String {
        self.type_string.clone()
    }

    /// Get a FileID representing the Blob content,
    /// used by URL.createObjectURL
    pub fn get_blob_url_id(&self) -> Uuid {
        let opt_sliced_parent = match *self.blob_impl.borrow() {
            BlobImpl::Sliced(ref parent, ref rel_pos) => {
                Some((
                    parent.promote(/* set_valid is */ false),
                    rel_pos.clone(),
                    parent.Size(),
                ))
            },
            _ => None,
        };

        match opt_sliced_parent {
            Some((parent_id, rel_pos, size)) => {
                self.create_sliced_url_id(&parent_id, &rel_pos, size)
            },
            None => self.promote(/* set_valid is */ true),
        }
    }

    /// Promote non-Slice blob:
    /// 1. Memory-based: The bytes in data slice will be transferred to file manager thread.
    /// 2. File-based: If set_valid, then activate the FileID so it can serve as URL
    /// Depending on set_valid, the returned FileID can be part of
    /// valid or invalid Blob URL.
    fn promote(&self, set_valid: bool) -> Uuid {
        let mut bytes = vec![];
        let global_url = self.global().get_url();

        match *self.blob_impl.borrow_mut() {
            BlobImpl::Sliced(_, _) => {
                debug!("Sliced can't have a sliced parent");
                // Return dummy id
                return Uuid::new_v4();
            },
            BlobImpl::File(ref f) => {
                if set_valid {
                    let origin = get_blob_origin(&global_url);
                    let (tx, rx) =
                        ipc::channel(self.global().time_profiler_chan().clone()).unwrap();

                    let msg =
                        FileManagerThreadMsg::ActivateBlobURL(f.id.clone(), tx, origin.clone());
                    self.send_to_file_manager(msg);

                    match rx.recv().unwrap() {
                        Ok(_) => return f.id.clone(),
                        // Return a dummy id on error
                        Err(_) => return Uuid::new_v4(),
                    }
                } else {
                    // no need to activate
                    return f.id.clone();
                }
            },
            BlobImpl::Memory(ref mut bytes_in) => mem::swap(bytes_in, &mut bytes),
        };

        let origin = get_blob_origin(&global_url);

        let blob_buf = BlobBuf {
            filename: None,
            type_string: self.type_string.clone(),
            size: bytes.len() as u64,
            bytes: bytes.to_vec(),
        };

        let id = Uuid::new_v4();
        let msg = FileManagerThreadMsg::PromoteMemory(id, blob_buf, set_valid, origin.clone());
        self.send_to_file_manager(msg);

        *self.blob_impl.borrow_mut() = BlobImpl::File(FileBlob {
            id: id.clone(),
            name: None,
            cache: DomRefCell::new(Some(bytes.to_vec())),
            size: bytes.len() as u64,
        });
        id
    }

    /// Get a FileID representing sliced parent-blob content
    fn create_sliced_url_id(
        &self,
        parent_id: &Uuid,
        rel_pos: &RelativePos,
        parent_len: u64,
    ) -> Uuid {
        let origin = get_blob_origin(&self.global().get_url());

        let (tx, rx) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
        let msg = FileManagerThreadMsg::AddSlicedURLEntry(
            parent_id.clone(),
            rel_pos.clone(),
            tx,
            origin.clone(),
        );
        self.send_to_file_manager(msg);
        match rx.recv().expect("File manager thread is down") {
            Ok(new_id) => {
                *self.blob_impl.borrow_mut() = BlobImpl::File(FileBlob {
                    id: new_id.clone(),
                    name: None,
                    cache: DomRefCell::new(None),
                    size: rel_pos.to_abs_range(parent_len as usize).len() as u64,
                });

                // Return the indirect id reference
                new_id
            },
            Err(_) => {
                // Return dummy id
                Uuid::new_v4()
            },
        }
    }

    /// Cleanups at the time of destruction/closing
    fn clean_up_file_resource(&self) {
        if let BlobImpl::File(ref f) = *self.blob_impl.borrow() {
            let origin = get_blob_origin(&self.global().get_url());

            let (tx, rx) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();

            let msg = FileManagerThreadMsg::DecRef(f.id.clone(), origin, tx);
            self.send_to_file_manager(msg);
            let _ = rx.recv().unwrap();
        }
    }

    fn send_to_file_manager(&self, msg: FileManagerThreadMsg) {
        let global = self.global();
        let resource_threads = global.resource_threads();
        let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));
    }
}

impl Drop for Blob {
    fn drop(&mut self) {
        self.clean_up_file_resource();
    }
}

fn read_file(global: &GlobalScope, id: Uuid) -> Result<Vec<u8>, ()> {
    let resource_threads = global.resource_threads();
    let (chan, recv) = ipc::channel(global.time_profiler_chan().clone()).map_err(|_| ())?;
    let origin = get_blob_origin(&global.get_url());
    let check_url_validity = false;
    let msg = FileManagerThreadMsg::ReadFile(chan, id, check_url_validity, origin);
    let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));

    let mut bytes = vec![];

    loop {
        match recv.recv().unwrap() {
            Ok(ReadFileProgress::Meta(mut blob_buf)) => {
                bytes.append(&mut blob_buf.bytes);
            },
            Ok(ReadFileProgress::Partial(mut bytes_in)) => {
                bytes.append(&mut bytes_in);
            },
            Ok(ReadFileProgress::EOF) => {
                return Ok(bytes);
            },
            Err(_) => return Err(()),
        }
    }
}

/// Extract bytes from BlobParts, used by Blob and File constructor
/// <https://w3c.github.io/FileAPI/#constructorBlob>
#[allow(unsafe_code)]
pub fn blob_parts_to_bytes(
    mut blobparts: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>,
) -> Result<Vec<u8>, ()> {
    let mut ret = vec![];
    for blobpart in &mut blobparts {
        match blobpart {
            &mut ArrayBufferOrArrayBufferViewOrBlobOrString::String(ref s) => {
                ret.extend(s.as_bytes());
            },
            &mut ArrayBufferOrArrayBufferViewOrBlobOrString::Blob(ref b) => {
                let bytes = b.get_bytes().unwrap_or(vec![]);
                ret.extend(bytes);
            },
            &mut ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBuffer(ref mut a) => unsafe {
                let bytes = a.as_slice();
                ret.extend(bytes);
            },
            &mut ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBufferView(ref mut a) => unsafe {
                let bytes = a.as_slice();
                ret.extend(bytes);
            },
        }
    }

    Ok(ret)
}

impl BlobMethods for Blob {
    // https://w3c.github.io/FileAPI/#dfn-size
    fn Size(&self) -> u64 {
        match *self.blob_impl.borrow() {
            BlobImpl::File(ref f) => f.size,
            BlobImpl::Memory(ref v) => v.len() as u64,
            BlobImpl::Sliced(ref parent, ref rel_pos) => {
                rel_pos.to_abs_range(parent.Size() as usize).len() as u64
            },
        }
    }

    // https://w3c.github.io/FileAPI/#dfn-type
    fn Type(&self) -> DOMString {
        DOMString::from(self.type_string.clone())
    }

    // https://w3c.github.io/FileAPI/#slice-method-algo
    fn Slice(
        &self,
        start: Option<i64>,
        end: Option<i64>,
        content_type: Option<DOMString>,
    ) -> DomRoot<Blob> {
        let rel_pos = RelativePos::from_opts(start, end);
        Blob::new_sliced(self, rel_pos, content_type.unwrap_or(DOMString::from("")))
    }
}

/// Get the normalized, MIME-parsable type string
/// <https://w3c.github.io/FileAPI/#dfn-type>
/// XXX: We will relax the restriction here,
/// since the spec has some problem over this part.
/// see https://github.com/w3c/FileAPI/issues/43
fn normalize_type_string(s: &str) -> String {
    if is_ascii_printable(s) {
        let s_lower = s.to_ascii_lowercase();
        // match s_lower.parse() as Result<Mime, ()> {
        // Ok(_) => s_lower,
        // Err(_) => "".to_string()
        s_lower
    } else {
        "".to_string()
    }
}

fn is_ascii_printable(string: &str) -> bool {
    // Step 5.1 in Sec 5.1 of File API spec
    // https://w3c.github.io/FileAPI/#constructorBlob
    string.chars().all(|c| c >= '\x20' && c <= '\x7E')
}
<commit_msg>Auto merge of #23886 - servo:jdm-patch-46, r=asajeffrey<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BlobBinding;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
use net_traits::blob_url_store::{get_blob_origin, BlobBuf};
use net_traits::filemanager_thread::{FileManagerThreadMsg, ReadFileProgress, RelativePos};
use net_traits::{CoreResourceMsg, IpcSend};
use profile_traits::ipc;
use std::mem;
use std::ops::Index;
use std::path::PathBuf;
use uuid::Uuid;

/// File-based blob
#[derive(JSTraceable)]
pub struct FileBlob {
    id: Uuid,
    name: Option<PathBuf>,
    cache: DomRefCell<Option<Vec<u8>>>,
    size: u64,
}

/// Different backends of Blob
#[must_root]
#[derive(JSTraceable)]
pub enum BlobImpl {
    /// File-based blob, whose content lives in the net process
    File(FileBlob),
    /// Memory-based blob, whose content lives in the script process
    Memory(Vec<u8>),
    /// Sliced blob, including parent blob reference and
    /// relative positions of current slicing range,
    /// IMPORTANT: The depth of tree is only two, i.e. the parent Blob must be
    /// either File-based or Memory-based
    Sliced(Dom<Blob>, RelativePos),
}

impl BlobImpl {
    /// Construct memory-backed BlobImpl
    #[allow(unrooted_must_root)]
    pub fn new_from_bytes(bytes: Vec<u8>) -> BlobImpl {
        BlobImpl::Memory(bytes)
    }

    /// Construct file-backed BlobImpl from File ID
    pub fn new_from_file(file_id: Uuid, name: PathBuf, size: u64) -> BlobImpl {
        BlobImpl::File(FileBlob {
            id: file_id,
            name: Some(name),
            cache: DomRefCell::new(None),
            size: size,
        })
    }
}

// https://w3c.github.io/FileAPI/#blob
#[dom_struct]
pub struct Blob {
    reflector_: Reflector,
    #[ignore_malloc_size_of = "No clear owner"]
    blob_impl: DomRefCell<BlobImpl>,
    /// content-type string
    type_string: String,
}

impl Blob {
    #[allow(unrooted_must_root)]
    pub fn new(global: &GlobalScope, blob_impl: BlobImpl, typeString: String) -> DomRoot<Blob> {
        let boxed_blob = Box::new(Blob::new_inherited(blob_impl, typeString));
        reflect_dom_object(boxed_blob, global, BlobBinding::Wrap)
    }

    #[allow(unrooted_must_root)]
    pub fn new_inherited(blob_impl: BlobImpl, type_string: String) -> Blob {
        Blob {
            reflector_: Reflector::new(),
            blob_impl: DomRefCell::new(blob_impl),
            // NOTE: Guarding the format correctness here,
            // https://w3c.github.io/FileAPI/#dfn-type
            type_string: normalize_type_string(&type_string),
        }
    }

    #[allow(unrooted_must_root)]
    fn new_sliced(
        parent: &Blob,
        rel_pos: RelativePos,
        relative_content_type: DOMString,
    ) -> DomRoot<Blob> {
        let blob_impl = match *parent.blob_impl.borrow() {
            BlobImpl::File(_) => {
                // Create new parent node
                BlobImpl::Sliced(Dom::from_ref(parent), rel_pos)
            },
            BlobImpl::Memory(_) => {
                // Create new parent node
                BlobImpl::Sliced(Dom::from_ref(parent), rel_pos)
            },
            BlobImpl::Sliced(ref grandparent, ref old_rel_pos) => {
                // Adjust the slicing position, using same parent
                BlobImpl::Sliced(grandparent.clone(), old_rel_pos.slice_inner(&rel_pos))
            },
        };

        Blob::new(&parent.global(), blob_impl, relative_content_type.into())
    }

    // https://w3c.github.io/FileAPI/#constructorBlob
    pub fn Constructor(
        global: &GlobalScope,
        blobParts: Option<Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>>,
        blobPropertyBag: &BlobBinding::BlobPropertyBag,
    ) -> Fallible<DomRoot<Blob>> {
        // TODO: accept other blobParts types - ArrayBuffer or ArrayBufferView
        let bytes: Vec<u8> = match blobParts {
            None => Vec::new(),
            Some(blobparts) => match blob_parts_to_bytes(blobparts) {
                Ok(bytes) => bytes,
                Err(_) => return Err(Error::InvalidCharacter),
            },
        };

        Ok(Blob::new(
            global,
            BlobImpl::new_from_bytes(bytes),
            blobPropertyBag.type_.to_string(),
        ))
    }

    /// Get a slice to inner data, this might incur synchronous read and caching
    pub fn get_bytes(&self) -> Result<Vec<u8>, ()> {
        match *self.blob_impl.borrow() {
            BlobImpl::File(ref f) => {
                let (buffer, is_new_buffer) = match *f.cache.borrow() {
                    Some(ref bytes) => (bytes.clone(), false),
                    None => {
                        let bytes = read_file(&self.global(), f.id.clone())?;
                        (bytes, true)
                    },
                };

                // Cache
                if is_new_buffer {
                    *f.cache.borrow_mut() = Some(buffer.clone());
                }

                Ok(buffer)
            },
            BlobImpl::Memory(ref s) => Ok(s.clone()),
            BlobImpl::Sliced(ref parent, ref rel_pos) => parent.get_bytes().map(|v| {
                let range = rel_pos.to_abs_range(v.len());
                v.index(range).to_vec()
            }),
        }
    }

    /// Get a copy of the type_string
    pub fn type_string(&self) -> String {
        self.type_string.clone()
    }

    /// Get a FileID representing the Blob content,
    /// used by URL.createObjectURL
    pub fn get_blob_url_id(&self) -> Uuid {
        let opt_sliced_parent = match *self.blob_impl.borrow() {
            BlobImpl::Sliced(ref parent, ref rel_pos) => {
                Some((
                    parent.promote(/* set_valid is */ false),
                    rel_pos.clone(),
                    parent.Size(),
                ))
            },
            _ => None,
        };

        match opt_sliced_parent {
            Some((parent_id, rel_pos, size)) => {
                self.create_sliced_url_id(&parent_id, &rel_pos, size)
            },
            None => self.promote(/* set_valid is */ true),
        }
    }

    /// Promote non-Slice blob:
    /// 1. Memory-based: The bytes in data slice will be transferred to file manager thread.
    /// 2. File-based: If set_valid, then activate the FileID so it can serve as URL
    /// Depending on set_valid, the returned FileID can be part of
    /// valid or invalid Blob URL.
    fn promote(&self, set_valid: bool) -> Uuid {
        let mut bytes = vec![];
        let global_url = self.global().get_url();

        match *self.blob_impl.borrow_mut() {
            BlobImpl::Sliced(_, _) => {
                debug!("Sliced can't have a sliced parent");
                // Return dummy id
                return Uuid::new_v4();
            },
            BlobImpl::File(ref f) => {
                if set_valid {
                    let origin = get_blob_origin(&global_url);
                    let (tx, rx) =
                        ipc::channel(self.global().time_profiler_chan().clone()).unwrap();

                    let msg =
                        FileManagerThreadMsg::ActivateBlobURL(f.id.clone(), tx, origin.clone());
                    self.send_to_file_manager(msg);

                    match rx.recv().unwrap() {
                        Ok(_) => return f.id.clone(),
                        // Return a dummy id on error
                        Err(_) => return Uuid::new_v4(),
                    }
                } else {
                    // no need to activate
                    return f.id.clone();
                }
            },
            BlobImpl::Memory(ref mut bytes_in) => mem::swap(bytes_in, &mut bytes),
        };

        let origin = get_blob_origin(&global_url);

        let blob_buf = BlobBuf {
            filename: None,
            type_string: self.type_string.clone(),
            size: bytes.len() as u64,
            bytes: bytes.to_vec(),
        };

        let id = Uuid::new_v4();
        let msg = FileManagerThreadMsg::PromoteMemory(id, blob_buf, set_valid, origin.clone());
        self.send_to_file_manager(msg);

        *self.blob_impl.borrow_mut() = BlobImpl::File(FileBlob {
            id: id.clone(),
            name: None,
            cache: DomRefCell::new(Some(bytes.to_vec())),
            size: bytes.len() as u64,
        });
        id
    }

    /// Get a FileID representing sliced parent-blob content
    fn create_sliced_url_id(
        &self,
        parent_id: &Uuid,
        rel_pos: &RelativePos,
        parent_len: u64,
    ) -> Uuid {
        let origin = get_blob_origin(&self.global().get_url());

        let (tx, rx) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
        let msg = FileManagerThreadMsg::AddSlicedURLEntry(
            parent_id.clone(),
            rel_pos.clone(),
            tx,
            origin.clone(),
        );
        self.send_to_file_manager(msg);
        match rx.recv().expect("File manager thread is down") {
            Ok(new_id) => {
                *self.blob_impl.borrow_mut() = BlobImpl::File(FileBlob {
                    id: new_id.clone(),
                    name: None,
                    cache: DomRefCell::new(None),
                    size: rel_pos.to_abs_range(parent_len as usize).len() as u64,
                });

                // Return the indirect id reference
                new_id
            },
            Err(_) => {
                // Return dummy id
                Uuid::new_v4()
            },
        }
    }

    /// Cleanups at the time of destruction/closing
    fn clean_up_file_resource(&self) {
        if let BlobImpl::File(ref f) = *self.blob_impl.borrow() {
            let origin = get_blob_origin(&self.global().get_url());

            let (tx, rx) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();

            let msg = FileManagerThreadMsg::DecRef(f.id.clone(), origin, tx);
            self.send_to_file_manager(msg);
            let _ = rx.recv();
        }
    }

    fn send_to_file_manager(&self, msg: FileManagerThreadMsg) {
        let global = self.global();
        let resource_threads = global.resource_threads();
        let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));
    }
}

impl Drop for Blob {
    fn drop(&mut self) {
        self.clean_up_file_resource();
    }
}

fn read_file(global: &GlobalScope, id: Uuid) -> Result<Vec<u8>, ()> {
    let resource_threads = global.resource_threads();
    let (chan, recv) = ipc::channel(global.time_profiler_chan().clone()).map_err(|_| ())?;
    let origin = get_blob_origin(&global.get_url());
    let check_url_validity = false;
    let msg = FileManagerThreadMsg::ReadFile(chan, id, check_url_validity, origin);
    let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));

    let mut bytes = vec![];

    loop {
        match recv.recv().unwrap() {
            Ok(ReadFileProgress::Meta(mut blob_buf)) => {
                bytes.append(&mut blob_buf.bytes);
            },
            Ok(ReadFileProgress::Partial(mut bytes_in)) => {
                bytes.append(&mut bytes_in);
            },
            Ok(ReadFileProgress::EOF) => {
                return Ok(bytes);
            },
            Err(_) => return Err(()),
        }
    }
}

/// Extract bytes from BlobParts, used by Blob and File constructor
/// <https://w3c.github.io/FileAPI/#constructorBlob>
#[allow(unsafe_code)]
pub fn blob_parts_to_bytes(
    mut blobparts: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>,
) -> Result<Vec<u8>, ()> {
    let mut ret = vec![];
    for blobpart in &mut blobparts {
        match blobpart {
            &mut ArrayBufferOrArrayBufferViewOrBlobOrString::String(ref s) => {
                ret.extend(s.as_bytes());
            },
            &mut ArrayBufferOrArrayBufferViewOrBlobOrString::Blob(ref b) => {
                let bytes = b.get_bytes().unwrap_or(vec![]);
                ret.extend(bytes);
            },
            &mut ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBuffer(ref mut a) => unsafe {
                let bytes = a.as_slice();
                ret.extend(bytes);
            },
            &mut ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBufferView(ref mut a) => unsafe {
                let bytes = a.as_slice();
                ret.extend(bytes);
            },
        }
    }

    Ok(ret)
}

impl BlobMethods for Blob {
    // https://w3c.github.io/FileAPI/#dfn-size
    fn Size(&self) -> u64 {
        match *self.blob_impl.borrow() {
            BlobImpl::File(ref f) => f.size,
            BlobImpl::Memory(ref v) => v.len() as u64,
            BlobImpl::Sliced(ref parent, ref rel_pos) => {
                rel_pos.to_abs_range(parent.Size() as usize).len() as u64
            },
        }
    }

    // https://w3c.github.io/FileAPI/#dfn-type
    fn Type(&self) -> DOMString {
        DOMString::from(self.type_string.clone())
    }

    // https://w3c.github.io/FileAPI/#slice-method-algo
    fn Slice(
        &self,
        start: Option<i64>,
        end: Option<i64>,
        content_type: Option<DOMString>,
    ) -> DomRoot<Blob> {
        let rel_pos = RelativePos::from_opts(start, end);
        Blob::new_sliced(self, rel_pos, content_type.unwrap_or(DOMString::from("")))
    }
}

/// Get the normalized, MIME-parsable type string
/// <https://w3c.github.io/FileAPI/#dfn-type>
/// XXX: We will relax the restriction here,
/// since the spec has some problem over this part.
/// see https://github.com/w3c/FileAPI/issues/43
fn normalize_type_string(s: &str) -> String {
    if is_ascii_printable(s) {
        let s_lower = s.to_ascii_lowercase();
        // match s_lower.parse() as Result<Mime, ()> {
        // Ok(_) => s_lower,
        // Err(_) => "".to_string()
        s_lower
    } else {
        "".to_string()
    }
}

fn is_ascii_printable(string: &str) -> bool {
    // Step 5.1 in Sec 5.1 of File API spec
    // https://w3c.github.io/FileAPI/#constructorBlob
    string.chars().all(|c| c >= '\x20' && c <= '\x7E')
}
<|endoftext|>