<commit_before>use crate::grammar::ast::{BooleanLit, Identifier, Underscore};
use crate::grammar::model::Fragment;
use nom::bytes::complete::take_while;
use nom::combinator::{map, recognize, verify};
use nom::error::context;
use nom::sequence::pair;
use nom::IResult;

impl<'s> Identifier<'s> {
    /// Reserved words that an identifier must not match.
    pub const RESERVED_WORDS: [&'static str; 3] =
        [BooleanLit::FALSE, BooleanLit::TRUE, Underscore::UNDERSCORE];

    fn new(frag: Fragment<'s>) -> Self {
        Self { frag }
    }

    fn raw_ident(input: Fragment<'s>) -> IResult<Fragment<'s>, Fragment<'s>> {
        verify(
            recognize(pair(
                take_while(|c: char| c.is_ascii_alphabetic() || c == '_'),
                take_while(|c: char| c.is_ascii_alphanumeric() || c == '_'),
            )),
            |fr: &Fragment<'s>| {
                Self::RESERVED_WORDS
                    .iter()
                    .all(|s: &&str| *s != fr.source())
            },
        )(input)
    }

    /// Parse an identifier from source code. Identifiers may include
    /// ASCII alphanumerics and underscores, but must not start with a number.
    /// An Identifier also must not be a reserved word.
    pub fn parse(input: Fragment<'s>) -> IResult<Fragment<'s>, Self> {
        context("expected identifier", map(Self::raw_ident, Self::new))(input)
    }
}
<commit_msg>Make identifier parser not override numlit<commit_after>use crate::grammar::ast::{BooleanLit, Identifier, Underscore};
use crate::grammar::model::Fragment;
use nom::bytes::complete::{take_while, take_while1};
use nom::combinator::{map, recognize, verify};
use nom::error::context;
use nom::sequence::pair;
use nom::IResult;

impl<'s> Identifier<'s> {
    /// Reserved words that an identifier must not match.
    pub const RESERVED_WORDS: [&'static str; 3] =
        [BooleanLit::FALSE, BooleanLit::TRUE, Underscore::UNDERSCORE];

    fn new(frag: Fragment<'s>) -> Self {
        Self { frag }
    }

    fn raw_ident(input: Fragment<'s>) -> IResult<Fragment<'s>, Fragment<'s>> {
        verify(
            recognize(pair(
                take_while1(|c: char| c.is_ascii_alphabetic() || c == '_'),
                take_while(|c: char| c.is_ascii_alphanumeric() || c == '_'),
            )),
            |fr: &Fragment<'s>| {
                Self::RESERVED_WORDS
                    .iter()
                    .all(|s: &&str| *s != fr.source())
            },
        )(input)
    }

    /// Parse an identifier from source code. Identifiers may include
    /// ASCII alphanumerics and underscores, but must not start with a number.
    /// An Identifier also must not be a reserved word.
    pub fn parse(input: Fragment<'s>) -> IResult<Fragment<'s>, Self> {
        context("expected identifier", map(Self::raw_ident, Self::new))(input)
    }
}
<|endoftext|>


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

use super::DistanceError;

/// Calculates the [hamming distance](https://en.wikipedia.org/wiki/Hamming_distance) between two strings of equal length.
///
/// The hamming distance is the number of positions at which two strings are different.
/// Unicode is fully supported. Returns an error if the string arguments do not have equal length.
///
/// ## Examples
/// ```
/// use distance::*;
///
/// // Hamming distance
/// let distance = hamming("karolin", "kathrin").unwrap();
/// assert_eq!(distance, 3);
/// ```
///
pub fn hamming(a: &str, b: &str) -> Result<usize, DistanceError> {
    if a.chars().count() != b.chars().count() {
        return Err(DistanceError::InvalidArgs);
    }

    let mut distance = 0;
    for (a_char, b_char) in a.chars().zip(b.chars()) {
        if a_char != b_char {
            distance += 1
        }
    }

    return Ok(distance);
}

#[cfg(test)]
mod tests {
    use super::hamming;

    #[test]
    fn basic() {
        assert_eq!(hamming("sitting", "sitting").unwrap(), 0);
        assert_eq!(hamming("abcdefg", "hijklmn").unwrap(), 7);
        assert_eq!(hamming("karolin", "kathrin").unwrap(), 3);
        assert_eq!(hamming("hello", "world").unwrap(), 4);
        assert_eq!(hamming("Rust", "rust").unwrap(), 1);
    }

    #[test]
    #[should_panic]
    fn different_len() {
        hamming("abra", "kadabra").unwrap();
    }

    #[test]
    fn empty() {
        assert_eq!(hamming("", "").unwrap(), 0);
    }

    #[test]
    fn unicode() {
        assert_eq!(hamming("さようなら", "さようなは").unwrap(), 1);
        assert_eq!(hamming("säge", "sage").unwrap(), 1);
        assert_eq!(hamming("ßäöüé", "ößäüè").unwrap(), 4);
    }

    #[test]
    #[should_panic]
    fn unicode_different_len() {
        hamming("さようならa", "さようなら").unwrap();
    }

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

use super::DistanceError;

/// Calculates the [hamming distance](https://en.wikipedia.org/wiki/Hamming_distance) between two strings of equal length.
///
/// The hamming distance is the number of positions at which two strings are different.
/// Unicode is fully supported. Returns an error if the string arguments do not have equal length.
///
/// ## Examples
/// ```
/// use distance::*;
///
/// // Hamming distance
/// let distance = hamming("karolin", "kathrin").unwrap();
/// assert_eq!(distance, 3);
/// ```
///
pub fn hamming(a: &str, b: &str) -> Result<usize, DistanceError> {
    if a.chars().count() != b.chars().count() {
        return Err(DistanceError::InvalidArgs);
    }

    let mut distance = 0;
    for (a_char, b_char) in a.chars().zip(b.chars()) {
        if a_char != b_char {
            distance += 1
        }
    }

    return Ok(distance);
}

#[cfg(test)]
mod tests {
    use super::hamming;
    use ::DistanceError;

    #[test]
    fn basic() {
        assert_eq!(hamming("sitting", "sitting").unwrap(), 0);
        assert_eq!(hamming("abcdefg", "hijklmn").unwrap(), 7);
        assert_eq!(hamming("karolin", "kathrin").unwrap(), 3);
        assert_eq!(hamming("hello", "world").unwrap(), 4);
        assert_eq!(hamming("Rust", "rust").unwrap(), 1);
    }

    #[test]
    #[allow(unused_variables)]
    fn matching() {
        match hamming("karolin", "kathrin") {
            Ok(distance) => assert_eq!(distance, 3),
            Err(err) => panic!("This should not happen"),
        };

        match hamming("abra", "kadabra") {
            Ok(distance) => panic!("These are not valid inputs"),
            Err(err) => assert_eq!(err, DistanceError::InvalidArgs),
        };
    }

    #[test]
    #[should_panic]
    fn different_len() {
        hamming("abra", "kadabra").unwrap();
    }

    #[test]
    fn empty() {
        assert_eq!(hamming("", "").unwrap(), 0);
    }

    #[test]
    fn unicode() {
        assert_eq!(hamming("さようなら", "さようなは").unwrap(), 1);
        assert_eq!(hamming("säge", "sage").unwrap(), 1);
        assert_eq!(hamming("ßäöüé", "ößäüè").unwrap(), 4);
    }

