<commit_before>//! This module deals with configuration data including the management of the list of secrets

#![allow(clippy::filter_map)]
use crate::image::CONF_LINE_IDENTIFIER__CONTROL;
use crate::image::CONF_LINE_IDENTIFIER__IMAGE;
use rand::Rng;
use thiserror::Error;
//use serde::Deserialize;
use serde_derive::Deserialize;

/// Tags comment lines in the configuration file.
pub const CONF_LINE_IDENTIFIER__COMMENT: char = '#';

/// Optionally tags secret strings in config-file. Can be omitted.
pub const CONF_LINE_IDENTIFIER__WORD: char = '-';

/// A tag to enclose parts of the secret to be visible from the start, e.g.
/// "guess_-me_" will be displayed in the game as "_ _ _ _ _ - m e"
pub const CONF_LINE_SECRET_MODIFIER__VISIBLE: char = '_';

/// A tag to insert a linebreak when the secret is displayed.
pub const CONF_LINE_SECRET_MODIFIER__LINEBREAK1: char = '\n';
pub const CONF_LINE_SECRET_MODIFIER__LINEBREAK2: char = '|';

// Custom error type used expressing potential syntax errors when parsing the configuration file.
#[derive(Error, Debug)]
pub enum ConfigParseError {
    #[error(
        "Syntax error in line {line_number:?}: `{line}`\n\n\
    The game modifier must be one of the following:\n\
        :traditional-rewarding\n\
        :success-rewarding\n\n\
        Edit config file and start again.\n"
    )]
    GameModifier { line_number: usize, line: String },
    #[error(
        "Syntax error in line {line_number:?}: `{line}`\n\n\
    The first character of every non-empty line has to be one of the following:\n\
        any letter or digit (secret string),\n\
        '#' (comment line),\n\
        '-' (secret string),\n\
        '|' (ASCII-Art image) or\n\
        ':' (game modifier).\n\n\
    Edit config file and start again.\n"
    )]
    LineIdentifier { line_number: usize, line: String },
    #[error["No image data found."]]
    NoImageData,
    #[error["A config file must have a least one secret string, which is\n\
    a non-empty line starting with a letter, digit, '_' or '-'."]]
    NoSecretString,
    #[error["Could not parse the proprietary format, because this is\n\
    meant to be in (erroneous) YAML format."]]
    NotInProprietaryFormat,
    #[error(
        "Syntax error: Please follow example:\n\
             \tsecrets: \n\
             \t- guess me\n\
             \t- \"guess me: with colon\"\n\
             \t- line| break\n\
             \t- _disclose _partly\n\
             \n\
             \t # The following is optional.\n\
             \ttraditional: false\n\
             \t # Optional ASCII-art lines start with 1 space ' '.\n\

             \timage: |1\n\
             \t   ::\n\
             \t C|__|\n\n\
             {0}"
    )]
    NotInYamlFormat(#[from] serde_yaml::Error),
    #[error["First line must be: `secrets:` (no spaces allowed before)."]]
    YamlSecretsLineMissing,
}

/// We need this because `serde_yaml::Error` does not implement `PartialEq`.
/// We compare only types.
impl PartialEq for ConfigParseError {
    fn eq(&self, other: &Self) -> bool {
        std::mem::discriminant(self) == std::mem::discriminant(other)
            && (self.to_string() == other.to_string())
    }
}

/// A dictionary holding all secret sentences from among whom one is chosen randomly at the
/// beginning of the game.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Dict {
    secrets: Vec<String>,
}

impl Dict {
    /// First try ot parse YAML, if it fails try the depreciated proprietary format and populate the dictionary
    /// with secrets.
    pub fn from(lines: &str) -> Result<Self, ConfigParseError> {
        // If both return an error, return the first one here.
        Self::from_yaml(&lines).or_else(|e| Self::from_proprietary(&lines).or(Err(e)))
    }

    /// Parse configuration file as toml data.
    pub fn from_yaml(lines: &str) -> Result<Self, ConfigParseError> {
        // Trim BOM
        let lines = lines.trim_start_matches('\u{feff}');

        for l in lines
            .lines()
            .filter(|s| !s.trim_start().starts_with('#'))
            .filter(|s| s.trim() != "")
        {
            if l.trim_end() == "secrets:" {
                break;
            } else {
                return Err(ConfigParseError::YamlSecretsLineMissing);
            }
        }

        let dict: Dict = serde_yaml::from_str(&lines)?;

        Ok(dict)
    }

    /// Parse the old configuration data format.
    fn from_proprietary(lines: &str) -> Result<Self, ConfigParseError> {
        if lines
            .lines()
            .any(|s| s.trim().starts_with("secrets:") || s.trim().starts_with("image:"))
        {
            return Err(ConfigParseError::NotInProprietaryFormat {});
        };

        let mut file_syntax_test2: Result<(), ConfigParseError> = Ok(());

        let wordlist =
          // remove Unicode BOM if present (\u{feff} has in UTF8 3 bytes).
          if lines.starts_with('\u{feff}') { &lines[3..] } else { &lines[..] }
            // interpret identifier line
            .lines()
            .enumerate()
            .filter(|&(_,l)|!( l.trim().is_empty() ||
                          l.starts_with(CONF_LINE_IDENTIFIER__COMMENT) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__CONTROL) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__IMAGE)
                        )
            )
            .map(|(n,l)| if l.starts_with(CONF_LINE_IDENTIFIER__WORD) {
                             l[1..].trim().to_string()
                        } else {
                             // Lines starting alphanumerically are secret strings also.
                             // We can safely unwrap here since all empty lines had been filtered.
                             let c = l.trim().chars().next().unwrap();
                             if c.is_alphanumeric() || c == CONF_LINE_SECRET_MODIFIER__VISIBLE {
                                l.trim().to_string()
                             } else {
                                 // we only save the first error
                                 if file_syntax_test2.is_ok() {
                                    file_syntax_test2 = Err(ConfigParseError::LineIdentifier {
                                        line_number: n+1, line: l.to_string() });
                                 };
                                // This will never be used but we have to return something
                                "".to_string()
                             }
                        }
            )
            .collect::<Vec<String>>();

        file_syntax_test2?;

        if wordlist.is_empty() {
            return Err(ConfigParseError::NoSecretString {});
        }

        Ok(Dict { secrets: wordlist })
    }

    /// Chooses randomly one secret from the dictionary and removes the secret from list
    pub fn get_random_secret(&mut self) -> Option<String> {
        match self.secrets.len() {
            0 => None,
            1 => Some(self.secrets.swap_remove(0)),
            _ => {
                let mut rng = rand::thread_rng();
                let i = rng.gen_range(0, &self.secrets.len() - 1);
                Some(self.secrets.swap_remove(i))
            }
        }
    }

    /// Is there exactly one secret left?
    pub fn is_empty(&self) -> bool {
        self.secrets.is_empty()
    }

    /// Add a secret to the list.
    pub fn add(&mut self, secret: String) {
        self.secrets.push(secret);
    }
}

// ***********************

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

    /// parse all 3 data types in configuration file format
    #[test]
    fn test_new_proprietary() {
        let config: &str = "
#  comment

guess me
hang_man_
_good l_uck
:traditional-rewarding
";
        let dict = Dict::from(&config).unwrap();

        let expected = Dict {
            secrets: vec![
                "guess me".to_string(),
                "hang_man_".to_string(),
                "_good l_uck".to_string(),
            ],
        };
        assert_eq!(dict, expected);

        let config: &str = "guess me";
        let dict = Dict::from(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
            // this is default
        });
        assert_eq!(dict, expected);

        // indent of comments is not allowed
        let config = "\n\n\n   # comment";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::LineIdentifier {
            line_number: 4,
            line: "   # comment".to_string(),
        });
        assert_eq!(dict, expected);

        // configuration must define at least one secret
        let config = "# nothing but comment";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::NoSecretString {});
        assert_eq!(dict, expected);

        let config = "one secret\n\n :traditional-rewarding";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::LineIdentifier {
            line_number: 3,
            line: " :traditional-rewarding".to_string(),
        });
        assert_eq!(dict, expected);
    }

    #[test]
    fn test_new_toml() {
        let config = "# comment\nsecrets:\n  - guess me\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "# comment\nsecrets:\n- guess me\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "# comment\nsecrets:\n- 222\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["222".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "sxxxecrets:";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::YamlSecretsLineMissing));

        let config = "  - guess me\nsecrets:\n";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::YamlSecretsLineMissing));

        let config = "# comment\nsecrets:\n   guess me\n";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::NotInYamlFormat(_)));
    }
}
<commit_msg>adapt error text to alert window size<commit_after>//! This module deals with configuration data including the management of the list of secrets

#![allow(clippy::filter_map)]
use crate::image::CONF_LINE_IDENTIFIER__CONTROL;
use crate::image::CONF_LINE_IDENTIFIER__IMAGE;
use rand::Rng;
use thiserror::Error;
//use serde::Deserialize;
use serde_derive::Deserialize;

/// Tags comment lines in the configuration file.
pub const CONF_LINE_IDENTIFIER__COMMENT: char = '#';

/// Optionally tags secret strings in config-file. Can be omitted.
pub const CONF_LINE_IDENTIFIER__WORD: char = '-';

/// A tag to enclose parts of the secret to be visible from the start, e.g.
/// "guess_-me_" will be displayed in the game as "_ _ _ _ _ - m e"
pub const CONF_LINE_SECRET_MODIFIER__VISIBLE: char = '_';

/// A tag to insert a linebreak when the secret is displayed.
pub const CONF_LINE_SECRET_MODIFIER__LINEBREAK1: char = '\n';
pub const CONF_LINE_SECRET_MODIFIER__LINEBREAK2: char = '|';

// Custom error type used expressing potential syntax errors when parsing the configuration file.
#[derive(Error, Debug)]
pub enum ConfigParseError {
    #[error(
        "Syntax error in line {line_number:?}: `{line}`\n\n\
    The game modifier must be one of the following:\n\
        :traditional-rewarding\n\
        :success-rewarding\n\n\
        Edit config file and start again.\n"
    )]
    GameModifier { line_number: usize, line: String },
    #[error(
        "Syntax error in line {line_number:?}: `{line}`\n\n\
    The first character of every non-empty line has to be one of the following:\n\
        any letter or digit (secret string),\n\
        '#' (comment line),\n\
        '-' (secret string),\n\
        '|' (ASCII-Art image) or\n\
        ':' (game modifier).\n\n\
    Edit config file and start again.\n"
    )]
    LineIdentifier { line_number: usize, line: String },
    #[error["No image data found."]]
    NoImageData,
    #[error["A config file must have a least one secret string, which is\n\
    a non-empty line starting with a letter, digit, '_' or '-'."]]
    NoSecretString,
    #[error["Could not parse the proprietary format, because this is\n\
    meant to be in (erroneous) YAML format."]]
    NotInProprietaryFormat,
    #[error(
        "Syntax error: Please follow the example below.\n\
         (The custom image is optional, it's lines start with a space.):\n\
             \t------------------------------\n\
             \tsecrets: \n\
             \t- guess me\n\
             \t- \"guess me: with colon\"\n\
             \t- line| break\n\
             \t- _disclose _partly\n\
             \n\
             \timage: |1\n\
             \t   :\n\
             \t  |_|>\n\
             \t------------------------------\n\
             {0}"
    )]
    NotInYamlFormat(#[from] serde_yaml::Error),
    #[error["First line must be: `secrets:` (no spaces allowed before)."]]
    YamlSecretsLineMissing,
}

/// We need this because `serde_yaml::Error` does not implement `PartialEq`.
/// We compare only types.
impl PartialEq for ConfigParseError {
    fn eq(&self, other: &Self) -> bool {
        std::mem::discriminant(self) == std::mem::discriminant(other)
            && (self.to_string() == other.to_string())
    }
}

/// A dictionary holding all secret sentences from among whom one is chosen randomly at the
/// beginning of the game.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Dict {
    secrets: Vec<String>,
}

impl Dict {
    /// First try ot parse YAML, if it fails try the depreciated proprietary format and populate the dictionary
    /// with secrets.
    pub fn from(lines: &str) -> Result<Self, ConfigParseError> {
        // If both return an error, return the first one here.
        Self::from_yaml(&lines).or_else(|e| Self::from_proprietary(&lines).or(Err(e)))
    }

    /// Parse configuration file as toml data.
    pub fn from_yaml(lines: &str) -> Result<Self, ConfigParseError> {
        // Trim BOM
        let lines = lines.trim_start_matches('\u{feff}');

        for l in lines
            .lines()
            .filter(|s| !s.trim_start().starts_with('#'))
            .filter(|s| s.trim() != "")
        {
            if l.trim_end() == "secrets:" {
                break;
            } else {
                return Err(ConfigParseError::YamlSecretsLineMissing);
            }
        }

        let dict: Dict = serde_yaml::from_str(&lines)?;

        Ok(dict)
    }

    /// Parse the old configuration data format.
    fn from_proprietary(lines: &str) -> Result<Self, ConfigParseError> {
        if lines
            .lines()
            .any(|s| s.trim().starts_with("secrets:") || s.trim().starts_with("image:"))
        {
            return Err(ConfigParseError::NotInProprietaryFormat {});
        };

        let mut file_syntax_test2: Result<(), ConfigParseError> = Ok(());

        let wordlist =
          // remove Unicode BOM if present (\u{feff} has in UTF8 3 bytes).
          if lines.starts_with('\u{feff}') { &lines[3..] } else { &lines[..] }
            // interpret identifier line
            .lines()
            .enumerate()
            .filter(|&(_,l)|!( l.trim().is_empty() ||
                          l.starts_with(CONF_LINE_IDENTIFIER__COMMENT) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__CONTROL) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__IMAGE)
                        )
            )
            .map(|(n,l)| if l.starts_with(CONF_LINE_IDENTIFIER__WORD) {
                             l[1..].trim().to_string()
                        } else {
                             // Lines starting alphanumerically are secret strings also.
                             // We can safely unwrap here since all empty lines had been filtered.
                             let c = l.trim().chars().next().unwrap();
                             if c.is_alphanumeric() || c == CONF_LINE_SECRET_MODIFIER__VISIBLE {
                                l.trim().to_string()
                             } else {
                                 // we only save the first error
                                 if file_syntax_test2.is_ok() {
                                    file_syntax_test2 = Err(ConfigParseError::LineIdentifier {
                                        line_number: n+1, line: l.to_string() });
                                 };
                                // This will never be used but we have to return something
                                "".to_string()
                             }
                        }
            )
            .collect::<Vec<String>>();

        file_syntax_test2?;

        if wordlist.is_empty() {
            return Err(ConfigParseError::NoSecretString {});
        }

        Ok(Dict { secrets: wordlist })
    }

    /// Chooses randomly one secret from the dictionary and removes the secret from list
    pub fn get_random_secret(&mut self) -> Option<String> {
        match self.secrets.len() {
            0 => None,
            1 => Some(self.secrets.swap_remove(0)),
            _ => {
                let mut rng = rand::thread_rng();
                let i = rng.gen_range(0, &self.secrets.len() - 1);
                Some(self.secrets.swap_remove(i))
            }
        }
    }

    /// Is there exactly one secret left?
    pub fn is_empty(&self) -> bool {
        self.secrets.is_empty()
    }

    /// Add a secret to the list.
    pub fn add(&mut self, secret: String) {
        self.secrets.push(secret);
    }
}

// ***********************

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

    /// parse all 3 data types in configuration file format
    #[test]
    fn test_new_proprietary() {
        let config: &str = "
#  comment

guess me
hang_man_
_good l_uck
:traditional-rewarding
";
        let dict = Dict::from(&config).unwrap();

        let expected = Dict {
            secrets: vec![
                "guess me".to_string(),
                "hang_man_".to_string(),
                "_good l_uck".to_string(),
            ],
        };
        assert_eq!(dict, expected);

        let config: &str = "guess me";
        let dict = Dict::from(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
            // this is default
        });
        assert_eq!(dict, expected);

        // indent of comments is not allowed
        let config = "\n\n\n   # comment";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::LineIdentifier {
            line_number: 4,
            line: "   # comment".to_string(),
        });
        assert_eq!(dict, expected);

        // configuration must define at least one secret
        let config = "# nothing but comment";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::NoSecretString {});
        assert_eq!(dict, expected);

        let config = "one secret\n\n :traditional-rewarding";
        let dict = Dict::from_proprietary(&config);
        let expected = Err(ConfigParseError::LineIdentifier {
            line_number: 3,
            line: " :traditional-rewarding".to_string(),
        });
        assert_eq!(dict, expected);
    }

    #[test]
    fn test_new_toml() {
        let config = "# comment\nsecrets:\n  - guess me\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "# comment\nsecrets:\n- guess me\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["guess me".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "# comment\nsecrets:\n- 222\n";
        let dict = Dict::from_yaml(&config);
        let expected = Ok(Dict {
            secrets: vec!["222".to_string()],
        });
        assert_eq!(dict, expected);

        let config = "sxxxecrets:";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::YamlSecretsLineMissing));

        let config = "  - guess me\nsecrets:\n";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::YamlSecretsLineMissing));

        let config = "# comment\nsecrets:\n   guess me\n";
        let dict = Dict::from_yaml(&config).unwrap_err();
        assert!(matches!(dict, ConfigParseError::NotInYamlFormat(_)));
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2018 The predicates-rs Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use regex;

use Predicate;

/// An error that occurred during parsing or compiling a regular expression.
pub type RegexError = regex::Error;

/// Predicate that uses regex matching
///
/// This is created by the `predicate::str::is_match`.
#[derive(Clone, Debug)]
pub struct RegexPredicate {
    re: regex::Regex,
}

impl Predicate<str> for RegexPredicate {
    fn eval(&self, variable: &str) -> bool {
        self.re.is_match(variable)
    }
}

/// Creates a new `Predicate` that uses a regular expression to match the string.
///
/// # Examples
///
/// ```
/// use predicates::prelude::*;
///
/// let predicate_fn = predicate::str::is_match("^Hel.o.*$").unwrap();
/// assert_eq!(true, predicate_fn.eval("Hello World"));
/// assert_eq!(false, predicate_fn.eval("Food World"));
/// ```
pub fn is_match<S>(pattern: S) -> Result<RegexPredicate, RegexError>
where
    S: AsRef<str>,
{
    regex::Regex::new(pattern.as_ref()).map(|re| RegexPredicate { re })
}
<commit_msg>feat(str): Add regex repetition count<commit_after>// Copyright (c) 2018 The predicates-rs Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use regex;

use Predicate;

/// An error that occurred during parsing or compiling a regular expression.
pub type RegexError = regex::Error;

/// Predicate that uses regex matching
///
/// This is created by the `predicate::str::is_match`.
#[derive(Clone, Debug)]
pub struct RegexPredicate {
    re: regex::Regex,
}

impl RegexPredicate {
    /// Require a specific count of matches.
    ///
    /// # Examples
    ///
    /// ```
    /// use predicates::prelude::*;
    ///
    /// let predicate_fn = predicate::str::is_match("T[a-z]*").unwrap().count(3);
    /// assert_eq!(true, predicate_fn.eval("One Two Three Two One"));
    /// assert_eq!(false, predicate_fn.eval("One Two Three"));
    /// ```
    pub fn count(self, count: usize) -> RegexMatchesPredicate {
        RegexMatchesPredicate { re: self.re, count }
    }
}

impl Predicate<str> for RegexPredicate {
    fn eval(&self, variable: &str) -> bool {
        self.re.is_match(variable)
    }
}

/// Predicate that checks for repeated patterns.
///
/// This is created by `predicates::str::is_match(...).count`.
#[derive(Clone, Debug)]
pub struct RegexMatchesPredicate {
    re: regex::Regex,
    count: usize,
}

impl Predicate<str> for RegexMatchesPredicate {
    fn eval(&self, variable: &str) -> bool {
        self.re.find_iter(variable).count() == self.count
    }
}
/// Creates a new `Predicate` that uses a regular expression to match the string.
///
/// # Examples
///
/// ```
/// use predicates::prelude::*;
///
/// let predicate_fn = predicate::str::is_match("^Hel.o.*$").unwrap();
/// assert_eq!(true, predicate_fn.eval("Hello World"));
/// assert_eq!(false, predicate_fn.eval("Food World"));
/// ```
pub fn is_match<S>(pattern: S) -> Result<RegexPredicate, RegexError>
where
    S: AsRef<str>,
{
    regex::Regex::new(pattern.as_ref()).map(|re| RegexPredicate { re })
}
<|endoftext|>


<commit_before>//! # Command Tokenization
//!
//! The command parser needs to be able to tokenize commands
//! into their constituent words and whitespace.
//!
//! The `tokenizer` breaks source text into a vector of tokens
//! which can be either whitespace or a word. The tokenizer
//! handles using double quotes to provide a single token
//! which may include whitespace.
//!
//! Tokens also track their source location within the source
//! text. This allows the parser using the tokenizer to provide
//! better error highlighting and other functionality.
//!
//! # Examples
//!
//! ```
//! use commands::tokenizer::tokenize;
//!
//! if let Ok(tokens) = tokenize("word") {
//!     assert_eq!(tokens.len(), 1);
//! }
//!
//! if let Ok(tokens) = tokenize("show interface") {
//!     assert_eq!(tokens.len(), 3);
//! }
//!
//! if let Ok(tokens) = tokenize("echo -n \"a b c\"") {
//!     assert_eq!(tokens.len(), 5);
//! }
//!
//! if let Ok(tokens) = tokenize("ls My\\ Documents") {
//!     assert_eq!(tokens.len(), 3);
//!     assert_eq!(tokens[2].text, "My\\ Documents");
//! }
//! ```

/// A position within a string.
///
/// The `SourceOffset` tracks 2 different ways of locating the
/// position:
///
/// * The index of the character within the string.
/// * The column and line number of the character.
///
/// The `SourceOffset` is typically used as a pair of offsets
/// indicating the start and end of a range of text as used
/// by the `SourceLocation`.
#[derive(Debug,PartialEq)]
pub struct SourceOffset {
    /// The index of this character within the string.
    pub char: usize,
    /// The line number on which this character may be found.
    pub line: usize,
    /// The column on which this character may be found.
    pub column: usize,
}