    #[test]
    #[should_panic]
    fn unicode_different_len() {
        hamming("さようならa", "さようなら").unwrap();
    }

}<|endoftext|>


<commit_before>//! Generates descriptors structure for unstable feature from Unstable Book

use crate::codegen::update;
use crate::codegen::{self, project_root, Mode, Result};
use chrono::prelude::*;
use fs::read_to_string;
use graphql_client::*;
use proc_macro2::TokenStream;
use quote::quote;
use regex::Regex;
use reqwest;
use serde::*;
use std::fs;
use std::fs::File;
use std::io::copy;
use std::io::prelude::*;
use std::path::PathBuf;
use std::process::Command;
use walkdir::WalkDir;

type URI = String;
type DateTime = String;

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "src/codegen/schema.graphql",
    query_path = "src/codegen/last_commit_that_affected_path.graphql"
)]
struct LastCommitThatAffectedPath;

fn deep_destructuring(
    response_body: Response<last_commit_that_affected_path::ResponseData>,
) -> CommitInfo {
    let t = response_body.data.unwrap().repository.unwrap().object.unwrap().on;

    use last_commit_that_affected_path::LastCommitThatAffectedPathRepositoryObjectOn::Commit;
    let commit = match t {
        Commit(data) => data,
        _ => panic!("type does not match"),
    };
    let edges = commit.history.edges.unwrap();
    let node = edges.first().unwrap().as_ref().unwrap().node.as_ref().unwrap();
    CommitInfo { commit_url: node.commit_url.clone(), committed_date: node.committed_date.clone() }
}

struct CommitInfo {
    commit_url: String,
    committed_date: String,
}

fn last_update(
    owner: &str,
    name: &str,
    path: &str,
    auth_token: Option<&str>,
) -> Result<CommitInfo> {
    let query =
        LastCommitThatAffectedPath::build_query(last_commit_that_affected_path::Variables {
            owner: owner.to_owned(),
            name: name.to_owned(),
            path: path.to_owned(),
        });

    let client = reqwest::blocking::Client::new();
    let mut headers = reqwest::header::HeaderMap::new();
    headers.insert("User-Agent", "https://github.com/rust-analyzer/rust-analyzer".parse()?);
    let mut request = client.post("https://api.github.com/graphql").headers(headers).json(&query);

    if auth_token.is_some() {
        request = request.bearer_auth(auth_token.unwrap());
    }

    let response = request.send()?;

    let response_body: Response<last_commit_that_affected_path::ResponseData> = response.json()?;
    Ok(deep_destructuring(response_body))
}

fn generate_descriptor(src_dir: PathBuf) -> Result<TokenStream> {
    let files = WalkDir::new(src_dir.join("language-features"))
        .into_iter()
        .chain(WalkDir::new(src_dir.join("library-features")))
        .filter_map(|e| e.ok())
        .filter(|entry| {
            // Get all `.md ` files
            entry.file_type().is_file()
                && entry.path().extension().map(|ext| ext == "md").unwrap_or(false)
        })
        .collect::<Vec<_>>();

    let definitions = files
        .iter()
        .map(|entry| {
            let path = entry.path();
            let feature_ident =
                format!("{}", path.file_stem().unwrap().to_str().unwrap().replace("-", "_"));
            let doc = format!("{}", std::fs::read_to_string(path).unwrap());

            quote! { LintCompletion { label: #feature_ident, description: #doc } }
        })
        .collect::<Vec<_>>();

    let ts = quote! {
        use crate::completion::LintCompletion;

        pub const UNSTABLE_FEATURE_DESCRIPTOR:  &[LintCompletion] = &[
            #(#definitions),*
        ];
    };
    Ok(ts)
}

fn add_anchor(text: impl std::fmt::Display, anchor: &str) -> String {
    let anchor_str = format!(
        r#"//The anchor is used to check if file is up to date and represent the time
    //of the last commit that affected path where located data for generation
    //ANCHOR: {}"#,
        anchor
    );
    format!("{}\n\n{}\n", anchor_str, text)
}

fn is_actual(path: &PathBuf, str_datetime: &str) -> bool {
    let re = Regex::new(r"//ANCHOR: (\S*)").unwrap();
    let opt_str = fs::read_to_string(path);
    if opt_str.is_err() {
        return false;
    }
    let text = opt_str.unwrap();
    let opt_datetime = re.captures(text.as_str());
    if opt_datetime.is_none() {
        return false;
    }
    let str_file_dt = opt_datetime.unwrap().get(1).unwrap().as_str();
    let file_dt = str_file_dt.parse::<chrono::DateTime<Utc>>().unwrap();
    let datetime = str_datetime.parse::<chrono::DateTime<Utc>>().unwrap();

    file_dt == datetime
}

fn download_tar(
    owner: &str,
    name: &str,
    auth_token: Option<&str>,
    destination: &PathBuf,
    fname: &str,
) -> Result<()> {
    let mut headers = reqwest::header::HeaderMap::new();
    headers.insert("User-Agent", "https://github.com/rust-analyzer/rust-analyzer".parse()?);
    let mut request = reqwest::blocking::Client::new()
        .get(format!("https://api.github.com/repos/{}/{}/tarball", owner, name).as_str())
        .headers(headers);

    if auth_token.is_some() {
        request = request.bearer_auth(auth_token.unwrap());
    }

    let response = request.send()?;
    let download_url = response.url();

    let mut response = reqwest::blocking::Client::new()
        .get(download_url.as_str())
        .send()?;

    let mut n = fname.to_string();
    n.push_str(".tar.gz");
    let fpath = destination.join(n);
    let mut file = File::create(fpath)?;
    response.copy_to(&mut file)?;

    Ok(())
}

pub fn generate_unstable_future_descriptor(mode: Mode) -> Result<()> {
    const auth_token: Option<&str> = None;

    let path = project_root().join(codegen::STORAGE);
    fs::create_dir_all(path.clone())?;

    let commit_info =
        last_update(codegen::REPO_OWNER, codegen::REPO_NAME, codegen::REPO_PATH, auth_token)?;

    if is_actual(
        &project_root().join(codegen::GENERATION_DESTINATION),
        commit_info.committed_date.as_str(),
    ) {
        return Ok(());
    }

    download_tar(codegen::REPO_OWNER, codegen::REPO_NAME, auth_token, &path, "repository")?;
    Command::new("tar")
        .args(&["-xvf", concat!("repository",".tar.gz"), "--wildcards", "*/src/doc/unstable-book/src", "--strip=1"])
        .current_dir(codegen::STORAGE)
        .output()?;

    let src_dir = path.join(codegen::REPO_PATH);
    let gen_holder = generate_descriptor(src_dir)?.to_string();
    let gen_holder = add_anchor(gen_holder, commit_info.committed_date.as_str());

    let destination = project_root().join(codegen::GENERATION_DESTINATION);
    let contents = crate::reformat(gen_holder)?;
    update(destination.as_path(), &contents, mode)?;

    Ok(())
}
<commit_msg>Update xtask/src/codegen/gen_unstable_future_descriptor.rs<commit_after>//! Generates descriptors structure for unstable feature from Unstable Book

use crate::codegen::update;
use crate::codegen::{self, project_root, Mode, Result};
use chrono::prelude::*;
use fs::read_to_string;
use graphql_client::*;
use proc_macro2::TokenStream;
use quote::quote;
use regex::Regex;
use reqwest;
use serde::*;
use std::fs;
use std::fs::File;
use std::io::copy;
use std::io::prelude::*;
use std::path::PathBuf;
use std::process::Command;
use walkdir::WalkDir;

type URI = String;
type DateTime = String;

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "src/codegen/schema.graphql",
    query_path = "src/codegen/last_commit_that_affected_path.graphql"
)]
struct LastCommitThatAffectedPath;

fn deep_destructuring(
    response_body: Response<last_commit_that_affected_path::ResponseData>,
) -> CommitInfo {
    let t = response_body.data.unwrap().repository.unwrap().object.unwrap().on;

    use last_commit_that_affected_path::LastCommitThatAffectedPathRepositoryObjectOn::Commit;
    let commit = match t {
        Commit(data) => data,
        _ => panic!("type does not match"),
    };
    let edges = commit.history.edges.unwrap();
    let node = edges.first().unwrap().as_ref().unwrap().node.as_ref().unwrap();
    CommitInfo { commit_url: node.commit_url.clone(), committed_date: node.committed_date.clone() }
}

struct CommitInfo {
    commit_url: String,
    committed_date: String,
}

fn last_update(
    owner: &str,
    name: &str,
    path: &str,
    auth_token: Option<&str>,
) -> Result<CommitInfo> {
    let query =
        LastCommitThatAffectedPath::build_query(last_commit_that_affected_path::Variables {
            owner: owner.to_owned(),
            name: name.to_owned(),
            path: path.to_owned(),
        });

    let client = reqwest::blocking::Client::new();
    let mut headers = reqwest::header::HeaderMap::new();
    headers.insert("User-Agent", "https://github.com/rust-analyzer/rust-analyzer".parse()?);
    let mut request = client.post("https://api.github.com/graphql").headers(headers).json(&query);

    if auth_token.is_some() {
        request = request.bearer_auth(auth_token.unwrap());
    }

    let response = request.send()?;

    let response_body: Response<last_commit_that_affected_path::ResponseData> = response.json()?;
    Ok(deep_destructuring(response_body))
}

fn generate_descriptor(src_dir: PathBuf) -> Result<TokenStream> {
    let files = WalkDir::new(src_dir.join("language-features"))
        .into_iter()
        .chain(WalkDir::new(src_dir.join("library-features")))
        .filter_map(|e| e.ok())
        .filter(|entry| {
            // Get all `.md ` files
            entry.file_type().is_file()
                && entry.path().extension().unwrap_or_default() == "md"
        })
        .collect::<Vec<_>>();

    let definitions = files
        .iter()
        .map(|entry| {
            let path = entry.path();
            let feature_ident =
                format!("{}", path.file_stem().unwrap().to_str().unwrap().replace("-", "_"));
            let doc = format!("{}", std::fs::read_to_string(path).unwrap());

            quote! { LintCompletion { label: #feature_ident, description: #doc } }
        })
        .collect::<Vec<_>>();

    let ts = quote! {
        use crate::completion::LintCompletion;

        pub const UNSTABLE_FEATURE_DESCRIPTOR:  &[LintCompletion] = &[
            #(#definitions),*
        ];
    };
    Ok(ts)
}

fn add_anchor(text: impl std::fmt::Display, anchor: &str) -> String {
    let anchor_str = format!(
        r#"//The anchor is used to check if file is up to date and represent the time
    //of the last commit that affected path where located data for generation
    //ANCHOR: {}"#,
        anchor
    );
    format!("{}\n\n{}\n", anchor_str, text)
}

fn is_actual(path: &PathBuf, str_datetime: &str) -> bool {
    let re = Regex::new(r"//ANCHOR: (\S*)").unwrap();
    let opt_str = fs::read_to_string(path);
    if opt_str.is_err() {
        return false;
    }
    let text = opt_str.unwrap();
    let opt_datetime = re.captures(text.as_str());
    if opt_datetime.is_none() {
        return false;
    }
    let str_file_dt = opt_datetime.unwrap().get(1).unwrap().as_str();
    let file_dt = str_file_dt.parse::<chrono::DateTime<Utc>>().unwrap();
    let datetime = str_datetime.parse::<chrono::DateTime<Utc>>().unwrap();

    file_dt == datetime
}

fn download_tar(
    owner: &str,
    name: &str,
    auth_token: Option<&str>,
    destination: &PathBuf,
    fname: &str,
) -> Result<()> {
    let mut headers = reqwest::header::HeaderMap::new();
    headers.insert("User-Agent", "https://github.com/rust-analyzer/rust-analyzer".parse()?);
    let mut request = reqwest::blocking::Client::new()
        .get(format!("https://api.github.com/repos/{}/{}/tarball", owner, name).as_str())
        .headers(headers);

    if auth_token.is_some() {
        request = request.bearer_auth(auth_token.unwrap());
    }

    let response = request.send()?;
    let download_url = response.url();

    let mut response = reqwest::blocking::Client::new()
        .get(download_url.as_str())
        .send()?;

    let mut n = fname.to_string();
    n.push_str(".tar.gz");
    let fpath = destination.join(n);
    let mut file = File::create(fpath)?;
    response.copy_to(&mut file)?;

    Ok(())
}

pub fn generate_unstable_future_descriptor(mode: Mode) -> Result<()> {
    const auth_token: Option<&str> = None;

    let path = project_root().join(codegen::STORAGE);
    fs::create_dir_all(path.clone())?;

    let commit_info =
        last_update(codegen::REPO_OWNER, codegen::REPO_NAME, codegen::REPO_PATH, auth_token)?;

    if is_actual(
        &project_root().join(codegen::GENERATION_DESTINATION),
        commit_info.committed_date.as_str(),
    ) {
        return Ok(());
    }

    download_tar(codegen::REPO_OWNER, codegen::REPO_NAME, auth_token, &path, "repository")?;
    Command::new("tar")
        .args(&["-xvf", concat!("repository",".tar.gz"), "--wildcards", "*/src/doc/unstable-book/src", "--strip=1"])
        .current_dir(codegen::STORAGE)
        .output()?;

    let src_dir = path.join(codegen::REPO_PATH);
    let gen_holder = generate_descriptor(src_dir)?.to_string();
    let gen_holder = add_anchor(gen_holder, commit_info.committed_date.as_str());

    let destination = project_root().join(codegen::GENERATION_DESTINATION);
    let contents = crate::reformat(gen_holder)?;
    update(destination.as_path(), &contents, mode)?;

    Ok(())
}
<|endoftext|>


<commit_before>//! Library to read and write protocol buffers data.

#![deny(missing_docs)]
#![deny(intra_doc_link_resolution_failure)]
#![cfg_attr(rustc_nightly, feature(specialization))]

#[cfg(feature = "bytes")]
extern crate bytes;
#[cfg(feature = "with-serde")]
extern crate serde;
#[macro_use]
#[cfg(feature = "with-serde")]
extern crate serde_derive;
pub use crate::clear::Clear;
pub use crate::core::parse_from_bytes;
#[cfg(feature = "bytes")]
pub use crate::core::parse_from_carllerche_bytes;
pub use crate::core::parse_from_reader;
pub use crate::core::Message;
pub use crate::enums::ProtobufEnum;
pub use crate::enums::ProtobufEnumOrUnknown;
pub use crate::oneof::Oneof;
pub use crate::repeated::RepeatedField;
pub use crate::singular::SingularField;
pub use crate::singular::SingularPtrField;
pub use crate::stream::CodedInputStream;
pub use crate::stream::CodedOutputStream;
pub use crate::unknown::UnknownFields;
pub use crate::unknown::UnknownFieldsIter;
pub use crate::unknown::UnknownValue;
pub use crate::unknown::UnknownValueRef;
pub use crate::unknown::UnknownValues;
pub use crate::unknown::UnknownValuesIter;
pub mod wire_format;
#[cfg(feature = "bytes")]
pub use crate::chars::Chars;
pub use crate::error::ProtobufError;
pub use crate::error::ProtobufResult;

// generated
pub mod descriptor;
// TODO: move plugin to mod codegen
pub mod plugin;
pub mod rustproto;

mod clear;
mod core;
mod enums;
mod error;
pub mod ext;
pub mod json;
mod lazy;
mod oneof;
pub mod prelude;
pub mod reflect;
mod repeated;
pub mod rt;
mod singular;
mod stream;
pub mod text_format;
pub mod well_known_types;

// used by test
#[cfg(test)]
#[path = "../../protobuf-test-common/src/hex.rs"]
mod hex;

mod cached_size;
mod chars;
mod unknown;
mod varint;
mod zigzag;

mod misc;

mod buf_read_iter;
mod buf_read_or_reader;

/// This symbol is in generated `version.rs`, include here for IDE
#[cfg(never)]
pub const VERSION: &str = "";
/// This symbol is in generated `version.rs`, include here for IDE
#[cfg(never)]
#[doc(hidden)]
pub const VERSION_IDENT: &str = "";
include!(concat!(env!("OUT_DIR"), "/version.rs"));
<commit_msg>Slightly better lib readme<commit_after>//! Library to read and write protocol buffers data.
//!
//! Rust files from `.proto` files can be generated with
//! `protobuf-codegen`, `protobuf-codegen-pure` crates.
//! See readme on the [project github page](https://github.com/stepancheg/rust-protobuf).

#![deny(missing_docs)]
#![deny(intra_doc_link_resolution_failure)]
#![cfg_attr(rustc_nightly, feature(specialization))]

#[cfg(feature = "bytes")]
extern crate bytes;
#[cfg(feature = "with-serde")]
extern crate serde;
#[macro_use]
#[cfg(feature = "with-serde")]
extern crate serde_derive;
pub use crate::clear::Clear;
pub use crate::core::parse_from_bytes;
#[cfg(feature = "bytes")]
pub use crate::core::parse_from_carllerche_bytes;
pub use crate::core::parse_from_reader;
pub use crate::core::Message;
pub use crate::enums::ProtobufEnum;
pub use crate::enums::ProtobufEnumOrUnknown;
pub use crate::oneof::Oneof;
pub use crate::repeated::RepeatedField;
pub use crate::singular::SingularField;
pub use crate::singular::SingularPtrField;
pub use crate::stream::CodedInputStream;
pub use crate::stream::CodedOutputStream;
pub use crate::unknown::UnknownFields;
pub use crate::unknown::UnknownFieldsIter;
pub use crate::unknown::UnknownValue;
pub use crate::unknown::UnknownValueRef;
pub use crate::unknown::UnknownValues;
pub use crate::unknown::UnknownValuesIter;
pub mod wire_format;
#[cfg(feature = "bytes")]
pub use crate::chars::Chars;
pub use crate::error::ProtobufError;
pub use crate::error::ProtobufResult;

// generated
pub mod descriptor;
// TODO: move plugin to mod codegen
pub mod plugin;
pub mod rustproto;

mod clear;
mod core;
mod enums;
mod error;
pub mod ext;
pub mod json;
mod lazy;
mod oneof;
pub mod prelude;
pub mod reflect;
mod repeated;
pub mod rt;
mod singular;
mod stream;
pub mod text_format;
pub mod well_known_types;

// used by test
#[cfg(test)]
#[path = "../../protobuf-test-common/src/hex.rs"]
mod hex;

mod cached_size;
mod chars;
mod unknown;
mod varint;
mod zigzag;

mod misc;

mod buf_read_iter;
mod buf_read_or_reader;

/// This symbol is in generated `version.rs`, include here for IDE
#[cfg(never)]
pub const VERSION: &str = "";
/// This symbol is in generated `version.rs`, include here for IDE
#[cfg(never)]
#[doc(hidden)]
pub const VERSION_IDENT: &str = "";
include!(concat!(env!("OUT_DIR"), "/version.rs"));
<|endoftext|>


<commit_before>pub mod builtins;
pub mod utils;
pub mod read_line;
// pub mod exec;

use std::env;
use std::path::{Path, PathBuf};
use std::fmt;
use std::io;
use std::io::Write;
use std::collections::HashMap;
use std::collections::hash_map::Entry;

#[derive(Clone)]
pub struct State {
    cwd: PathBuf,
    variables: HashMap<String, String>,
    aliases: HashMap<String, String>,
    argv: Vec<String>,
    argc: usize,
    exit_status: i32,
}

impl fmt::Debug for State {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "\nState {{
\tcwd: {},
\tvariables: {:?},
\taliases: {:?},
\targv: {:?}
\targc: {}
\texit_status: {}
}}
",
               self.cwd.display(),
               self.variables,
               self.aliases,
               self.argv,
               self.argc,
               self.exit_status)
    }
}