impl SourceOffset {
    /// Construct a `SourceOffset`.
    pub fn new(char: usize, line: usize, column: usize) -> SourceOffset {
        SourceOffset {
            char: char,
            line: line,
            column: column,
        }
    }
}

/// A range within a string.
#[derive(Debug,PartialEq)]
pub struct SourceLocation {
    /// The start of the range.
    pub start: SourceOffset,
    /// The end of the range.
    pub end: SourceOffset,
}

impl SourceLocation {
    /// Construct a `SourceLocation`.
    pub fn new(start: SourceOffset, end: SourceOffset) -> SourceLocation {
        SourceLocation {
            start: start,
            end: end,
        }
    }
}

/// Errors
pub enum TokenizerError {
    /// Character not allowed here
    CharacterNotAllowedHere(usize),

    /// Special not yet implemented
    SpecialNotYetImplemented(usize),

    /// Escaping backslash at end of input
    EscapingBackslashAtEndOfInput,

    /// Unclosed double quote at end of input
    UnclosedDoubleQuoteAtEndOfInput,

    /// Escaped double quote at end of input
    EscapedDoubleQuoteAtEndOfInput,
}

/// The role that a token plays: `Whitespace` or `Word`.
#[derive(Clone,Copy,Debug,PartialEq)]
pub enum TokenType {
    /// Internal usage only.
    Invalid,
    /// The token represents whitespace and not a word.
    Whitespace,
    /// The token represents a word within the string. This
    /// takes double quoted strings into account.
    Word,
}

/// A token from a body of text.
///
/// The lifetime parameter `'t` refers to the lifetime
/// of the body of text that was tokenized, creating this token.
#[derive(Debug,PartialEq)]
pub struct Token<'t> {
    /// The text of the token.
    pub text: &'t str,
    /// The type of the token (`Whitespace` or `Word`).
    pub token_type: TokenType,
    /// The location of the token in the source body of text.
    pub location: SourceLocation,
}

impl<'t> Token<'t> {
    /// Construct a `Token`. The lifetime parameter `'t` refers to the lifetime of the
    /// text being tokenized.
    pub fn new(text: &'t str, token_type: TokenType, location: SourceLocation) -> Token {
        Token {
            text: text,
            token_type: token_type,
            location: location,
        }
    }
}

#[derive(Clone,Copy,PartialEq)]
enum State {
    Initial,
    Special,
    Whitespace,
    Doublequote,
    DoublequoteBackslash,
    Word,
    WordBackslash,
}

struct Tokenizer<'t> {
    text: &'t str,
    state: State,
    token_type: TokenType,
    token_start: usize,
    token_end: usize,
    tokens: Vec<Token<'t>>,
}

impl<'t> Tokenizer<'t> {
    fn new(text: &'t str) -> Tokenizer {
        Tokenizer {
            text: text,
            state: State::Initial,
            token_type: TokenType::Invalid,
            token_start: 0,
            token_end: 0,
            tokens: vec![],
        }
    }

    fn reset(&mut self) {
        self.state = State::Initial;
        self.token_type = TokenType::Invalid;
        self.token_start = 0;
        self.token_end = 0;
    }

    fn reduce(&mut self) {
        let token_text = &self.text[self.token_start..self.token_end + 1];
        let loc = SourceLocation::new(SourceOffset::new(self.token_start, 0, self.token_start),
                                      SourceOffset::new(self.token_end, 0, self.token_end));
        self.tokens.push(Token::new(token_text, self.token_type, loc));
        self.reset();
    }

    fn shift(&mut self, offset: usize, next_state: State) {
        self.recognize(offset, next_state);
        self.token_end = offset;
        self.state = next_state;
    }

    fn recognize(&mut self, offset: usize, next_state: State) {
        if self.token_type == TokenType::Invalid {
            self.token_type = if next_state == State::Whitespace {
                TokenType::Whitespace
            } else {
                TokenType::Word
            };
            self.token_start = offset;
        }
    }

    fn special(&mut self, offset: usize) {
        self.shift(offset, State::Special);
        self.reduce();
    }

    fn initial(&mut self, offset: usize, c: char) {
        if c.is_whitespace() {
            self.shift(offset, State::Whitespace);
        } else if c == ';' {
            self.special(offset);
        } else if c == '?' {
            self.special(offset);
        } else if c == '|' {
            self.special(offset);
        } else if c == '"' {
            self.shift(offset, State::Doublequote);
        } else if c == '\\' {
            self.recognize(offset, State::Word);
            self.shift(offset, State::WordBackslash);
        } else {
            self.shift(offset, State::Word);
        }
    }

    fn tokenize(&mut self) -> Result<(), TokenizerError> {
        for (offset, c) in self.text.chars().enumerate() {
            match self.state {
                State::Initial => self.initial(offset, c),
                State::Whitespace => {
                    if c.is_whitespace() {
                        self.shift(offset, State::Whitespace);
                    } else {
                        self.reduce();
                        self.initial(offset, c);
                    };
                }
                State::Word => {
                    if c.is_whitespace() {
                        self.reduce();
                        self.shift(offset, State::Whitespace);
                    } else if c == ';' {
                        self.reduce();
                        self.special(offset);
                    } else if c == '|' {
                        self.reduce();
                        self.special(offset);
                    } else if c == '"' {
                        self.reduce();
                        self.shift(offset, State::Doublequote);
                    } else if c == '\\' {
                        self.shift(offset, State::WordBackslash);
                    } else {
                        self.shift(offset, State::Word);
                    }
                }
                State::WordBackslash => {
                    // XXX: This should be if !c.is_control() perhaps?
                    if c.is_alphanumeric() || c.is_whitespace() {
                        self.shift(offset, State::Word);
                    } else {
                        return Err(TokenizerError::CharacterNotAllowedHere(offset));
                    };
                }
                State::Doublequote => {
                    if c == '"' {
                        self.shift(offset, State::Doublequote);
                        self.reduce();
                    } else if c == '\\' {
                        self.shift(offset, State::DoublequoteBackslash);
                    } else {
                        self.shift(offset, State::Doublequote);
                    };
                }
                State::DoublequoteBackslash => {
                    if !c.is_whitespace() {
                        self.shift(offset, State::Doublequote);
                    } else {
                        return Err(TokenizerError::CharacterNotAllowedHere(offset));
                    };
                }
                State::Special => {
                    return Err(TokenizerError::SpecialNotYetImplemented(offset));
                }
            }
        }

        // Now for the end of the text...
        match self.state {
            State::Initial => {}
            State::Word => self.reduce(),
            State::Whitespace => self.reduce(),
            State::WordBackslash => return Err(TokenizerError::EscapingBackslashAtEndOfInput),
            State::Doublequote => return Err(TokenizerError::UnclosedDoubleQuoteAtEndOfInput),
            State::DoublequoteBackslash => return Err(TokenizerError::EscapedDoubleQuoteAtEndOfInput),
            State::Special => return Err(TokenizerError::SpecialNotYetImplemented(self.text.len() - 1)),
        }

        Ok(())
    }
}

/// Tokenize a body of text.
pub fn tokenize(text: &str) -> Result<Vec<Token>, TokenizerError> {
    let mut tokenizer = Tokenizer::new(text);
    match tokenizer.tokenize() {
        Ok(_) => Ok(tokenizer.tokens),
        Err(error) => Err(error),
    }
}

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

    fn mk_token(text: &str, token_type: TokenType, start: usize, end: usize) -> Token {
        Token::new(text,
                   token_type,
                   SourceLocation::new(SourceOffset::new(start, 0, start),
                                       SourceOffset::new(end, 0, end)))
    }

    #[test]
    fn empty_test() {
        match tokenize("") {
            Ok(ts) => assert_eq!(ts.len(), 0),
            _ => {},
        };
    }

    #[test]
    fn single_word() {
        match tokenize("a") {
            Ok(ts) => {
                assert_eq!(ts.len(), 1);
                assert_eq!(ts[0], mk_token("a", TokenType::Word, 0, 0));
            },
            _ => {},
        };
    }

    #[test]
    fn multiple_words() {
        match tokenize(" aa bb  ccc ") {
            Ok(ts) => {
                assert_eq!(ts.len(), 7);
                assert_eq!(ts[0], mk_token(" ", TokenType::Whitespace, 0, 0));
                assert_eq!(ts[1], mk_token("aa", TokenType::Word, 1, 2));
                assert_eq!(ts[2], mk_token(" ", TokenType::Whitespace, 3, 3));
                assert_eq!(ts[3], mk_token("bb", TokenType::Word, 4, 5));
                assert_eq!(ts[4], mk_token("  ", TokenType::Whitespace, 6, 7));
                assert_eq!(ts[5], mk_token("ccc", TokenType::Word, 8, 10));
                assert_eq!(ts[6], mk_token(" ", TokenType::Whitespace, 11, 11));
            },
            _ => {},
        };
    }

    #[test]
    fn quoted_text() {
        match tokenize("a \"b c\"") {
            Ok(ts) => {
                assert_eq!(ts.len(), 3);
                assert_eq!(ts[0], mk_token("a", TokenType::Word, 0, 0));
                assert_eq!(ts[1], mk_token(" ", TokenType::Whitespace, 1, 1));
                assert_eq!(ts[2], mk_token("\"b c\"", TokenType::Word, 2, 6));
            },
            _ => {},
        };
    }

    #[test]
    fn escaped_whitespace_in_word() {
        match tokenize("a\\ b") {
            Ok(ts) => {
                assert_eq!(ts.len(), 1);
                assert_eq!(ts[0], mk_token("a\\ b", TokenType::Word, 0, 3));
            },
            _ => {},
        };
    }
}
<commit_msg>rustfmt.<commit_after>//! # Command Tokenization
//!
//! The command parser needs to be able to tokenize commands
//! into their constituent words and whitespace.
//!
//! The `tokenizer` breaks source text into a vector of tokens
//! which can be either whitespace or a word. The tokenizer
//! handles using double quotes to provide a single token
//! which may include whitespace.
//!
//! Tokens also track their source location within the source
//! text. This allows the parser using the tokenizer to provide
//! better error highlighting and other functionality.
//!
//! # Examples
//!
//! ```
//! use commands::tokenizer::tokenize;
//!
//! if let Ok(tokens) = tokenize("word") {
//!     assert_eq!(tokens.len(), 1);
//! }
//!
//! if let Ok(tokens) = tokenize("show interface") {
//!     assert_eq!(tokens.len(), 3);
//! }
//!
//! if let Ok(tokens) = tokenize("echo -n \"a b c\"") {
//!     assert_eq!(tokens.len(), 5);
//! }
//!
//! if let Ok(tokens) = tokenize("ls My\\ Documents") {
//!     assert_eq!(tokens.len(), 3);
//!     assert_eq!(tokens[2].text, "My\\ Documents");
//! }
//! ```

/// A position within a string.
///
/// The `SourceOffset` tracks 2 different ways of locating the
/// position:
///
/// * The index of the character within the string.
/// * The column and line number of the character.
///
/// The `SourceOffset` is typically used as a pair of offsets
/// indicating the start and end of a range of text as used
/// by the `SourceLocation`.
#[derive(Debug,PartialEq)]
pub struct SourceOffset {
    /// The index of this character within the string.
    pub char: usize,
    /// The line number on which this character may be found.
    pub line: usize,
    /// The column on which this character may be found.
    pub column: usize,
}

impl SourceOffset {
    /// Construct a `SourceOffset`.
    pub fn new(char: usize, line: usize, column: usize) -> SourceOffset {
        SourceOffset {
            char: char,
            line: line,
            column: column,
        }
    }
}

/// A range within a string.
#[derive(Debug,PartialEq)]
pub struct SourceLocation {
    /// The start of the range.
    pub start: SourceOffset,
    /// The end of the range.
    pub end: SourceOffset,
}

impl SourceLocation {
    /// Construct a `SourceLocation`.
    pub fn new(start: SourceOffset, end: SourceOffset) -> SourceLocation {
        SourceLocation {
            start: start,
            end: end,
        }
    }
}

/// Errors
pub enum TokenizerError {
    /// Character not allowed here
    CharacterNotAllowedHere(usize),

    /// Special not yet implemented
    SpecialNotYetImplemented(usize),

    /// Escaping backslash at end of input
    EscapingBackslashAtEndOfInput,

    /// Unclosed double quote at end of input
    UnclosedDoubleQuoteAtEndOfInput,

    /// Escaped double quote at end of input
    EscapedDoubleQuoteAtEndOfInput,
}

/// The role that a token plays: `Whitespace` or `Word`.
#[derive(Clone,Copy,Debug,PartialEq)]
pub enum TokenType {
    /// Internal usage only.
    Invalid,
    /// The token represents whitespace and not a word.
    Whitespace,
    /// The token represents a word within the string. This
    /// takes double quoted strings into account.
    Word,
}

/// A token from a body of text.
///
/// The lifetime parameter `'t` refers to the lifetime
/// of the body of text that was tokenized, creating this token.
#[derive(Debug,PartialEq)]
pub struct Token<'t> {
    /// The text of the token.
    pub text: &'t str,
    /// The type of the token (`Whitespace` or `Word`).
    pub token_type: TokenType,
    /// The location of the token in the source body of text.
    pub location: SourceLocation,
}

impl<'t> Token<'t> {
    /// Construct a `Token`. The lifetime parameter `'t` refers to the lifetime of the
    /// text being tokenized.
    pub fn new(text: &'t str, token_type: TokenType, location: SourceLocation) -> Token {
        Token {
            text: text,
            token_type: token_type,
            location: location,
        }
    }
}

#[derive(Clone,Copy,PartialEq)]
enum State {
    Initial,
    Special,
    Whitespace,
    Doublequote,
    DoublequoteBackslash,
    Word,
    WordBackslash,
}

struct Tokenizer<'t> {
    text: &'t str,
    state: State,
    token_type: TokenType,
    token_start: usize,
    token_end: usize,
    tokens: Vec<Token<'t>>,
}

impl<'t> Tokenizer<'t> {
    fn new(text: &'t str) -> Tokenizer {
        Tokenizer {
            text: text,
            state: State::Initial,
            token_type: TokenType::Invalid,
            token_start: 0,
            token_end: 0,
            tokens: vec![],
        }
    }

    fn reset(&mut self) {
        self.state = State::Initial;
        self.token_type = TokenType::Invalid;
        self.token_start = 0;
        self.token_end = 0;
    }

    fn reduce(&mut self) {
        let token_text = &self.text[self.token_start..self.token_end + 1];
        let loc = SourceLocation::new(SourceOffset::new(self.token_start, 0, self.token_start),
                                      SourceOffset::new(self.token_end, 0, self.token_end));
        self.tokens.push(Token::new(token_text, self.token_type, loc));
        self.reset();
    }

    fn shift(&mut self, offset: usize, next_state: State) {
        self.recognize(offset, next_state);
        self.token_end = offset;
        self.state = next_state;
    }

    fn recognize(&mut self, offset: usize, next_state: State) {
        if self.token_type == TokenType::Invalid {
            self.token_type = if next_state == State::Whitespace {
                TokenType::Whitespace
            } else {
                TokenType::Word
            };
            self.token_start = offset;
        }
    }

    fn special(&mut self, offset: usize) {
        self.shift(offset, State::Special);
        self.reduce();
    }

    fn initial(&mut self, offset: usize, c: char) {
        if c.is_whitespace() {
            self.shift(offset, State::Whitespace);
        } else if c == ';' {
            self.special(offset);
        } else if c == '?' {
            self.special(offset);
        } else if c == '|' {
            self.special(offset);
        } else if c == '"' {
            self.shift(offset, State::Doublequote);
        } else if c == '\\' {
            self.recognize(offset, State::Word);
            self.shift(offset, State::WordBackslash);
        } else {
            self.shift(offset, State::Word);
        }
    }

    fn tokenize(&mut self) -> Result<(), TokenizerError> {
        for (offset, c) in self.text.chars().enumerate() {
            match self.state {
                State::Initial => self.initial(offset, c),
                State::Whitespace => {
                    if c.is_whitespace() {
                        self.shift(offset, State::Whitespace);
                    } else {
                        self.reduce();
                        self.initial(offset, c);
                    };
                }
                State::Word => {
                    if c.is_whitespace() {
                        self.reduce();
                        self.shift(offset, State::Whitespace);
                    } else if c == ';' {
                        self.reduce();
                        self.special(offset);
                    } else if c == '|' {
                        self.reduce();
                        self.special(offset);
                    } else if c == '"' {
                        self.reduce();
                        self.shift(offset, State::Doublequote);
                    } else if c == '\\' {
                        self.shift(offset, State::WordBackslash);
                    } else {
                        self.shift(offset, State::Word);
                    }
                }
                State::WordBackslash => {
                    // XXX: This should be if !c.is_control() perhaps?
                    if c.is_alphanumeric() || c.is_whitespace() {
                        self.shift(offset, State::Word);
                    } else {
                        return Err(TokenizerError::CharacterNotAllowedHere(offset));
                    };
                }
                State::Doublequote => {
                    if c == '"' {
                        self.shift(offset, State::Doublequote);
                        self.reduce();
                    } else if c == '\\' {
                        self.shift(offset, State::DoublequoteBackslash);
                    } else {
                        self.shift(offset, State::Doublequote);
                    };
                }
                State::DoublequoteBackslash => {
                    if !c.is_whitespace() {
                        self.shift(offset, State::Doublequote);
                    } else {
                        return Err(TokenizerError::CharacterNotAllowedHere(offset));
                    };
                }
                State::Special => {
                    return Err(TokenizerError::SpecialNotYetImplemented(offset));
                }
            }
        }

        // Now for the end of the text...
        match self.state {
            State::Initial => {}
            State::Word => self.reduce(),
            State::Whitespace => self.reduce(),
            State::WordBackslash => return Err(TokenizerError::EscapingBackslashAtEndOfInput),
            State::Doublequote => return Err(TokenizerError::UnclosedDoubleQuoteAtEndOfInput),
            State::DoublequoteBackslash => {
                return Err(TokenizerError::EscapedDoubleQuoteAtEndOfInput)
            }
            State::Special => {
                return Err(TokenizerError::SpecialNotYetImplemented(self.text.len() - 1))
            }
        }

        Ok(())
    }
}

/// Tokenize a body of text.
pub fn tokenize(text: &str) -> Result<Vec<Token>, TokenizerError> {
    let mut tokenizer = Tokenizer::new(text);
    match tokenizer.tokenize() {
        Ok(_) => Ok(tokenizer.tokens),
        Err(error) => Err(error),
    }
}

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

    fn mk_token(text: &str, token_type: TokenType, start: usize, end: usize) -> Token {
        Token::new(text,
                   token_type,
                   SourceLocation::new(SourceOffset::new(start, 0, start),
                                       SourceOffset::new(end, 0, end)))
    }

    #[test]
    fn empty_test() {
        match tokenize("") {
            Ok(ts) => assert_eq!(ts.len(), 0),
            _ => {}
        };
    }

    #[test]
    fn single_word() {
        match tokenize("a") {
            Ok(ts) => {
                assert_eq!(ts.len(), 1);
                assert_eq!(ts[0], mk_token("a", TokenType::Word, 0, 0));
            }
            _ => {}
        };
    }

    #[test]
    fn multiple_words() {
        match tokenize(" aa bb  ccc ") {
            Ok(ts) => {
                assert_eq!(ts.len(), 7);
                assert_eq!(ts[0], mk_token(" ", TokenType::Whitespace, 0, 0));
                assert_eq!(ts[1], mk_token("aa", TokenType::Word, 1, 2));
                assert_eq!(ts[2], mk_token(" ", TokenType::Whitespace, 3, 3));
                assert_eq!(ts[3], mk_token("bb", TokenType::Word, 4, 5));
                assert_eq!(ts[4], mk_token("  ", TokenType::Whitespace, 6, 7));
                assert_eq!(ts[5], mk_token("ccc", TokenType::Word, 8, 10));
                assert_eq!(ts[6], mk_token(" ", TokenType::Whitespace, 11, 11));
            }
            _ => {}
        };
    }

    #[test]
    fn quoted_text() {
        match tokenize("a \"b c\"") {
            Ok(ts) => {
                assert_eq!(ts.len(), 3);
                assert_eq!(ts[0], mk_token("a", TokenType::Word, 0, 0));
                assert_eq!(ts[1], mk_token(" ", TokenType::Whitespace, 1, 1));
                assert_eq!(ts[2], mk_token("\"b c\"", TokenType::Word, 2, 6));
            }
            _ => {}
        };
    }

    #[test]
    fn escaped_whitespace_in_word() {
        match tokenize("a\\ b") {
            Ok(ts) => {
                assert_eq!(ts.len(), 1);
                assert_eq!(ts[0], mk_token("a\\ b", TokenType::Word, 0, 3));
            }
            _ => {}
        };
    }
}
<|endoftext|>


<commit_before>use event::Event;
use sass::comment::SassComment;
use sass::rule::SassRule;
use sass::selector::SassSelector;
use sass::variable::SassVariable;
use top_level_event::TopLevelEvent;

use std::borrow::Cow::Borrowed;

fn is_ascii_whitespace(c: u8) -> bool {
   is_newline(c) || is_ascii_whitespace_no_nl(c)
}

fn is_ascii_whitespace_no_nl(c: u8) -> bool {
    c == b'\t' || c == 0x0b || c == 0x0c || c == b' '
}

fn is_newline(c: u8) -> bool {
    c == b'\n' || c == b'\r'
}

fn isnt_newline(c: u8) -> bool {
    !is_newline(c)
}

// unusual among "scan" functions in that it scans from the _back_ of the string
// TODO: should also scan unicode whitespace?
fn scan_trailing_whitespace(data: &str) -> usize {
    match data.as_bytes().iter().rev().position(|&c| !is_ascii_whitespace_no_nl(c)) {
        Some(i) => i,
        None => data.len()
    }
}