impl State {
    pub fn new(cwd: String) -> State {
        State {
            cwd: utils::make_absolute(PathBuf::from(cwd)).unwrap(),
            variables: HashMap::new(),
            aliases: HashMap::new(),
            argv: Vec::new(),
            argc: 0,
            exit_status: 0,
        }
    }

    pub fn default() -> State {
        match env::current_dir() {
            Ok(cwd) => {
                State {
                    cwd: cwd,
                    variables: HashMap::new(),
                    aliases: HashMap::new(),
                    argv: Vec::new(),
                    argc: 0,
                    exit_status: 0,
                }
            }
            Err(e) => panic!(e),
        }
    }

    pub fn exec_paths(&self) -> Vec<PathBuf> {
        let path = if let Some(s) = self.variables.get("PATH") {
            s.clone()
        } else {
            "".to_string()
        };

        path.split(";")
            .map(|x| Path::new(x).to_path_buf())
            .collect()
    }

    pub fn env<'a>(&'a mut self, key: String, value: String) -> &'a mut State {
        self.variables.insert(key, value);
        self
    }

    pub fn alias<'a>(&'a mut self, alias: String, value: String) -> &'a mut State {
        self.aliases.insert(alias, value);
        self
    }
}

pub fn run(initial_state: State) {
    let mut s = initial_state.clone();
    let mut builtins = builtins::load();
    let i = read_line::Input::from(&s);

    println!("Welcome to rsh! {:?}", s);

    loop {

        let input = i.prompt(format!("{} -> ", s.cwd.display()));

        s.argv = parse_args(&input);
        s.argc = s.argv.len();

        print!("\n");
        println!("Input: {}\nState: {:?}", input, s);

        let first_arg = s.argv.get(0).unwrap().clone();
        if let Entry::Occupied(f) = builtins.entry(String::from(first_arg)) {
            let bn = f.get();
            s.exit_status = bn(&mut s);
            // prompt for input again.
            continue;
        }
    }
}