#[derive(Debug)]
pub struct Tokenizer<'a> {
    sass: &'a str,
    bytes: &'a [u8],
    offset: usize,
    state: State,
    sass_rule_stack: Vec<SassRule<'a>>,
    current_sass_rule_selectors_done: bool,
}

#[derive(PartialEq, Debug, Clone)]
enum State {
    OutsideRules,
    InVariable,
    InComment,
    InRule,
    InSelectors,
    InProperties,
    EndRule,
    Eof,
}

impl<'a> Tokenizer<'a> {
    pub fn new(sass: &'a str) -> Tokenizer<'a> {
        Tokenizer {
            sass: &sass,
            bytes: &sass.as_bytes(),
            offset: 0,
            state: State::OutsideRules,
            sass_rule_stack: Vec::new(),
            current_sass_rule_selectors_done: false,
        }
    }

    fn start_something(&mut self) -> Option<TopLevelEvent<'a>> {
        let mut current_sass_rule = SassRule::new();
        self.current_sass_rule_selectors_done = false;

        self.pick_something();
        while self.state != State::OutsideRules {
            if self.state == State::Eof { return None }

            if self.state == State::InSelectors {
                let sel = self.next_selector();
                if sel.is_some() {
                    current_sass_rule.selectors.push(sel.unwrap());
                }
            } else if self.state == State::InProperties {
                let prop = self.next_property();
                if prop.is_some() {
                    current_sass_rule.children.push(prop.unwrap());
                }
            } else if self.state == State::EndRule {
                // consume '}', should probably be checking that's what we actually have
                self.offset += 1;

                match self.sass_rule_stack.pop() {
                    Some(mut rule) => {
                        rule.children.push(Event::ChildRule(current_sass_rule));
                        current_sass_rule = rule;
                        self.pick_something();
                    },
                    None => self.state = State::OutsideRules,
                }
            } else if self.state == State::InRule {
                self.sass_rule_stack.push(current_sass_rule);
                current_sass_rule = SassRule::new();
                self.current_sass_rule_selectors_done = false;
                self.pick_something();
            } else if self.state == State::InVariable {
                let var = self.next_variable();
                if var.is_some() {
                    return var
                } else {
                    // is this really what we should be doing here? reachable?
                    self.state = State::Eof;
                }
            } else if self.state == State::InComment {
                let comment = self.next_comment();
                if comment.is_some() {
                    if self.sass_rule_stack.len() == 0 &&
                       current_sass_rule.selectors.len() == 0 &&
                       current_sass_rule.children.len() == 0 {
                           return Some(TopLevelEvent::Comment(SassComment { comment: comment.unwrap() }))
                    } else {
                        current_sass_rule.children.push(comment.unwrap());
                        self.pick_something();
                    }
                } else {
                    // is this really what we should be doing here? reachable?
                    self.state = State::Eof;
                }
            } else {
                println!("i dont know what to do for {:?}", self.state);
                println!("current sass rule = {:?}", current_sass_rule);
                self.state = State::Eof;
            }
        }

        Some(TopLevelEvent::Rule(current_sass_rule))
    }

    fn pick_something(&mut self) {
        self.skip_leading_whitespace();

        if self.offset == self.sass.len() {
            self.state = State::Eof;
            return
        }

        let c = self.bytes[self.offset];

        if c == b'}' {
            self.state = State::EndRule;
            return
        }

        if c == b'$' {
            self.state = State::InVariable;
            return
        }

        if c == b'/' && (self.offset + 1) < self.sass.len() {
            let d = self.bytes[self.offset + 1];
            if d == b'*' {
                self.state = State::InComment;
                return
            }
        }

        if self.current_sass_rule_selectors_done {
            self.state = State::InProperties;
            return
        }

        self.state = State::InSelectors;
    }

    fn scan_while<F>(&mut self, data: &str, f: F) -> usize
            where F: Fn(u8) -> bool {
        match data.as_bytes().iter().position(|&c| !f(c)) {
            Some(i) => i,
            None => data.len()
        }
    }

    fn skip_leading_whitespace(&mut self) {
        let mut i = self.offset;
        let limit = self.sass.len();

        while i < limit {
            let c = self.bytes[i];
            if is_ascii_whitespace(c) {
                i += self.scan_while(&self.sass[i..self.sass.len()], is_ascii_whitespace);
            } else if c == b'/' && i + 1 < limit && self.bytes[i + 1] == b'/' {
                i += self.scan_while(&self.sass[i..self.sass.len()], isnt_newline);
            } else {
                self.offset = i;
                return
            }
        }
        self.offset = limit;
    }

    fn next_comment(&mut self) -> Option<Event<'a>> {
        let comment_body_beginning = self.offset;
        let mut i = comment_body_beginning + 2;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b'*' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            if self.bytes[i+1] == b'/' {
                self.offset = i + 2;
                return Some(Event::Comment(Borrowed(&self.sass[comment_body_beginning..i + 2])))
            } else {
                i += 1;
            }
        }
        None
    }

    fn next_variable(&mut self) -> Option<TopLevelEvent<'a>> {
        // TODO: can parts of this be deduplicated with properties?
        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Some(TopLevelEvent::Variable(SassVariable{
                    name: Borrowed(&self.sass[name_beginning..name_end]),
                    value: Borrowed(&self.sass[value_beginning..value_end]),
                }))
            }
        }
        self.offset = self.sass.len();
        None
    }

    fn next_property(&mut self) -> Option<Event<'a>> {
        self.skip_leading_whitespace();

        if self.offset == self.sass.len() {
            self.state = State::Eof;
            return None
        }

        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        let c = self.bytes[i];
        if c == b'}' {
            self.state = State::EndRule;
            return None
        }

        let d = self.bytes[i + 1];
        if c == b'/' && d == b'*' {
            self.state = State::InComment;
            return None
        }

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' || c == b'{') {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            // Inefficient since we already skipped the whitespace and we'll have to
            // do it again but oh well
            let c = self.bytes[i];
            if c == b'{' {
                self.state = State::InRule;
                return None
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                if self.bytes[name_beginning] == b'$' {
                    return Some(Event::Variable(SassVariable {
                        name: Borrowed(&self.sass[name_beginning..name_end]),
                        value: Borrowed(&self.sass[value_beginning..value_end])
                    }))
                } else {
                    return Some(Event::Property(
                        Borrowed(&self.sass[name_beginning..name_end]),
                        Borrowed(&self.sass[value_beginning..value_end])
                    ))
                }
            }
        }
        self.offset = self.sass.len();
        None
    }

    fn next_selector(&mut self) -> Option<SassSelector<'a>> {
        self.skip_leading_whitespace();

        let beginning = self.offset;
        let mut i = beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b',' || c == b'{' || c == b':') {
                Some(pos) => { i += pos; },
                None => { i = limit; break; },
            }

            let c = self.bytes[i];

            if c == b':' {
                self.state = State::InProperties;
                return None
            }

            if c == b',' || c == b'{' {
                let n = scan_trailing_whitespace(&self.sass[beginning..i]);
                let end = i - n;
                if end > beginning {
                    if c == b'{' {
                        if self.current_sass_rule_selectors_done {
                            self.state = State::InRule;
                            return None
                        } else {
                            self.current_sass_rule_selectors_done = true;
                            self.state = State::InProperties;
                        }
                    }
                    self.offset = i + 1;
                    return Some(SassSelector::new(&self.sass[beginning..end]))
                } else {
                    // only whitespace between commas
                    self.offset += 1;
                    return self.next_selector()
                }
            }

            self.offset = i;
            if i > beginning {
                return Some(SassSelector::new(&self.sass[beginning..i]))
            }
            i += 1;
        }

        if i > beginning {
            self.offset = i;
            Some(SassSelector::new(&self.sass[beginning..i]))
        } else {
            self.state = State::Eof;
            None
        }
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = TopLevelEvent<'a>;

    fn next(&mut self) -> Option<TopLevelEvent<'a>> {
        if self.offset < self.sass.len() {
            return self.start_something()
        }
        None
    }
}
<commit_msg>Add some ideas from jake<commit_after>use event::Event;
use sass::comment::SassComment;
use sass::rule::SassRule;
use sass::selector::SassSelector;
use sass::variable::SassVariable;
use top_level_event::TopLevelEvent;

use std::borrow::Cow::Borrowed;

fn is_ascii_whitespace(c: u8) -> bool {
   is_newline(c) || is_ascii_whitespace_no_nl(c)
}

fn is_ascii_whitespace_no_nl(c: u8) -> bool {
    c == b'\t' || c == 0x0b || c == 0x0c || c == b' '
}

fn is_newline(c: u8) -> bool {
    c == b'\n' || c == b'\r'
}

fn isnt_newline(c: u8) -> bool {
    !is_newline(c)
}

// unusual among "scan" functions in that it scans from the _back_ of the string
// TODO: should also scan unicode whitespace?
fn scan_trailing_whitespace(data: &str) -> usize {
    match data.as_bytes().iter().rev().position(|&c| !is_ascii_whitespace_no_nl(c)) {
        Some(i) => i,
        None => data.len()
    }
}

#[derive(Debug)]
pub struct Tokenizer<'a> {
    sass: &'a str,
    bytes: &'a [u8],
    offset: usize,
    state: State,
    sass_rule_stack: Vec<SassRule<'a>>,
    current_sass_rule_selectors_done: bool,
}

#[derive(PartialEq, Debug, Copy, Clone)]
enum State {
    OutsideRules,
    InVariable,
    InComment,
    InRule,
    InSelectors,
    InProperties,
    EndRule,
    Eof,
}

impl<'a> Tokenizer<'a> {
    pub fn new(sass: &'a str) -> Tokenizer<'a> {
        Tokenizer {
            sass: &sass,
            bytes: &sass.as_bytes(),
            offset: 0,
            state: State::OutsideRules,
            sass_rule_stack: Vec::new(),
            current_sass_rule_selectors_done: false,
        }
    }

    fn start_something(&mut self) -> Option<TopLevelEvent<'a>> {
        let mut current_sass_rule = SassRule::new();
        self.current_sass_rule_selectors_done = false;

        self.pick_something();
        while self.state != State::OutsideRules {
            if self.state == State::Eof { return None }

            // See if this works now that State is Copy
            // self.state = match self.state

            if self.state == State::InSelectors {
                let sel = self.next_selector();
                if sel.is_some() {
                    current_sass_rule.selectors.push(sel.unwrap());
                }
            } else if self.state == State::InProperties {
                let prop = self.next_property();
                if prop.is_some() {
                    current_sass_rule.children.push(prop.unwrap());
                }
            } else if self.state == State::EndRule {
                // consume '}', should probably be checking that's what we actually have
                self.offset += 1;

                match self.sass_rule_stack.pop() {
                    Some(mut rule) => {
                        rule.children.push(Event::ChildRule(current_sass_rule));
                        current_sass_rule = rule;
                        self.pick_something();
                    },
                    None => self.state = State::OutsideRules,
                }
            } else if self.state == State::InRule {
                self.sass_rule_stack.push(current_sass_rule);
                current_sass_rule = SassRule::new();
                self.current_sass_rule_selectors_done = false;
                self.pick_something();
            } else if self.state == State::InVariable {
                let var = self.next_variable();
                if var.is_some() {
                    return var
                } else {
                    // is this really what we should be doing here? reachable?
                    self.state = State::Eof;
                }
            } else if self.state == State::InComment {
                let comment = self.next_comment();
                if comment.is_some() {
                    if self.sass_rule_stack.len() == 0 &&
                       current_sass_rule.selectors.len() == 0 &&
                       current_sass_rule.children.len() == 0 {
                           return Some(TopLevelEvent::Comment(SassComment { comment: comment.unwrap() }))
                    } else {
                        current_sass_rule.children.push(comment.unwrap());
                        self.pick_something();
                    }
                } else {
                    // is this really what we should be doing here? reachable?
                    self.state = State::Eof;
                }
            } else {
                println!("i dont know what to do for {:?}", self.state);
                println!("current sass rule = {:?}", current_sass_rule);
                self.state = State::Eof;
            }
        }

        Some(TopLevelEvent::Rule(current_sass_rule))
    }

    fn pick_something(&mut self) {
        self.skip_leading_whitespace();

        if self.offset == self.sass.len() {
            self.state = State::Eof;
            return
        }

        let c = self.bytes[self.offset];

        if c == b'}' {
            self.state = State::EndRule;
            return
        }

        if c == b'$' {
            self.state = State::InVariable;
            return
        }

        if c == b'/' && (self.offset + 1) < self.sass.len() {
            let d = self.bytes[self.offset + 1];
            if d == b'*' {
                self.state = State::InComment;
                return
            }
        }

        if self.current_sass_rule_selectors_done {
            self.state = State::InProperties;
            return
        }

        self.state = State::InSelectors;
    }

    fn scan_while<F>(&mut self, data: &str, f: F) -> usize
            where F: Fn(u8) -> bool {
        match data.as_bytes().iter().position(|&c| !f(c)) {
            Some(i) => i,
            None => data.len()
        }
    }

    fn skip_leading_whitespace(&mut self) {
        let mut i = self.offset;
        let limit = self.sass.len();

        while i < limit {
            let c = self.bytes[i];
            if is_ascii_whitespace(c) {
                i += self.scan_while(&self.sass[i..self.sass.len()], is_ascii_whitespace);
            } else if c == b'/' && i + 1 < limit && self.bytes[i + 1] == b'/' {
                i += self.scan_while(&self.sass[i..self.sass.len()], isnt_newline);
            } else {
                self.offset = i;
                return
            }
        }
        self.offset = limit;
    }

    fn next_comment(&mut self) -> Option<Event<'a>> {
        let comment_body_beginning = self.offset;
        let mut i = comment_body_beginning + 2;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b'*' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            if self.bytes[i+1] == b'/' {
                self.offset = i + 2;
                return Some(Event::Comment(Borrowed(&self.sass[comment_body_beginning..i + 2])))
            } else {
                i += 1;
            }
        }
        None
    }

    fn next_variable(&mut self) -> Option<TopLevelEvent<'a>> {
        // TODO: can parts of this be deduplicated with properties?
        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' ) {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                return Some(TopLevelEvent::Variable(SassVariable{
                    name: Borrowed(&self.sass[name_beginning..name_end]),
                    value: Borrowed(&self.sass[value_beginning..value_end]),
                }))
            }
        }
        self.offset = self.sass.len();
        None
    }

    fn next_property(&mut self) -> Option<Event<'a>> {
        self.skip_leading_whitespace();

        if self.offset == self.sass.len() {
            self.state = State::Eof;
            return None
        }

        let name_beginning = self.offset;
        let mut i = name_beginning;
        let limit = self.sass.len();

        let c = self.bytes[i];
        if c == b'}' {
            self.state = State::EndRule;
            return None
        }

        let d = self.bytes[i + 1];
        if c == b'/' && d == b'*' {
            self.state = State::InComment;
            return None
        }

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b':' || c == b'{') {
                Some(pos) => { i += pos; },
                None => { break; },
            }

            // Inefficient since we already skipped the whitespace and we'll have to
            // do it again but oh well
            let c = self.bytes[i];
            if c == b'{' {
                self.state = State::InRule;
                return None
            }

            let name_end = i;

            i += 1;
            self.offset = i;
            self.skip_leading_whitespace();

            let value_beginning = self.offset;
            i = value_beginning;

            while i < limit {
                match self.bytes[i..limit].iter().position(|&c| c == b';') {
                    Some(pos) => { i += pos; },
                    None => { i = limit; break; },
                }

                let value_end = i;
                self.offset = i + 1;

                self.skip_leading_whitespace();

                if self.bytes[name_beginning] == b'$' {
                    return Some(Event::Variable(SassVariable {
                        name: Borrowed(&self.sass[name_beginning..name_end]),
                        value: Borrowed(&self.sass[value_beginning..value_end])
                    }))
                } else {
                    return Some(Event::Property(
                        Borrowed(&self.sass[name_beginning..name_end]),
                        Borrowed(&self.sass[value_beginning..value_end])
                    ))
                }
            }
        }
        self.offset = self.sass.len();
        None
    }

    fn next_selector(&mut self) -> Option<SassSelector<'a>> {
        self.skip_leading_whitespace();

        let beginning = self.offset;
        let mut i = beginning;
        let limit = self.sass.len();

        while i < limit {
            match self.bytes[i..limit].iter().position(|&c| c == b',' || c == b'{' || c == b':') {
                Some(pos) => { i += pos; },
                None => { i = limit; break; },
            }

            let c = self.bytes[i];

            if c == b':' {
                self.state = State::InProperties;
                return None
            }

            if c == b',' || c == b'{' {
                let n = scan_trailing_whitespace(&self.sass[beginning..i]);
                let end = i - n;
                if end > beginning {
                    if c == b'{' {
                        if self.current_sass_rule_selectors_done {
                            self.state = State::InRule;
                            return None
                        } else {
                            self.current_sass_rule_selectors_done = true;
                            self.state = State::InProperties;
                        }
                    }
                    self.offset = i + 1;
                    return Some(SassSelector::new(&self.sass[beginning..end]))
                } else {
                    // only whitespace between commas
                    self.offset += 1;
                    return self.next_selector()
                }
            }

            self.offset = i;
            if i > beginning {
                return Some(SassSelector::new(&self.sass[beginning..i]))
            }
            i += 1;
        }

        if i > beginning {
            self.offset = i;
            Some(SassSelector::new(&self.sass[beginning..i]))
        } else {
            self.state = State::Eof;
            None
        }
    }
}

impl<'a> Iterator for Tokenizer<'a> {
    type Item = TopLevelEvent<'a>;

    fn next(&mut self) -> Option<TopLevelEvent<'a>> {
        if self.offset < self.sass.len() {
            return self.start_something()
        }
        None
    }
}
<|endoftext|>


<commit_before>// See LICENSE file for copyright and license details.

// Marauder is grid-less turn-based strategy game.

extern mod glfw;
extern mod gl;
extern mod cgmath;
extern mod native;

use std::f32::consts::{
  PI,
  FRAC_PI_2
};
use std::num::{
  sqrt,
  pow,
  abs,
  sin,
  cos
};
use gltypes = gl::types;
use cgmath::matrix::{
  Matrix,
  Mat4,
  Mat3,
  ToMat4
};
use cgmath::vector::{
  Vec3,
  Vec2,
  Vector
};
use cgmath::projection;
use cgmath::angle;

#[link(name = "glfw")]
#[link(name = "m")]
extern {}

pub mod misc {
  use cgmath::matrix::Mat4;
  use std;

  pub fn print_mat4(name: &str, mat: Mat4<f32>) {
    println!("{}:", name);
    for i in range(0u, 4) {
      for j in range(0u, 4) {
        print!("{} ", *mat.cr(i, j));
      }
      println("");
    }
    println("");
  }

  /// usage: let s = mvp_matrix; println(type_of(&s));
  pub fn type_of<T>(_: &T) -> &'static str {
    unsafe {
      (*std::unstable::intrinsics::get_tydesc::<T>()).name
    }
  }
}

static mut MOUSE_POS: Vec2<f32> = Vec2{x: 0.0f32, y: 0.0};
static mut CAMERA_POS: Vec3<f32> = Vec3{x: 0.0f32, y: 0.0, z: 0.0};

static WIN_SIZE: Vec2<u32> = Vec2{x: 640, y: 480};
static VERTICES_COUNT: i32 = 3 * 2;

static VERTEX_DATA: [gltypes::GLfloat, ..VERTICES_COUNT * 3] = [
   0.0,  1.0, 0.0,
   2.0, -1.0, 0.0,
  -2.0, -1.0, 0.0,

  0.0,  1.0,  0.0,
  0.0, -1.0,  2.0,
  0.0, -1.0, -2.0
];

static VERTEX_SHADER_SRC: &'static str = "
  #version 130
  in vec3 position;
  uniform mat4 model_view_proj_matrix;
  void main() {
    vec4 v = vec4(position, 1);
    gl_Position = model_view_proj_matrix * v;
  }
";

static FRAGMENT_SHADER_SRC: &'static str = "
  #version 130
  out vec4 out_color;
  void main() {
    out_color = vec4(1.0, 1.0, 1.0, 1.0);
  }
";

pub struct Visualizer {
  hex_ex_radius: gltypes::GLfloat,
  hex_in_radius: gltypes::GLfloat
}

impl Visualizer {
  pub fn new() -> Visualizer {
    let hex_ex_radius: gltypes::GLfloat = 1.0 / 2.0;
    let hex_in_radius = sqrt(
        pow(hex_ex_radius, 2.0) - pow(hex_ex_radius / 2.0, 2.0));
    let visualizer = Visualizer {
      hex_ex_radius: hex_ex_radius,
      hex_in_radius: hex_in_radius
    };
    visualizer
  }

  pub fn dist(a: Vec2<f32>, b: Vec2<f32>) -> f32 {
    let dx = abs(b.x - a.x);
    let dy = abs(b.y - a.y);
    sqrt(pow(dx, 2.0) + pow(dy, 2.0))
  }

  pub fn v2i_to_v2f(&self, i: Vec2<i32>) -> Vec2<f32> {
    let v = Vec2 {
      x: (i.x as f32) * self.hex_in_radius * 2.0,
      y: (i.y as f32) * self.hex_ex_radius * 1.5
    };
    if i.y % 2 == 0 {
      Vec2{x: v.x + self.hex_in_radius, y: v.y}
    } else {
      v
    }
  }

  pub fn index_to_circle_vertex(&self, count: int, i: int) -> Vec2<f32> {
    let n = FRAC_PI_2 + 2.0 * PI * (i as f32) / (count as f32);
    Vec2{x: cos(n), y: sin(n)}.mul_s(self.hex_ex_radius)
  }
}

fn compile_shader(src: &str, shader_type: gltypes::GLenum) -> gltypes::GLuint {
  let shader = gl::CreateShader(shader_type);
  unsafe {
    gl::ShaderSource(shader, 1, &src.to_c_str().unwrap(), std::ptr::null());
    gl::CompileShader(shader);

    let mut status = gl::FALSE as gltypes::GLint;
    gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

    if status != (gl::TRUE as gltypes::GLint) {
      let mut len = 0;
      gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
      // subtract 1 to skip the trailing null character
      let mut buf = std::vec::from_elem(len as uint - 1, 0u8);
      gl::GetShaderInfoLog(shader, len, std::ptr::mut_null(),
        buf.as_mut_ptr() as *mut gltypes::GLchar
      );
      fail!("compile_shader(): " + std::str::raw::from_utf8(buf));
    }
  }
  shader
}