#[derive(PartialEq)]
enum BuildType {
    None,
    Single,
    Double,
}

struct ParseResult {
    result: Vec<String>,
    completed: bool,
    build_string: String,
    build_type: BuildType,
}

fn parse_args(args: &String) -> Vec<String> {
    if args.len() == 0 {
        return Vec::new();
    }

    let mut parse_result: ParseResult = ParseResult {
        result: Vec::new(),
        completed: false,
        build_string: String::from(""),
        build_type: BuildType::None,
    };

    let mut argstr = args.clone();

    parse_string_into_vec(&argstr, &mut parse_result);

    while !parse_result.completed {
        // prompt for the rest of input
        print!(">");
        io::stdout().flush();
        argstr = String::from("");
        io::stdin().read_line(&mut argstr).unwrap();

        parse_string_into_vec(&argstr, &mut parse_result);
    }

    let result: Vec<String> = parse_result.result
        .into_iter()
        .filter(|s| s.len() > 0)
        .collect();

    result
}

fn parse_string_into_vec(string: &String, parse_result: &mut ParseResult) {
    let &mut ParseResult { ref mut build_string,
                           ref mut build_type,
                           ref mut completed,
                           ref mut result } = parse_result;
    let mut iter = string.chars().peekable();

    while let Some(c) = iter.next() {
        match c {
            '\'' => {
                match *build_type {
                    BuildType::Single => {
                        *build_type = BuildType::None;
                        if iter.peek() == Some(&' ') {
                            result.push(build_string.clone());
                            *build_string = String::from("");
                        }
                    }

                    BuildType::None => {
                        *build_type = BuildType::Single;
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            '\"' => {
                match *build_type {
                    BuildType::Double => {
                        *build_type = BuildType::None;
                        if iter.peek() == Some(&' ') {
                            result.push(build_string.clone());
                            *build_string = String::from("");
                        }
                    }

                    BuildType::None => {
                        *build_type = BuildType::Double;
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            ' ' => {
                match *build_type {
                    BuildType::None => {
                        result.push(build_string.clone());
                        *build_string = String::from("");
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            '\n' => {
                match *build_type {
                    BuildType::None => {
                        match iter.peek() {
                            Some(_) => {
                                build_string.push(c);
                            }
                            None => {}
                        }
                    }
                    _ => {
                        build_string.push(c);
                    }
                }
            }

            _ => {
                build_string.push(c);
            }
        }
    }

    if *build_type == BuildType::None {
        *completed = true;
        if build_string.len() > 0 {
            result.push(build_string.clone());
        }
    }
}

#[test]
fn parse_args_test() {
    // parse empty string
    {
        let expected: Vec<String> = Vec::new();
        let result = parse_args(&String::from(""));
        assert_eq!(result, expected);
    }

    // parse single-word string
    {
        let expected: Vec<String> = vec!["echo".to_string()];
        let result = parse_args(&String::from("echo"));
        assert_eq!(result, expected);
    }

    // parse single-word string inside quotes
    {
        let expected = vec!["echo".to_string()];
        let result = parse_args(&String::from("\"echo\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with closed quotes section
    {
        let expected = vec!["echo".to_string(), "-n".into(), "Hello Dear World".into()];
        let result = parse_args(&String::from("echo -n \"Hello Dear World\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with multiple closed quotes sections
    {
        let expected = vec!["echo".to_string(), "Hello".into(), "Dear World".into()];
        let result = parse_args(&String::from("echo \"Hello\" \"Dear World\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with no spaces around single quotes
    {
        let expected = vec!["echo".to_string(), "helloworld".into()];
        let result = parse_args(&String::from("echo 'hello'world"));
        assert_eq!(result, expected);
    }

    // parse multi-word string with no spaces around double quotes
    {
        let expected = vec!["echo".to_string(), "helloworld".into()];
        let result = parse_args(&String::from("echo \"hello\"world"));
        assert_eq!(result, expected);
    }

    // allow double quotes inside single quotes
    {
        let expected = vec!["\"".to_string()];
        let result = parse_args(&String::from("'\"'"));
        assert_eq!(result, expected);
    }

    // allow single quotes inside double quotes
    {
        let expected = vec!["\'".to_string()];
        let result = parse_args(&String::from("\"\'\""));
        assert_eq!(result, expected);
    }

    // handle multiple quote sections in succession
    {
        let expected = vec!["echo".to_string(), "hello world how are you".into()];
        let result = parse_args(&String::from("echo \"hello world\"\' how are you\'"));
        assert_eq!(result, expected);
    }

    // preserve newlines
    {
        let expected = vec!["echo".to_string(), "hello\nworld".into()];
        let result = parse_args(&String::from("echo \"hello\nworld\""));
        assert_eq!(result, expected);
    }

    // remove final newline
    {
        let expected = vec!["echo".to_string()];
        let result = parse_args(&String::from("echo\n"));
        assert_eq!(result, expected);
    }
}
<commit_msg>fix some debug formatting<commit_after>pub mod builtins;
pub mod utils;
pub mod read_line;
// pub mod exec;

use std::env;
use std::path::{Path, PathBuf};
use std::fmt;
use std::io;
use std::io::Write;
use std::collections::HashMap;
use std::collections::hash_map::Entry;

#[derive(Clone)]
pub struct State {
    cwd: PathBuf,
    variables: HashMap<String, String>,
    aliases: HashMap<String, String>,
    argv: Vec<String>,
    argc: usize,
    exit_status: i32,
}

impl fmt::Debug for State {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "State {{
\tcwd: {},
\tvariables: {:?},
\taliases: {:?},
\targv: {:?}
\targc: {}
\texit_status: {}
}}
",
               self.cwd.display(),
               self.variables,
               self.aliases,
               self.argv,
               self.argc,
               self.exit_status)
    }
}

impl State {
    pub fn new(cwd: String) -> State {
        State {
            cwd: utils::make_absolute(PathBuf::from(cwd)).unwrap(),
            variables: HashMap::new(),
            aliases: HashMap::new(),
            argv: Vec::new(),
            argc: 0,
            exit_status: 0,
        }
    }

    pub fn default() -> State {
        match env::current_dir() {
            Ok(cwd) => {
                State {
                    cwd: cwd,
                    variables: HashMap::new(),
                    aliases: HashMap::new(),
                    argv: Vec::new(),
                    argc: 0,
                    exit_status: 0,
                }
            }
            Err(e) => panic!(e),
        }
    }

    pub fn exec_paths(&self) -> Vec<PathBuf> {
        let path = if let Some(s) = self.variables.get("PATH") {
            s.clone()
        } else {
            "".to_string()
        };

        path.split(";")
            .map(|x| Path::new(x).to_path_buf())
            .collect()
    }

    pub fn env<'a>(&'a mut self, key: String, value: String) -> &'a mut State {
        self.variables.insert(key, value);
        self
    }

    pub fn alias<'a>(&'a mut self, alias: String, value: String) -> &'a mut State {
        self.aliases.insert(alias, value);
        self
    }
}

pub fn run(initial_state: State) {
    let mut s = initial_state.clone();
    let mut builtins = builtins::load();
    let i = read_line::Input::from(&s);

    println!("Welcome to rsh! {:?}", s);

    loop {

        let input = i.prompt(format!("{} -> ", s.cwd.display()));

        s.argv = parse_args(&input);
        s.argc = s.argv.len();

        print!("\n");
        println!("Input: {} {:?}", input, s);

        let first_arg = s.argv.get(0).unwrap().clone();
        if let Entry::Occupied(f) = builtins.entry(String::from(first_arg)) {
            let bn = f.get();
            s.exit_status = bn(&mut s);
            // prompt for input again.
            continue;
        }
    }
}

#[derive(PartialEq)]
enum BuildType {
    None,
    Single,
    Double,
}

struct ParseResult {
    result: Vec<String>,
    completed: bool,
    build_string: String,
    build_type: BuildType,
}

fn parse_args(args: &String) -> Vec<String> {
    if args.len() == 0 {
        return Vec::new();
    }

    let mut parse_result: ParseResult = ParseResult {
        result: Vec::new(),
        completed: false,
        build_string: String::from(""),
        build_type: BuildType::None,
    };

    let mut argstr = args.clone();

    parse_string_into_vec(&argstr, &mut parse_result);

    while !parse_result.completed {
        // prompt for the rest of input
        print!(">");
        io::stdout().flush();
        argstr = String::from("");
        io::stdin().read_line(&mut argstr).unwrap();

        parse_string_into_vec(&argstr, &mut parse_result);
    }

    let result: Vec<String> = parse_result.result
        .into_iter()
        .filter(|s| s.len() > 0)
        .collect();

    result
}

fn parse_string_into_vec(string: &String, parse_result: &mut ParseResult) {
    let &mut ParseResult { ref mut build_string,
                           ref mut build_type,
                           ref mut completed,
                           ref mut result } = parse_result;
    let mut iter = string.chars().peekable();

    while let Some(c) = iter.next() {
        match c {
            '\'' => {
                match *build_type {
                    BuildType::Single => {
                        *build_type = BuildType::None;
                        if iter.peek() == Some(&' ') {
                            result.push(build_string.clone());
                            *build_string = String::from("");
                        }
                    }

                    BuildType::None => {
                        *build_type = BuildType::Single;
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            '\"' => {
                match *build_type {
                    BuildType::Double => {
                        *build_type = BuildType::None;
                        if iter.peek() == Some(&' ') {
                            result.push(build_string.clone());
                            *build_string = String::from("");
                        }
                    }

                    BuildType::None => {
                        *build_type = BuildType::Double;
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            ' ' => {
                match *build_type {
                    BuildType::None => {
                        result.push(build_string.clone());
                        *build_string = String::from("");
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            '\n' => {
                match *build_type {
                    BuildType::None => {
                        match iter.peek() {
                            Some(_) => {
                                build_string.push(c);
                            }
                            None => {}
                        }
                    }
                    _ => {
                        build_string.push(c);
                    }
                }
            }

            _ => {
                build_string.push(c);
            }
        }
    }

    if *build_type == BuildType::None {
        *completed = true;
        if build_string.len() > 0 {
            result.push(build_string.clone());
        }
    }
}

#[test]
fn parse_args_test() {
    // parse empty string
    {
        let expected: Vec<String> = Vec::new();
        let result = parse_args(&String::from(""));
        assert_eq!(result, expected);
    }

    // parse single-word string
    {
        let expected: Vec<String> = vec!["echo".to_string()];
        let result = parse_args(&String::from("echo"));
        assert_eq!(result, expected);
    }

    // parse single-word string inside quotes
    {
        let expected = vec!["echo".to_string()];
        let result = parse_args(&String::from("\"echo\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with closed quotes section
    {
        let expected = vec!["echo".to_string(), "-n".into(), "Hello Dear World".into()];
        let result = parse_args(&String::from("echo -n \"Hello Dear World\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with multiple closed quotes sections
    {
        let expected = vec!["echo".to_string(), "Hello".into(), "Dear World".into()];
        let result = parse_args(&String::from("echo \"Hello\" \"Dear World\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with no spaces around single quotes
    {
        let expected = vec!["echo".to_string(), "helloworld".into()];
        let result = parse_args(&String::from("echo 'hello'world"));
        assert_eq!(result, expected);
    }

    // parse multi-word string with no spaces around double quotes
    {
        let expected = vec!["echo".to_string(), "helloworld".into()];
        let result = parse_args(&String::from("echo \"hello\"world"));
        assert_eq!(result, expected);
    }

    // allow double quotes inside single quotes
    {
        let expected = vec!["\"".to_string()];
        let result = parse_args(&String::from("'\"'"));
        assert_eq!(result, expected);
    }

    // allow single quotes inside double quotes
    {
        let expected = vec!["\'".to_string()];
        let result = parse_args(&String::from("\"\'\""));
        assert_eq!(result, expected);
    }

    // handle multiple quote sections in succession
    {
        let expected = vec!["echo".to_string(), "hello world how are you".into()];
        let result = parse_args(&String::from("echo \"hello world\"\' how are you\'"));
        assert_eq!(result, expected);
    }

    // preserve newlines
    {
        let expected = vec!["echo".to_string(), "hello\nworld".into()];
        let result = parse_args(&String::from("echo \"hello\nworld\""));
        assert_eq!(result, expected);
    }

    // remove final newline
    {
        let expected = vec!["echo".to_string()];
        let result = parse_args(&String::from("echo\n"));
        assert_eq!(result, expected);
    }
}
<|endoftext|>


<commit_before>
use token::{Token, TokenKind};
use error::{NcclError, ErrorKind};

// ranked worst to least
enum Indent {
    Neither,
    Tabs,
    Spaces(u8),
}

pub struct Scanner {
    source: Vec<u8>,
    tokens: Vec<Token>,
    start: usize,
    current: usize,
    line: u64,
    indent: Indent,
}

impl Scanner {
    pub fn new(source: String) -> Self {
        Scanner {
            source: source.into_bytes(),
            tokens: Vec::new(),
            start: 0,
            current: 0,
            line: 1,
            indent: Indent::Neither,
        }
    }

    pub fn scan_tokens(&mut self) -> Result<Vec<Token>, NcclError> {
        while !self.is_at_end() {
            self.start = self.current;
            match self.scan_token() {
                Ok(_) => {},
                Err(e) => return Err(e)
            }
        }

        self.tokens.push(Token::new(TokenKind::EOF, "".into(), self.line));
        Ok(self.tokens.clone())
    }

    fn scan_token(&mut self) -> Result<(), NcclError> {
        let mut error = Ok(());
        match self.advance() {
            b':' => {
                self.add_token(TokenKind::Colon);
                while self.peek() == b' ' && !self.is_at_end() {
                    self.advance();
                }
                if self.is_at_end() {
                    error = Err(NcclError::new(ErrorKind::ParseError, "Expected schema name, found EOF", self.line));
                }
            },

            b'#' => {
                while self.peek() != b'\n' && !self.is_at_end() {
                    self.advance();
                }
            },

            b' ' => {
                match self.indent {
                    Indent::Neither => {
                        let mut spaces = 0;
                        while self.peek() == b' ' && !self.is_at_end() {
                            self.advance();
                            spaces += 1;
                        }
                        if self.is_at_end() {
                            error = Err(NcclError::new(ErrorKind::ParseError, "Expected value, found EOF", self.line));
                        }
                        self.indent = Indent::Spaces(spaces);
                        self.add_token(TokenKind::Indent);
                    }
                    Indent::Spaces(s) => {
                        let mut spaces = 0;
                        while spaces <= s && self.peek() == b' ' && !self.is_at_end() {
                            self.advance();
                            spaces += 1;
                        }
                        if self.is_at_end() {
                            error = Err(NcclError::new(ErrorKind::ParseError, "Expected value, found EOF", self.line));
                        }
                        if spaces != s {
                            error = Err(NcclError::new(ErrorKind::IndentationError, "Incorrect number of spaces", self.line));
                        }
                        self.add_token(TokenKind::Indent);
                    },
                    Indent::Tabs => { error = Err(NcclError::new(ErrorKind::IndentationError, "Expected tabs, found spaces", self.line)); }
                }
            },

            b'\t' => {
                match self.indent {
                    Indent::Neither => {
                        self.add_token(TokenKind::Indent);
                        self.indent = Indent::Tabs;
                    },
                    Indent::Tabs => {
                        self.add_token(TokenKind::Indent);
                    },
                    Indent::Spaces(_) => { error = Err(NcclError::new(ErrorKind::IndentationError, "Expected spaces, found tabs", self.line)); }
                }
            },

            b'\n' => {
                self.add_token(TokenKind::Newline);
                self.line += 1;
            },

            b'"' => if let Err(error) = self.string() {},

            _ => if let Err(error) = self.identifier() {},
        }

        error
    }

    fn identifier(&mut self) -> Result<(), NcclError> {
        while self.peek() != b'\n' && !self.is_at_end() {
            self.advance();
        }

        let value = String::from_utf8(self.source[self.start..self.current].to_vec()).unwrap();
        self.add_token_string(TokenKind::Name, value);

        Ok(())
    }

    fn string(&mut self) -> Result<(), NcclError> {
        while self.peek() != b'"' && !self.is_at_end() {
            if self.peek() == b'\n' {
                self.line += 1;
            }
            self.advance();
        }

        if self.is_at_end() {
            return Err(NcclError::new(ErrorKind::ParseError, "Unterminated string", self.line));
        }

        self.advance();

        let value = String::from_utf8(self.source[self.start + 1..self.current - 1].to_vec()).unwrap();
        self.add_token_string(TokenKind::Name, value);

        Ok(())
    }

    fn is_at_end(&mut self) -> bool {
        self.current >= self.source.len()
    }

    fn advance(&mut self) -> u8 {
        self.current += 1;
        self.source[self.current - 1]
    }

    fn add_token(&mut self, kind: TokenKind) {
        // assume valid UTF8
        let text = String::from_utf8(self.source[self.start..self.current].to_vec()).unwrap();
        self.tokens.push(Token::new(kind, text, self.line));
    }

    fn add_token_string(&mut self, kind: TokenKind, value: String) {
        self.tokens.push(Token::new(kind, value, self.line));
    }

    fn matches(&mut self, expected: char) -> bool {
        if self.is_at_end() {
            false
        } else if self.source[self.current] != expected as u8 {
            false
        } else {
            self.current += 1;
            true
        }
    }

    fn peek(&mut self) -> u8 {
        if self.current >= self.source.len() {
            b'\0'
        } else {
            self.source[self.current]
        }
    }
}

<commit_msg>actually report errors<commit_after>
use token::{Token, TokenKind};
use error::{NcclError, ErrorKind};

// ranked worst to least
enum Indent {
    Neither,
    Tabs,
    Spaces(u8),
}

pub struct Scanner {
    source: Vec<u8>,
    tokens: Vec<Token>,
    start: usize,
    current: usize,
    line: u64,
    indent: Indent,
}

impl Scanner {
    pub fn new(source: String) -> Self {
        Scanner {
            source: source.into_bytes(),
            tokens: Vec::new(),
            start: 0,
            current: 0,
            line: 1,
            indent: Indent::Neither,
        }
    }

    pub fn scan_tokens(&mut self) -> Result<Vec<Token>, NcclError> {
        while !self.is_at_end() {
            self.start = self.current;
            match self.scan_token() {
                Ok(_) => {},
                Err(e) => return Err(e)
            }
        }

        self.tokens.push(Token::new(TokenKind::EOF, "".into(), self.line));
        Ok(self.tokens.clone())
    }

    fn scan_token(&mut self) -> Result<(), NcclError> {
        let mut error = Ok(());
        match self.advance() {
            b':' => {
                self.add_token(TokenKind::Colon);
                while self.peek() == b' ' && !self.is_at_end() {
                    self.advance();
                }
                if self.is_at_end() {
                    error = Err(NcclError::new(ErrorKind::ParseError, "Expected schema name, found EOF", self.line));
                }
            },

            b'#' => {
                while self.peek() != b'\n' && !self.is_at_end() {
                    self.advance();
                }
            },

            b' ' => {
                match self.indent {
                    Indent::Neither => {
                        let mut spaces = 0;
                        while self.peek() == b' ' && !self.is_at_end() {
                            self.advance();
                            spaces += 1;
                        }
                        if self.is_at_end() {
                            error = Err(NcclError::new(ErrorKind::ParseError, "Expected value, found EOF", self.line));
                        }
                        self.indent = Indent::Spaces(spaces);
                        self.add_token(TokenKind::Indent);
                    }
                    Indent::Spaces(s) => {
                        let mut spaces = 0;
                        while spaces <= s && self.peek() == b' ' && !self.is_at_end() {
                            self.advance();
                            spaces += 1;
                        }
                        if self.is_at_end() {
                            error = Err(NcclError::new(ErrorKind::ParseError, "Expected value, found EOF", self.line));
                        }
                        if spaces != s {
                            error = Err(NcclError::new(ErrorKind::IndentationError, "Incorrect number of spaces", self.line));
                        }
                        self.add_token(TokenKind::Indent);
                    },
                    Indent::Tabs => { error = Err(NcclError::new(ErrorKind::IndentationError, "Expected tabs, found spaces", self.line)); }
                }
            },

            b'\t' => {
                match self.indent {
                    Indent::Neither => {
                        self.add_token(TokenKind::Indent);
                        self.indent = Indent::Tabs;
                    },
                    Indent::Tabs => {
                        self.add_token(TokenKind::Indent);
                    },
                    Indent::Spaces(_) => { error = Err(NcclError::new(ErrorKind::IndentationError, "Expected spaces, found tabs", self.line)); }
                }
            },

            b'\n' => {
                self.add_token(TokenKind::Newline);
                self.line += 1;
            },

            b'"' => if let Err(e) = self.string() { error = Err(e); },

            _ => if let Err(e) = self.identifier() { error = Err(e); },
        }

        error
    }

    fn identifier(&mut self) -> Result<(), NcclError> {
        while self.peek() != b'\n' && !self.is_at_end() {
            self.advance();
        }

        let value = String::from_utf8(self.source[self.start..self.current].to_vec()).unwrap();
        self.add_token_string(TokenKind::Name, value);

        Ok(())
    }

    fn string(&mut self) -> Result<(), NcclError> {
        while self.peek() != b'"' && !self.is_at_end() {
            if self.peek() == b'\n' {
                self.line += 1;
            }
            self.advance();
        }

        if self.is_at_end() {
            return Err(NcclError::new(ErrorKind::ParseError, "Unterminated string", self.line));
        }

        self.advance();

        let value = String::from_utf8(self.source[self.start + 1..self.current - 1].to_vec()).unwrap();
        self.add_token_string(TokenKind::Name, value);

        Ok(())
    }

    fn is_at_end(&mut self) -> bool {
        self.current >= self.source.len()
    }

    fn advance(&mut self) -> u8 {
        self.current += 1;
        self.source[self.current - 1]
    }

    fn add_token(&mut self, kind: TokenKind) {
        // assume valid UTF8
        let text = String::from_utf8(self.source[self.start..self.current].to_vec()).unwrap();
        self.tokens.push(Token::new(kind, text, self.line));
    }

    fn add_token_string(&mut self, kind: TokenKind, value: String) {
        self.tokens.push(Token::new(kind, value, self.line));
    }

    fn matches(&mut self, expected: char) -> bool {
        if self.is_at_end() {
            false
        } else if self.source[self.current] != expected as u8 {
            false
        } else {
            self.current += 1;
            true
        }
    }

    fn peek(&mut self) -> u8 {
        if self.current >= self.source.len() {
            b'\0'
        } else {
            self.source[self.current]
        }
    }
}

<|endoftext|>


<commit_before>//! Sub is a token that represents a
//! segment of the format string that is a substitution
//! it is created by Sub's implementation of the Tokenizer trait
//! Subs which have numeric field chars make use of the num_format
//! submodule
use std::slice::Iter;
use std::iter::Peekable;
use std::str::Chars;
use std::process::exit;
use cli;
use itertools::{put_back_n, PutBackN};
use super::token;
use super::unescaped_text::UnescapedText;
use super::num_format::format_field::{FieldType, FormatField};
use super::num_format::num_format;
// use std::collections::HashSet;

fn err_conv(sofar: &str) {
    cli::err_msg(&format!("%{}: invalid conversion specification", sofar));
    exit(cli::EXIT_ERR);
}

fn convert_asterisk_arg_int(asterisk_arg: &str) -> isize {
    // this is a costly way to parse the
    // args used for asterisk values into integers
    // from various bases. Actually doing it correctly
    // (going through the pipeline to intf, but returning
    // the integer instead of writing it to string and then
    // back) is on the refactoring TODO
    let field_type = FieldType::Intf;
    let field_char = 'i';
    let field_info = FormatField {
        min_width: Some(0),
        second_field: Some(0),
        orig: &asterisk_arg.to_string(),
        field_type: &field_type,
        field_char: &field_char,
    };
    num_format::num_format(&field_info, Some(&asterisk_arg.to_string()))
        .unwrap()
        .parse::<isize>()
        .unwrap()
}

pub enum CanAsterisk<T> {
    Fixed(T),
    Asterisk,
}

// Sub is a tokenizer which creates tokens
// for substitution segments of a format string
pub struct Sub {
    min_width: CanAsterisk<Option<isize>>,
    second_field: CanAsterisk<Option<u32>>,
    field_char: char,
    field_type: FieldType,
    orig: String,
}
impl Sub {
    pub fn new(
        min_width: CanAsterisk<Option<isize>>,
        second_field: CanAsterisk<Option<u32>>,
        field_char: char,
        orig: String,
    ) -> Sub {
        // for more dry printing, field characters are grouped
        // in initialization of token.
        let field_type = match field_char {
            's' | 'b' => FieldType::Strf,
            'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf,
            'f' | 'F' => FieldType::Floatf,
            'a' | 'A' => FieldType::CninetyNineHexFloatf,
            'e' | 'E' => FieldType::Scif,
            'g' | 'G' => FieldType::Decf,
            'c' => FieldType::Charf,
            _ => {
                // should be unreachable.
                println!("Invalid fieldtype");
                exit(cli::EXIT_ERR);
            }
        };
        Sub {
            min_width,
            second_field,
            field_char,
            field_type,
            orig,
        }
    }
}

struct SubParser {
    min_width_tmp: Option<String>,
    min_width_is_asterisk: bool,
    past_decimal: bool,
    second_field_tmp: Option<String>,
    second_field_is_asterisk: bool,
    specifiers_found: bool,
    field_char: Option<char>,
    text_so_far: String,
}

impl SubParser {
    fn new() -> SubParser {
        SubParser {
            min_width_tmp: None,
            min_width_is_asterisk: false,
            past_decimal: false,
            second_field_tmp: None,
            second_field_is_asterisk: false,
            specifiers_found: false,
            field_char: None,
            text_so_far: String::new(),
        }
    }
    fn from_it(
        it: &mut PutBackN<Chars>,
        args: &mut Peekable<Iter<String>>,
    ) -> Option<Box<dyn token::Token>> {
        let mut parser = SubParser::new();
        if parser.sub_vals_retrieved(it) {
            let t: Box<dyn token::Token> = SubParser::build_token(parser);
            t.print(args);
            Some(t)
        } else {
            None
        }
    }
    fn build_token(parser: SubParser) -> Box<dyn token::Token> {
        // not a self method so as to allow move of subparser vals.
        // return new Sub struct as token
        let t: Box<dyn token::Token> = Box::new(Sub::new(
            if parser.min_width_is_asterisk {
                CanAsterisk::Asterisk
            } else {
                CanAsterisk::Fixed(parser.min_width_tmp.map(|x| x.parse::<isize>().unwrap()))
            },
            if parser.second_field_is_asterisk {
                CanAsterisk::Asterisk
            } else {
                CanAsterisk::Fixed(parser.second_field_tmp.map(|x| x.parse::<u32>().unwrap()))
            },
            parser.field_char.unwrap(),
            parser.text_so_far,
        ));
        t
    }
    fn sub_vals_retrieved(&mut self, it: &mut PutBackN<Chars>) -> bool {
        if !SubParser::successfully_eat_prefix(it, &mut self.text_so_far) {
            return false;
        }
        // this fn in particular is much longer than it needs to be
        // .could get a lot
        // of code savings just by cleaning it up. shouldn't use a regex
        // though, as we want to mimic the original behavior of printing
        // the field as interpreted up until the error in the field.

        let mut legal_fields = vec![// 'a', 'A', //c99 hex float implementation not yet complete
                                    'b',
                                    'c',
                                    'd',
                                    'e',
                                    'E',
                                    'f',
                                    'F',
                                    'g',
                                    'G',
                                    'i',
                                    'o',
                                    's',
                                    'u',
                                    'x',
                                    'X'];
        let mut specifiers = vec!['h', 'j', 'l', 'L', 't', 'z'];
        legal_fields.sort();
        specifiers.sort();

        // divide substitution from %([0-9]+)?(.[0-9+])?([a-zA-Z])
        // into min_width, second_field, field_char
        for ch in it {
            self.text_so_far.push(ch);
            match ch as char {
                '-' | '*' | '0'..='9' => {
                    if !self.past_decimal {
                        if self.min_width_is_asterisk || self.specifiers_found {
                            err_conv(&self.text_so_far);
                        }
                        if self.min_width_tmp.is_none() {
                            self.min_width_tmp = Some(String::new());
                        }
                        match self.min_width_tmp.as_mut() {
                            Some(x) => {
                                if (ch == '-' || ch == '*') && !x.is_empty() {
                                    err_conv(&self.text_so_far);
                                }
                                if ch == '*' {
                                    self.min_width_is_asterisk = true;
                                }
                                x.push(ch);
                            }
                            None => {
                                panic!("should be unreachable");
                            }
                        }
                    } else {
                        // second field should never have a
                        // negative value
                        if self.second_field_is_asterisk || ch == '-' || self.specifiers_found {
                            err_conv(&self.text_so_far);
                        }
                        if self.second_field_tmp.is_none() {
                            self.second_field_tmp = Some(String::new());
                        }
                        match self.second_field_tmp.as_mut() {
                            Some(x) => {
                                if ch == '*' && !x.is_empty() {
                                    err_conv(&self.text_so_far);
                                }
                                if ch == '*' {
                                    self.second_field_is_asterisk = true;
                                }
                                x.push(ch);
                            }
                            None => {
                                panic!("should be unreachable");
                            }
                        }
                    }
                }
                '.' => {
                    if !self.past_decimal {
                        self.past_decimal = true;
                    } else {
                        err_conv(&self.text_so_far);
                    }
                }
                x if legal_fields.binary_search(&x).is_ok() => {
                    self.field_char = Some(ch);
                    self.text_so_far.push(ch);
                    break;
                }
                x if specifiers.binary_search(&x).is_ok() => {
                    if !self.past_decimal {
                        self.past_decimal = true;
                    }
                    if !self.specifiers_found {
                        self.specifiers_found = true;
                    }
                }
                _ => {
                    err_conv(&self.text_so_far);
                }
            }
        }
        if self.field_char.is_none() {
            err_conv(&self.text_so_far);
        }
        let field_char_retrieved = self.field_char.unwrap();
        if self.past_decimal && self.second_field_tmp.is_none() {
            self.second_field_tmp = Some(String::from("0"));
        }
        self.validate_field_params(field_char_retrieved);
        // if the dot is provided without a second field
        // printf interprets it as 0.
        match self.second_field_tmp.as_mut() {
            Some(x) => {
                if x.len() == 0 {
                    self.min_width_tmp = Some(String::from("0"));
                }
            }
            _ => {}
        }

        true
    }
    fn successfully_eat_prefix(it: &mut PutBackN<Chars>, text_so_far: &mut String) -> bool {
        // get next two chars,
        // if they're '%%' we're not tokenizing it
        // else put chars back
        let preface = it.next();
        let n_ch = it.next();
        if preface == Some('%') && n_ch != Some('%') {
            match n_ch {
                Some(x) => {
                    it.put_back(x);
                    true
                }
                None => {
                    text_so_far.push('%');
                    err_conv(&text_so_far);
                    false
                }
            }
        } else {
            n_ch.map(|x| it.put_back(x));
            preface.map(|x| it.put_back(x));
            false
        }
    }
    fn validate_field_params(&self, field_char: char) {
        // check for illegal combinations here when possible vs
        // on each application so we check less per application
        // to do: move these checks to Sub::new
        if (field_char == 's' && self.min_width_tmp == Some(String::from("0")))
            || (field_char == 'c'
                && (self.min_width_tmp == Some(String::from("0")) || self.past_decimal))
            || (field_char == 'b'
                && (self.min_width_tmp.is_some() || self.past_decimal
                    || self.second_field_tmp.is_some()))
        {
            err_conv(&self.text_so_far);
        }
    }
}

impl token::Tokenizer for Sub {
    fn from_it(
        it: &mut PutBackN<Chars>,
        args: &mut Peekable<Iter<String>>,
    ) -> Option<Box<dyn token::Token>> {
        SubParser::from_it(it, args)
    }
}
impl token::Token for Sub {
    fn print(&self, pf_args_it: &mut Peekable<Iter<String>>) {
        let field = FormatField {
            min_width: match self.min_width {
                CanAsterisk::Fixed(x) => x,
                CanAsterisk::Asterisk => {
                    match pf_args_it.next() {
                        // temporary, use intf.rs instead
                        Some(x) => Some(convert_asterisk_arg_int(x)),
                        None => Some(0),
                    }
                }
            },
            second_field: match self.second_field {
                CanAsterisk::Fixed(x) => x,
                CanAsterisk::Asterisk => {
                    match pf_args_it.next() {
                        // temporary, use intf.rs instead
                        Some(x) => {
                            let result = convert_asterisk_arg_int(x);
                            if result < 0 {
                                None
                            } else {
                                Some(result as u32)
                            }
                        }
                        None => Some(0),
                    }
                }
            },
            field_char: &self.field_char,
            field_type: &self.field_type,
            orig: &self.orig,
        };
        let pf_arg = pf_args_it.next();

        // minimum width is handled independently of actual
        // field char
        let pre_min_width_opt: Option<String> = match *field.field_type {
            // if %s just return arg
            // if %b use UnescapedText module's unescaping-fn
            // if %c return first char of arg
            FieldType::Strf | FieldType::Charf => {
                match pf_arg {
                    Some(arg_string) => {
                        match *field.field_char {
                            's' => Some(match field.second_field {
                                Some(max) => String::from(&arg_string[..max as usize]),
                                None => arg_string.clone(),
                            }),
                            'b' => {
                                let mut a_it = put_back_n(arg_string.chars());
                                UnescapedText::from_it_core(&mut a_it, true);
                                None
                            }
                            // for 'c': get iter of string vals,
                            // get opt<char> of first val
                            // and map it to opt<String>
                            'c' | _ => arg_string.chars().next().map(|x| x.to_string()),
                        }
                    }
                    None => None,
                }
            }
            _ => {
                // non string/char fields are delegated to num_format
                num_format::num_format(&field, pf_arg)
            }
        };
        match pre_min_width_opt {
            // if have a string, print it, ensuring minimum width is met.
            Some(pre_min_width) => {
                print!(
                    "{}",
                    match field.min_width {
                        Some(min_width) => {
                            let diff: isize =
                                min_width.abs() as isize - pre_min_width.len() as isize;
                            if diff > 0 {
                                let mut final_str = String::new();
                                // definitely more efficient ways
                                //  to do this.
                                let pad_before = min_width > 0;
                                if !pad_before {
                                    final_str.push_str(&pre_min_width);
                                }
                                for _ in 0..diff {
                                    final_str.push(' ');
                                }
                                if pad_before {
                                    final_str.push_str(&pre_min_width);
                                }
                                final_str
                            } else {
                                pre_min_width
                            }
                        }
                        None => pre_min_width,
                    }
                );
            }
            None => {}
        }
    }
}
<commit_msg>refactor/polish ~ fix `cargo clippy` complaints (map => if let)<commit_after>//! Sub is a token that represents a
//! segment of the format string that is a substitution
//! it is created by Sub's implementation of the Tokenizer trait
//! Subs which have numeric field chars make use of the num_format
//! submodule
use std::slice::Iter;
use std::iter::Peekable;
use std::str::Chars;
use std::process::exit;
use cli;
use itertools::{put_back_n, PutBackN};
use super::token;
use super::unescaped_text::UnescapedText;
use super::num_format::format_field::{FieldType, FormatField};
use super::num_format::num_format;
// use std::collections::HashSet;

fn err_conv(sofar: &str) {
    cli::err_msg(&format!("%{}: invalid conversion specification", sofar));
    exit(cli::EXIT_ERR);
}

fn convert_asterisk_arg_int(asterisk_arg: &str) -> isize {
    // this is a costly way to parse the
    // args used for asterisk values into integers
    // from various bases. Actually doing it correctly
    // (going through the pipeline to intf, but returning
    // the integer instead of writing it to string and then
    // back) is on the refactoring TODO
    let field_type = FieldType::Intf;
    let field_char = 'i';
    let field_info = FormatField {
        min_width: Some(0),
        second_field: Some(0),
        orig: &asterisk_arg.to_string(),
        field_type: &field_type,
        field_char: &field_char,
    };
    num_format::num_format(&field_info, Some(&asterisk_arg.to_string()))
        .unwrap()
        .parse::<isize>()
        .unwrap()
}

pub enum CanAsterisk<T> {
    Fixed(T),
    Asterisk,
}

// Sub is a tokenizer which creates tokens
// for substitution segments of a format string
pub struct Sub {
    min_width: CanAsterisk<Option<isize>>,
    second_field: CanAsterisk<Option<u32>>,
    field_char: char,
    field_type: FieldType,
    orig: String,
}
impl Sub {
    pub fn new(
        min_width: CanAsterisk<Option<isize>>,
        second_field: CanAsterisk<Option<u32>>,
        field_char: char,
        orig: String,
    ) -> Sub {
        // for more dry printing, field characters are grouped
        // in initialization of token.
        let field_type = match field_char {
            's' | 'b' => FieldType::Strf,
            'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf,
            'f' | 'F' => FieldType::Floatf,
            'a' | 'A' => FieldType::CninetyNineHexFloatf,
            'e' | 'E' => FieldType::Scif,
            'g' | 'G' => FieldType::Decf,
            'c' => FieldType::Charf,
            _ => {
                // should be unreachable.
                println!("Invalid fieldtype");
                exit(cli::EXIT_ERR);
            }
        };
        Sub {
            min_width,
            second_field,
            field_char,
            field_type,
            orig,
        }
    }
}

struct SubParser {
    min_width_tmp: Option<String>,
    min_width_is_asterisk: bool,
    past_decimal: bool,
    second_field_tmp: Option<String>,
    second_field_is_asterisk: bool,
    specifiers_found: bool,
    field_char: Option<char>,
    text_so_far: String,
}

impl SubParser {
    fn new() -> SubParser {
        SubParser {
            min_width_tmp: None,
            min_width_is_asterisk: false,
            past_decimal: false,
            second_field_tmp: None,
            second_field_is_asterisk: false,
            specifiers_found: false,
            field_char: None,
            text_so_far: String::new(),
        }
    }
    fn from_it(
        it: &mut PutBackN<Chars>,
        args: &mut Peekable<Iter<String>>,
    ) -> Option<Box<dyn token::Token>> {
        let mut parser = SubParser::new();
        if parser.sub_vals_retrieved(it) {
            let t: Box<dyn token::Token> = SubParser::build_token(parser);
            t.print(args);
            Some(t)
        } else {
            None
        }
    }
    fn build_token(parser: SubParser) -> Box<dyn token::Token> {
        // not a self method so as to allow move of subparser vals.
        // return new Sub struct as token
        let t: Box<dyn token::Token> = Box::new(Sub::new(
            if parser.min_width_is_asterisk {
                CanAsterisk::Asterisk
            } else {
                CanAsterisk::Fixed(parser.min_width_tmp.map(|x| x.parse::<isize>().unwrap()))
            },
            if parser.second_field_is_asterisk {
                CanAsterisk::Asterisk
            } else {
                CanAsterisk::Fixed(parser.second_field_tmp.map(|x| x.parse::<u32>().unwrap()))
            },
            parser.field_char.unwrap(),
            parser.text_so_far,
        ));
        t
    }
    fn sub_vals_retrieved(&mut self, it: &mut PutBackN<Chars>) -> bool {
        if !SubParser::successfully_eat_prefix(it, &mut self.text_so_far) {
            return false;
        }
        // this fn in particular is much longer than it needs to be
        // .could get a lot
        // of code savings just by cleaning it up. shouldn't use a regex
        // though, as we want to mimic the original behavior of printing
        // the field as interpreted up until the error in the field.

        let mut legal_fields = vec![// 'a', 'A', //c99 hex float implementation not yet complete
                                    'b',
                                    'c',
                                    'd',
                                    'e',
                                    'E',
                                    'f',
                                    'F',
                                    'g',
                                    'G',
                                    'i',
                                    'o',
                                    's',
                                    'u',
                                    'x',
                                    'X'];
        let mut specifiers = vec!['h', 'j', 'l', 'L', 't', 'z'];
        legal_fields.sort();
        specifiers.sort();

        // divide substitution from %([0-9]+)?(.[0-9+])?([a-zA-Z])
        // into min_width, second_field, field_char
        for ch in it {
            self.text_so_far.push(ch);
            match ch as char {
                '-' | '*' | '0'..='9' => {
                    if !self.past_decimal {
                        if self.min_width_is_asterisk || self.specifiers_found {
                            err_conv(&self.text_so_far);
                        }
                        if self.min_width_tmp.is_none() {
                            self.min_width_tmp = Some(String::new());
                        }
                        match self.min_width_tmp.as_mut() {
                            Some(x) => {
                                if (ch == '-' || ch == '*') && !x.is_empty() {
                                    err_conv(&self.text_so_far);
                                }
                                if ch == '*' {
                                    self.min_width_is_asterisk = true;
                                }
                                x.push(ch);
                            }
                            None => {
                                panic!("should be unreachable");
                            }
                        }
                    } else {
                        // second field should never have a
                        // negative value
                        if self.second_field_is_asterisk || ch == '-' || self.specifiers_found {
                            err_conv(&self.text_so_far);
                        }
                        if self.second_field_tmp.is_none() {
                            self.second_field_tmp = Some(String::new());
                        }
                        match self.second_field_tmp.as_mut() {
                            Some(x) => {
                                if ch == '*' && !x.is_empty() {
                                    err_conv(&self.text_so_far);
                                }
                                if ch == '*' {
                                    self.second_field_is_asterisk = true;
                                }
                                x.push(ch);
                            }
                            None => {
                                panic!("should be unreachable");
                            }
                        }
                    }
                }
                '.' => {
                    if !self.past_decimal {
                        self.past_decimal = true;
                    } else {
                        err_conv(&self.text_so_far);
                    }
                }
                x if legal_fields.binary_search(&x).is_ok() => {
                    self.field_char = Some(ch);
                    self.text_so_far.push(ch);
                    break;
                }
                x if specifiers.binary_search(&x).is_ok() => {
                    if !self.past_decimal {
                        self.past_decimal = true;
                    }
                    if !self.specifiers_found {
                        self.specifiers_found = true;
                    }
                }
                _ => {
                    err_conv(&self.text_so_far);
                }
            }
        }
        if self.field_char.is_none() {
            err_conv(&self.text_so_far);
        }
        let field_char_retrieved = self.field_char.unwrap();
        if self.past_decimal && self.second_field_tmp.is_none() {
            self.second_field_tmp = Some(String::from("0"));
        }
        self.validate_field_params(field_char_retrieved);
        // if the dot is provided without a second field
        // printf interprets it as 0.
        match self.second_field_tmp.as_mut() {
            Some(x) => {
                if x.len() == 0 {
                    self.min_width_tmp = Some(String::from("0"));
                }
            }
            _ => {}
        }

        true
    }
    fn successfully_eat_prefix(it: &mut PutBackN<Chars>, text_so_far: &mut String) -> bool {
        // get next two chars,
        // if they're '%%' we're not tokenizing it
        // else put chars back
        let preface = it.next();
        let n_ch = it.next();
        if preface == Some('%') && n_ch != Some('%') {
            match n_ch {
                Some(x) => {
                    it.put_back(x);
                    true
                }
                None => {
                    text_so_far.push('%');
                    err_conv(&text_so_far);
                    false
                }
            }
        } else {
            if let Some(x) = n_ch { it.put_back(x) };
            if let Some(x) = preface { it.put_back(x) };
            false
        }
    }
    fn validate_field_params(&self, field_char: char) {
        // check for illegal combinations here when possible vs
        // on each application so we check less per application
        // to do: move these checks to Sub::new
        if (field_char == 's' && self.min_width_tmp == Some(String::from("0")))
            || (field_char == 'c'
                && (self.min_width_tmp == Some(String::from("0")) || self.past_decimal))
            || (field_char == 'b'
                && (self.min_width_tmp.is_some() || self.past_decimal
                    || self.second_field_tmp.is_some()))
        {
            err_conv(&self.text_so_far);
        }
    }
}

impl token::Tokenizer for Sub {
    fn from_it(
        it: &mut PutBackN<Chars>,
        args: &mut Peekable<Iter<String>>,
    ) -> Option<Box<dyn token::Token>> {
        SubParser::from_it(it, args)
    }
}
impl token::Token for Sub {
    fn print(&self, pf_args_it: &mut Peekable<Iter<String>>) {
        let field = FormatField {
            min_width: match self.min_width {
                CanAsterisk::Fixed(x) => x,
                CanAsterisk::Asterisk => {
                    match pf_args_it.next() {
                        // temporary, use intf.rs instead
                        Some(x) => Some(convert_asterisk_arg_int(x)),
                        None => Some(0),
                    }
                }
            },
            second_field: match self.second_field {
                CanAsterisk::Fixed(x) => x,
                CanAsterisk::Asterisk => {
                    match pf_args_it.next() {
                        // temporary, use intf.rs instead
                        Some(x) => {
                            let result = convert_asterisk_arg_int(x);
                            if result < 0 {
                                None
                            } else {
                                Some(result as u32)
                            }
                        }
                        None => Some(0),
                    }
                }
            },
            field_char: &self.field_char,
            field_type: &self.field_type,
            orig: &self.orig,
        };
        let pf_arg = pf_args_it.next();

        // minimum width is handled independently of actual
        // field char
        let pre_min_width_opt: Option<String> = match *field.field_type {
            // if %s just return arg
            // if %b use UnescapedText module's unescaping-fn
            // if %c return first char of arg
            FieldType::Strf | FieldType::Charf => {
                match pf_arg {
                    Some(arg_string) => {
                        match *field.field_char {
                            's' => Some(match field.second_field {
                                Some(max) => String::from(&arg_string[..max as usize]),
                                None => arg_string.clone(),
                            }),
                            'b' => {
                                let mut a_it = put_back_n(arg_string.chars());
                                UnescapedText::from_it_core(&mut a_it, true);
                                None
                            }
                            // for 'c': get iter of string vals,
                            // get opt<char> of first val
                            // and map it to opt<String>
                            'c' | _ => arg_string.chars().next().map(|x| x.to_string()),
                        }
                    }
                    None => None,
                }
            }
            _ => {
                // non string/char fields are delegated to num_format
                num_format::num_format(&field, pf_arg)
            }
        };
        match pre_min_width_opt {
            // if have a string, print it, ensuring minimum width is met.
            Some(pre_min_width) => {
                print!(
                    "{}",
                    match field.min_width {
                        Some(min_width) => {
                            let diff: isize =
                                min_width.abs() as isize - pre_min_width.len() as isize;
                            if diff > 0 {
                                let mut final_str = String::new();
                                // definitely more efficient ways
                                //  to do this.
                                let pad_before = min_width > 0;
                                if !pad_before {
                                    final_str.push_str(&pre_min_width);
                                }
                                for _ in 0..diff {
                                    final_str.push(' ');
                                }
                                if pad_before {
                                    final_str.push_str(&pre_min_width);
                                }
                                final_str
                            } else {
                                pre_min_width
                            }
                        }
                        None => pre_min_width,
                    }
                );
            }
            None => {}
        }
    }
}
<|endoftext|>