fn link_program(
    vertex_shader: gltypes::GLuint,
    fragment_shader: gltypes::GLuint
) -> gltypes::GLuint {
  let program = gl::CreateProgram();
  gl::AttachShader(program, vertex_shader);
  gl::AttachShader(program, fragment_shader);
  gl::LinkProgram(program);
  unsafe {
    let mut status = gl::FALSE as gltypes::GLint;
    gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);

    if status != (gl::TRUE as gltypes::GLint) {
      let mut len: gltypes::GLint = 0;
      gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
      // subtract 1 to skip the trailing null character
      let mut buf = std::vec::from_elem(len as uint - 1, 0u8);
      gl::GetProgramInfoLog(program, len, std::ptr::mut_null(),
        buf.as_mut_ptr() as *mut gltypes::GLchar
      );
      fail!("link_program(): " + std::str::raw::from_utf8(buf));
    }
  }
  program
}

fn tr(m: Mat4<f32>, v: Vec3<f32>) -> Mat4<f32> {
  let mut t = Mat4::<f32>::identity();
  *t.mut_cr(3, 0) = v.x;
  *t.mut_cr(3, 1) = v.y;
  *t.mut_cr(3, 2) = v.z;
  m.mul_m(&t)
}

fn rot_x(m: Mat4<f32>, angle: f32) -> Mat4<f32> {
  let r = Mat3::from_angle_x(angle::rad(angle)).to_mat4();
  m.mul_m(&r)
}

fn rot_y(m: Mat4<f32>, angle: f32) -> Mat4<f32> {
  let r = Mat3::from_angle_y(angle::rad(angle)).to_mat4();
  m.mul_m(&r)
}

struct Win {
  vertex_shader: gltypes::GLuint,
  fragment_shader: gltypes::GLuint,
  program: gltypes::GLuint,
  vertex_array_obj: gltypes::GLuint,
  vertex_buffer_obj: gltypes::GLuint,
  matrix_id: gltypes::GLint,
  projection_matrix: Mat4<f32>,
  window: glfw::Window
}

fn get_projection_matrix() -> Mat4<f32> {
  let fov = angle::deg(45.0f32);
  let ratio = 4.0 / 3.0;
  let display_range_min = 0.1;
  let display_range_max = 100.0;
  projection::perspective(
    fov, ratio, display_range_min, display_range_max
  )
}

impl Win {
  fn new() -> Win {
    let mut win = Win{
      vertex_shader: 0,
      fragment_shader: 0,
      program: 0,
      vertex_array_obj: 0,
      vertex_buffer_obj: 0,
      matrix_id: 0,
      projection_matrix: get_projection_matrix(),
      window: glfw::Window::create(
        WIN_SIZE.x,
        WIN_SIZE.y,
        "OpenGL",
        glfw::Windowed
      ).unwrap()
    };
    win.init_opengl();
    win
  }

  fn init_opengl(&mut self) {
    // glfw::window_hint::context_version(3, 2);

    self.window.make_context_current();
    self.window.set_cursor_pos_callback(~CursorPosContext);
    self.window.set_key_callback(~KeyContext);

    // Load the OpenGL function pointers
    gl::load_with(glfw::get_proc_address);

    self.vertex_shader = compile_shader(
      VERTEX_SHADER_SRC, gl::VERTEX_SHADER);
    self.fragment_shader = compile_shader(
      FRAGMENT_SHADER_SRC, gl::FRAGMENT_SHADER);
    self.program = link_program(self.vertex_shader, self.fragment_shader);

    unsafe {
      // Create Vertex Array Object
      gl::GenVertexArrays(1, &mut self.vertex_array_obj);
      gl::BindVertexArray(self.vertex_array_obj);

      // Create a Vertex Buffer Object
      gl::GenBuffers(1, &mut self.vertex_buffer_obj);
      gl::BindBuffer(gl::ARRAY_BUFFER, self.vertex_buffer_obj);

      // Copy vertex data to VBO
      let float_size = std::mem::size_of::<gltypes::GLfloat>();
      let vertices_ptr = (VERTEX_DATA.len() * float_size) as gltypes::GLsizeiptr;
      gl::BufferData(
        gl::ARRAY_BUFFER,
        vertices_ptr,
        std::cast::transmute(&VERTEX_DATA[0]),
        gl::STATIC_DRAW
      );

      gl::UseProgram(self.program);
      gl::BindFragDataLocation(
        self.program, 0, "out_color".to_c_str().unwrap());

      // Specify the layout of the vertex data
      let pos_attr = gl::GetAttribLocation(
        self.program, "position".to_c_str().unwrap()) as gltypes::GLuint;
      gl::EnableVertexAttribArray(pos_attr);

      let size = 3;
      let normalized = gl::FALSE;
      let stride = 0;
      gl::VertexAttribPointer(
        pos_attr,
        size,
        gl::FLOAT,
        normalized,
        stride,
        std::ptr::null()
      );

      self.matrix_id = gl::GetUniformLocation(
        self.program, "model_view_proj_matrix".to_c_str().unwrap()
      );
    }
  }

  fn cleanup(&self) {
    gl::DeleteProgram(self.program);
    gl::DeleteShader(self.fragment_shader);
    gl::DeleteShader(self.vertex_shader);
    unsafe {
      gl::DeleteBuffers(1, &self.vertex_buffer_obj);
      gl::DeleteVertexArrays(1, &self.vertex_array_obj);
    }
  }

  fn update_matrices(&self) {
    let mut mvp_matrix = self.projection_matrix;
    unsafe {
      mvp_matrix = tr(mvp_matrix, Vec3{x: 0.0f32, y: 0.0, z: -10.0f32});
      mvp_matrix = rot_x(mvp_matrix, MOUSE_POS.y / 100.0);
      mvp_matrix = rot_y(mvp_matrix, MOUSE_POS.x / 100.0);
      mvp_matrix = tr(mvp_matrix, CAMERA_POS);

      // Send our transformation to the currently bound shader,
      // in the "model_view_proj_matrix" uniform for each model
      // you render, since the model_view_proj_matrix will be
      // different (at least the M part).
      gl::UniformMatrix4fv(self.matrix_id, 1, gl::FALSE, mvp_matrix.cr(0, 0));
    }
  }

  fn draw(&self) {
    self.update_matrices();
    gl::ClearColor(0.3, 0.3, 0.3, 1.0);
    gl::Clear(gl::COLOR_BUFFER_BIT);
    gl::DrawArrays(gl::TRIANGLES, 0, VERTICES_COUNT);
    self.window.swap_buffers();
  }

  fn is_running(&self) -> bool {
    return !self.window.should_close()
  }

  fn process_events(&self) {
    glfw::poll_events();
  }
}

struct CursorPosContext;
impl glfw::CursorPosCallback for CursorPosContext {
  fn call(&self, w: &glfw::Window, xpos: f64, ypos: f64) {
    if w.get_mouse_button(glfw::MouseButtonRight) == glfw::Press {
      unsafe {
        MOUSE_POS.x = xpos as f32;
        MOUSE_POS.y = ypos as f32;
      }
    }
  }
}

struct ErrorContext;
impl glfw::ErrorCallback for ErrorContext {
  fn call(&self, _: glfw::Error, description: ~str) {
    println!("GLFW Error: {:s}", description);
  }
}

struct KeyContext;
impl glfw::KeyCallback for KeyContext {
  fn call(
    &self,
    window: &glfw::Window,
    key:    glfw::Key,
    _:      std::libc::c_int,
    action: glfw::Action,
    _:      glfw::Modifiers
  ) {
    let distance = 1.0;
    if action != glfw::Press {
      return;
    }
    match key {
      glfw::KeyEscape | glfw::KeyQ
                     => window.set_should_close(true),
      glfw::KeySpace => println!("space"),
      glfw::KeyUp    => unsafe { CAMERA_POS.y -= distance },
      glfw::KeyDown  => unsafe { CAMERA_POS.y += distance },
      glfw::KeyRight => unsafe { CAMERA_POS.x -= distance },
      glfw::KeyLeft  => unsafe { CAMERA_POS.x += distance },
      _ => {}
    }
  }
}

fn main() {
  glfw::set_error_callback(~ErrorContext);
  do glfw::start {
    let win =  Win::new();
    while win.is_running() {
      win.process_events();
      win.draw();
    }
    win.cleanup();
  }
}

#[start]
fn start(argc: int, argv: **u8) -> int {
  native::start(argc, argv, main)
}

// vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab:
<commit_msg>Visualizer: Added index_to_hex_vertex()<commit_after>// See LICENSE file for copyright and license details.

// Marauder is grid-less turn-based strategy game.

extern mod glfw;
extern mod gl;
extern mod cgmath;
extern mod native;

use std::f32::consts::{
  PI,
  FRAC_PI_2
};
use std::num::{
  sqrt,
  pow,
  abs,
  sin,
  cos
};
use gltypes = gl::types;
use cgmath::matrix::{
  Matrix,
  Mat4,
  Mat3,
  ToMat4
};
use cgmath::vector::{
  Vec3,
  Vec2,
  Vector
};
use cgmath::projection;
use cgmath::angle;

#[link(name = "glfw")]
#[link(name = "m")]
extern {}

pub mod misc {
  use cgmath::matrix::Mat4;
  use std;

  pub fn print_mat4(name: &str, mat: Mat4<f32>) {
    println!("{}:", name);
    for i in range(0u, 4) {
      for j in range(0u, 4) {
        print!("{} ", *mat.cr(i, j));
      }
      println("");
    }
    println("");
  }

  /// usage: let s = mvp_matrix; println(type_of(&s));
  pub fn type_of<T>(_: &T) -> &'static str {
    unsafe {
      (*std::unstable::intrinsics::get_tydesc::<T>()).name
    }
  }
}

static mut MOUSE_POS: Vec2<f32> = Vec2{x: 0.0f32, y: 0.0};
static mut CAMERA_POS: Vec3<f32> = Vec3{x: 0.0f32, y: 0.0, z: 0.0};

static WIN_SIZE: Vec2<u32> = Vec2{x: 640, y: 480};
static VERTICES_COUNT: i32 = 3 * 2;

static VERTEX_DATA: [gltypes::GLfloat, ..VERTICES_COUNT * 3] = [
   0.0,  1.0, 0.0,
   2.0, -1.0, 0.0,
  -2.0, -1.0, 0.0,

  0.0,  1.0,  0.0,
  0.0, -1.0,  2.0,
  0.0, -1.0, -2.0
];

static VERTEX_SHADER_SRC: &'static str = "
  #version 130
  in vec3 position;
  uniform mat4 model_view_proj_matrix;
  void main() {
    vec4 v = vec4(position, 1);
    gl_Position = model_view_proj_matrix * v;
  }
";

static FRAGMENT_SHADER_SRC: &'static str = "
  #version 130
  out vec4 out_color;
  void main() {
    out_color = vec4(1.0, 1.0, 1.0, 1.0);
  }
";

pub struct Visualizer {
  hex_ex_radius: gltypes::GLfloat,
  hex_in_radius: gltypes::GLfloat
}

impl Visualizer {
  pub fn new() -> Visualizer {
    let hex_ex_radius: gltypes::GLfloat = 1.0 / 2.0;
    let hex_in_radius = sqrt(
        pow(hex_ex_radius, 2.0) - pow(hex_ex_radius / 2.0, 2.0));
    let visualizer = Visualizer {
      hex_ex_radius: hex_ex_radius,
      hex_in_radius: hex_in_radius
    };
    visualizer
  }

  pub fn dist(a: Vec2<f32>, b: Vec2<f32>) -> f32 {
    let dx = abs(b.x - a.x);
    let dy = abs(b.y - a.y);
    sqrt(pow(dx, 2.0) + pow(dy, 2.0))
  }

  pub fn v2i_to_v2f(&self, i: Vec2<i32>) -> Vec2<f32> {
    let v = Vec2 {
      x: (i.x as f32) * self.hex_in_radius * 2.0,
      y: (i.y as f32) * self.hex_ex_radius * 1.5
    };
    if i.y % 2 == 0 {
      Vec2{x: v.x + self.hex_in_radius, y: v.y}
    } else {
      v
    }
  }

  pub fn index_to_circle_vertex(&self, count: int, i: int) -> Vec2<f32> {
    let n = FRAC_PI_2 + 2.0 * PI * (i as f32) / (count as f32);
    Vec2{x: cos(n), y: sin(n)}.mul_s(self.hex_ex_radius)
  }

  pub fn index_to_hex_vertex(&self, i: int) -> Vec2<f32> {
    self.index_to_circle_vertex(6, i)
  }
}

fn compile_shader(src: &str, shader_type: gltypes::GLenum) -> gltypes::GLuint {
  let shader = gl::CreateShader(shader_type);
  unsafe {
    gl::ShaderSource(shader, 1, &src.to_c_str().unwrap(), std::ptr::null());
    gl::CompileShader(shader);

    let mut status = gl::FALSE as gltypes::GLint;
    gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

    if status != (gl::TRUE as gltypes::GLint) {
      let mut len = 0;
      gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
      // subtract 1 to skip the trailing null character
      let mut buf = std::vec::from_elem(len as uint - 1, 0u8);
      gl::GetShaderInfoLog(shader, len, std::ptr::mut_null(),
        buf.as_mut_ptr() as *mut gltypes::GLchar
      );
      fail!("compile_shader(): " + std::str::raw::from_utf8(buf));
    }
  }
  shader
}

fn link_program(
    vertex_shader: gltypes::GLuint,
    fragment_shader: gltypes::GLuint
) -> gltypes::GLuint {
  let program = gl::CreateProgram();
  gl::AttachShader(program, vertex_shader);
  gl::AttachShader(program, fragment_shader);
  gl::LinkProgram(program);
  unsafe {
    let mut status = gl::FALSE as gltypes::GLint;
    gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);

    if status != (gl::TRUE as gltypes::GLint) {
      let mut len: gltypes::GLint = 0;
      gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
      // subtract 1 to skip the trailing null character
      let mut buf = std::vec::from_elem(len as uint - 1, 0u8);
      gl::GetProgramInfoLog(program, len, std::ptr::mut_null(),
        buf.as_mut_ptr() as *mut gltypes::GLchar
      );
      fail!("link_program(): " + std::str::raw::from_utf8(buf));
    }
  }
  program
}

fn tr(m: Mat4<f32>, v: Vec3<f32>) -> Mat4<f32> {
  let mut t = Mat4::<f32>::identity();
  *t.mut_cr(3, 0) = v.x;
  *t.mut_cr(3, 1) = v.y;
  *t.mut_cr(3, 2) = v.z;
  m.mul_m(&t)
}

fn rot_x(m: Mat4<f32>, angle: f32) -> Mat4<f32> {
  let r = Mat3::from_angle_x(angle::rad(angle)).to_mat4();
  m.mul_m(&r)
}

fn rot_y(m: Mat4<f32>, angle: f32) -> Mat4<f32> {
  let r = Mat3::from_angle_y(angle::rad(angle)).to_mat4();
  m.mul_m(&r)
}

struct Win {
  vertex_shader: gltypes::GLuint,
  fragment_shader: gltypes::GLuint,
  program: gltypes::GLuint,
  vertex_array_obj: gltypes::GLuint,
  vertex_buffer_obj: gltypes::GLuint,
  matrix_id: gltypes::GLint,
  projection_matrix: Mat4<f32>,
  window: glfw::Window
}

fn get_projection_matrix() -> Mat4<f32> {
  let fov = angle::deg(45.0f32);
  let ratio = 4.0 / 3.0;
  let display_range_min = 0.1;
  let display_range_max = 100.0;
  projection::perspective(
    fov, ratio, display_range_min, display_range_max
  )
}

impl Win {
  fn new() -> Win {
    let mut win = Win{
      vertex_shader: 0,
      fragment_shader: 0,
      program: 0,
      vertex_array_obj: 0,
      vertex_buffer_obj: 0,
      matrix_id: 0,
      projection_matrix: get_projection_matrix(),
      window: glfw::Window::create(
        WIN_SIZE.x,
        WIN_SIZE.y,
        "OpenGL",
        glfw::Windowed
      ).unwrap()
    };
    win.init_opengl();
    win
  }

  fn init_opengl(&mut self) {
    // glfw::window_hint::context_version(3, 2);

    self.window.make_context_current();
    self.window.set_cursor_pos_callback(~CursorPosContext);
    self.window.set_key_callback(~KeyContext);

    // Load the OpenGL function pointers
    gl::load_with(glfw::get_proc_address);

    self.vertex_shader = compile_shader(
      VERTEX_SHADER_SRC, gl::VERTEX_SHADER);
    self.fragment_shader = compile_shader(
      FRAGMENT_SHADER_SRC, gl::FRAGMENT_SHADER);
    self.program = link_program(self.vertex_shader, self.fragment_shader);

    unsafe {
      // Create Vertex Array Object
      gl::GenVertexArrays(1, &mut self.vertex_array_obj);
      gl::BindVertexArray(self.vertex_array_obj);

      // Create a Vertex Buffer Object
      gl::GenBuffers(1, &mut self.vertex_buffer_obj);
      gl::BindBuffer(gl::ARRAY_BUFFER, self.vertex_buffer_obj);

      // Copy vertex data to VBO
      let float_size = std::mem::size_of::<gltypes::GLfloat>();
      let vertices_ptr = (VERTEX_DATA.len() * float_size) as gltypes::GLsizeiptr;
      gl::BufferData(
        gl::ARRAY_BUFFER,
        vertices_ptr,
        std::cast::transmute(&VERTEX_DATA[0]),
        gl::STATIC_DRAW
      );

      gl::UseProgram(self.program);
      gl::BindFragDataLocation(
        self.program, 0, "out_color".to_c_str().unwrap());

      // Specify the layout of the vertex data
      let pos_attr = gl::GetAttribLocation(
        self.program, "position".to_c_str().unwrap()) as gltypes::GLuint;
      gl::EnableVertexAttribArray(pos_attr);

      let size = 3;
      let normalized = gl::FALSE;
      let stride = 0;
      gl::VertexAttribPointer(
        pos_attr,
        size,
        gl::FLOAT,
        normalized,
        stride,
        std::ptr::null()
      );

      self.matrix_id = gl::GetUniformLocation(
        self.program, "model_view_proj_matrix".to_c_str().unwrap()
      );
    }
  }

  fn cleanup(&self) {
    gl::DeleteProgram(self.program);
    gl::DeleteShader(self.fragment_shader);
    gl::DeleteShader(self.vertex_shader);
    unsafe {
      gl::DeleteBuffers(1, &self.vertex_buffer_obj);
      gl::DeleteVertexArrays(1, &self.vertex_array_obj);
    }
  }

  fn update_matrices(&self) {
    let mut mvp_matrix = self.projection_matrix;
    unsafe {
      mvp_matrix = tr(mvp_matrix, Vec3{x: 0.0f32, y: 0.0, z: -10.0f32});
      mvp_matrix = rot_x(mvp_matrix, MOUSE_POS.y / 100.0);
      mvp_matrix = rot_y(mvp_matrix, MOUSE_POS.x / 100.0);
      mvp_matrix = tr(mvp_matrix, CAMERA_POS);

      // Send our transformation to the currently bound shader,
      // in the "model_view_proj_matrix" uniform for each model
      // you render, since the model_view_proj_matrix will be
      // different (at least the M part).
      gl::UniformMatrix4fv(self.matrix_id, 1, gl::FALSE, mvp_matrix.cr(0, 0));
    }
  }

  fn draw(&self) {
    self.update_matrices();
    gl::ClearColor(0.3, 0.3, 0.3, 1.0);
    gl::Clear(gl::COLOR_BUFFER_BIT);
    gl::DrawArrays(gl::TRIANGLES, 0, VERTICES_COUNT);
    self.window.swap_buffers();
  }

  fn is_running(&self) -> bool {
    return !self.window.should_close()
  }

  fn process_events(&self) {
    glfw::poll_events();
  }
}

struct CursorPosContext;
impl glfw::CursorPosCallback for CursorPosContext {
  fn call(&self, w: &glfw::Window, xpos: f64, ypos: f64) {
    if w.get_mouse_button(glfw::MouseButtonRight) == glfw::Press {
      unsafe {
        MOUSE_POS.x = xpos as f32;
        MOUSE_POS.y = ypos as f32;
      }
    }
  }
}

struct ErrorContext;
impl glfw::ErrorCallback for ErrorContext {
  fn call(&self, _: glfw::Error, description: ~str) {
    println!("GLFW Error: {:s}", description);
  }
}

struct KeyContext;
impl glfw::KeyCallback for KeyContext {
  fn call(
    &self,
    window: &glfw::Window,
    key:    glfw::Key,
    _:      std::libc::c_int,
    action: glfw::Action,
    _:      glfw::Modifiers
  ) {
    let distance = 1.0;
    if action != glfw::Press {
      return;
    }
    match key {
      glfw::KeyEscape | glfw::KeyQ
                     => window.set_should_close(true),
      glfw::KeySpace => println!("space"),
      glfw::KeyUp    => unsafe { CAMERA_POS.y -= distance },
      glfw::KeyDown  => unsafe { CAMERA_POS.y += distance },
      glfw::KeyRight => unsafe { CAMERA_POS.x -= distance },
      glfw::KeyLeft  => unsafe { CAMERA_POS.x += distance },
      _ => {}
    }
  }
}

fn main() {
  glfw::set_error_callback(~ErrorContext);
  do glfw::start {
    let win =  Win::new();
    while win.is_running() {
      win.process_events();
      win.draw();
    }
    win.cleanup();
  }
}

#[start]
fn start(argc: int, argv: **u8) -> int {
  native::start(argc, argv, main)
}

// vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab:
<|endoftext|>