<commit_before>use std::io;
use byteorder::{ReadBytesExt, WriteBytesExt, LittleEndian, BigEndian};
use {GameDataRead, GameDataWrite, Texture, Textures};
use gamedata_io::{Chunk, get_chunk_header, ReadError, read_into_byte_vec, Tell};

const IMAGE_DATA_ALIGNMENT: u32 = 128;

impl<'a> Chunk<'a> for Textures {
    const TYPE_ID: &'static [u8; 4] = b"TXTR";
    type ReadOutput = Self;
    fn read<R: GameDataRead>(reader: &mut R) -> Result<Textures, ReadError> {
        get_chunk_header(reader, Self::TYPE_ID)?;
        let num_textures = reader.read_u32::<LittleEndian>()?;
        trace!("{} textures", num_textures);
        // Read texture entry offsets
        for _ in 0..num_textures {
            // For now just discard them
            reader.read_u32::<LittleEndian>()?;
        }
        let mut textures = Vec::new();
        let mut finished_offset = 0;
        for i in 0..num_textures {
            let unk = reader.read_u32::<LittleEndian>()?;
            let offset = reader.read_u32::<LittleEndian>()?;
            let reader_offset = reader.tell()?;
            reader.seek(io::SeekFrom::Start(offset as u64))?;
            assert!(offset % IMAGE_DATA_ALIGNMENT == 0,
                    "Image data is assumed to be aligned on {} byte boundaries",
                    IMAGE_DATA_ALIGNMENT);
            trace!("Reading image data {} @ {}", i, offset);
            let png = read_png(reader)?;
            finished_offset = reader.tell()?;
            reader.seek(io::SeekFrom::Start(reader_offset))?;
            textures.push(Texture {
                              unknown: unk,
                              png_data: png,
                          });
        }
        // Looks like chunks don't use the same alignment as image data.
        // Or maybe they don't use alignment at all?
        // Why the zero padding then?
        while finished_offset % 16 != 0 {
            finished_offset += 1;
        }
        reader.seek(io::SeekFrom::Start(finished_offset))?;
        Ok(Textures { textures: textures })
    }
    fn write_content<W: GameDataWrite>(&self, writer: &mut W) -> io::Result<()> {
        writer.write_u32::<LittleEndian>(self.textures.len() as u32)?;
        let start_offset = writer.tell()?;
        let num_textures = self.textures.len() as u32;
        let offset_table_len = num_textures * 4;
        let fileinfo_table_len = num_textures * 8;
        let mut texture_data_offsets: Vec<u32> = Vec::with_capacity(num_textures as usize);
        let offset_table_offset = writer.tell()?;
        // Skip offset table and fileinfos and write image data first
        writer.seek(io::SeekFrom::Current((offset_table_len + fileinfo_table_len) as i64))?;
        for (i, t) in self.textures.iter().enumerate() {
            let mut offset = writer.tell()?;
            while offset % IMAGE_DATA_ALIGNMENT as u64 != 0 {
                offset += 1;
            }
            writer.seek(io::SeekFrom::Start(offset))?;
            trace!("Writing image data {} @ {}", i, offset);
            writer.write_all(&t.png_data)?;
            texture_data_offsets.push(offset as u32);
        }
        let finished_offset = writer.tell()?;
        // Go back and write offset table
        writer.seek(io::SeekFrom::Start(offset_table_offset))?;
        for i in 0..num_textures {
            writer.write_u32::<LittleEndian>(start_offset as u32 + offset_table_len + (i * 8))?;
        }
        // Write fileinfos
        for (t, &off) in self.textures.iter().zip(texture_data_offsets.iter()) {
            writer.write_u32::<LittleEndian>(t.unknown)?;
            writer.write_u32::<LittleEndian>(off)?;
        }
        writer.seek(io::SeekFrom::Start(finished_offset))?;
        trace!("Finished at {}", finished_offset);
        let mut offset = finished_offset;
        // Write zero padding
        while offset % 16 != 0 {
            offset += 1;
            writer.write_u8(0)?;
        }
        Ok(())
    }
}

fn png_length<R: GameDataRead>(reader: &mut R) -> Result<u32, ReadError> {
    const MAGIC: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
    let reader_start = reader.seek(io::SeekFrom::Current(0))?;
    let mut buf = [0u8; 8];
    reader.read_exact(&mut buf)?;
    assert_eq!(buf, MAGIC);
    loop {
        let length = reader.read_u32::<BigEndian>()?;
        let mut chunk_type = [0u8; 4];
        reader.read_exact(&mut chunk_type)?;
        let crc_len = 4;
        reader.seek(io::SeekFrom::Current(length as i64 + crc_len))?;
        if &chunk_type == b"IEND" {
            break;
        }
    }
    let reader_end = reader.seek(io::SeekFrom::Current(0))?;
    let length = reader_end - reader_start;
    reader.seek(io::SeekFrom::Start(reader_start))?;
    trace!("Length of PNG is {}", length);
    Ok(length as u32)
}

fn read_png<R: GameDataRead>(reader: &mut R) -> Result<Vec<u8>, ReadError> {
    let length = png_length(reader)?;
    let buf = read_into_byte_vec(reader, length as usize)?;
    Ok(buf)
}
<commit_msg>Use assert_eq! instead of assert!<commit_after>use std::io;
use byteorder::{ReadBytesExt, WriteBytesExt, LittleEndian, BigEndian};
use {GameDataRead, GameDataWrite, Texture, Textures};
use gamedata_io::{Chunk, get_chunk_header, ReadError, read_into_byte_vec, Tell};

const IMAGE_DATA_ALIGNMENT: u32 = 128;

impl<'a> Chunk<'a> for Textures {
    const TYPE_ID: &'static [u8; 4] = b"TXTR";
    type ReadOutput = Self;
    fn read<R: GameDataRead>(reader: &mut R) -> Result<Textures, ReadError> {
        get_chunk_header(reader, Self::TYPE_ID)?;
        let num_textures = reader.read_u32::<LittleEndian>()?;
        trace!("{} textures", num_textures);
        // Read texture entry offsets
        for _ in 0..num_textures {
            // For now just discard them
            reader.read_u32::<LittleEndian>()?;
        }
        let mut textures = Vec::new();
        let mut finished_offset = 0;
        for i in 0..num_textures {
            let unk = reader.read_u32::<LittleEndian>()?;
            let offset = reader.read_u32::<LittleEndian>()?;
            let reader_offset = reader.tell()?;
            reader.seek(io::SeekFrom::Start(offset as u64))?;
            assert_eq!(offset % IMAGE_DATA_ALIGNMENT,
                       0,
                       "Image data is assumed to be aligned on {} byte boundaries",
                       IMAGE_DATA_ALIGNMENT);
            trace!("Reading image data {} @ {}", i, offset);
            let png = read_png(reader)?;
            finished_offset = reader.tell()?;
            reader.seek(io::SeekFrom::Start(reader_offset))?;
            textures.push(Texture {
                              unknown: unk,
                              png_data: png,
                          });
        }
        // Looks like chunks don't use the same alignment as image data.
        // Or maybe they don't use alignment at all?
        // Why the zero padding then?
        while finished_offset % 16 != 0 {
            finished_offset += 1;
        }
        reader.seek(io::SeekFrom::Start(finished_offset))?;
        Ok(Textures { textures: textures })
    }
    fn write_content<W: GameDataWrite>(&self, writer: &mut W) -> io::Result<()> {
        writer.write_u32::<LittleEndian>(self.textures.len() as u32)?;
        let start_offset = writer.tell()?;
        let num_textures = self.textures.len() as u32;
        let offset_table_len = num_textures * 4;
        let fileinfo_table_len = num_textures * 8;
        let mut texture_data_offsets: Vec<u32> = Vec::with_capacity(num_textures as usize);
        let offset_table_offset = writer.tell()?;
        // Skip offset table and fileinfos and write image data first
        writer.seek(io::SeekFrom::Current((offset_table_len + fileinfo_table_len) as i64))?;
        for (i, t) in self.textures.iter().enumerate() {
            let mut offset = writer.tell()?;
            while offset % IMAGE_DATA_ALIGNMENT as u64 != 0 {
                offset += 1;
            }
            writer.seek(io::SeekFrom::Start(offset))?;
            trace!("Writing image data {} @ {}", i, offset);
            writer.write_all(&t.png_data)?;
            texture_data_offsets.push(offset as u32);
        }
        let finished_offset = writer.tell()?;
        // Go back and write offset table
        writer.seek(io::SeekFrom::Start(offset_table_offset))?;
        for i in 0..num_textures {
            writer.write_u32::<LittleEndian>(start_offset as u32 + offset_table_len + (i * 8))?;
        }
        // Write fileinfos
        for (t, &off) in self.textures.iter().zip(texture_data_offsets.iter()) {
            writer.write_u32::<LittleEndian>(t.unknown)?;
            writer.write_u32::<LittleEndian>(off)?;
        }
        writer.seek(io::SeekFrom::Start(finished_offset))?;
        trace!("Finished at {}", finished_offset);
        let mut offset = finished_offset;
        // Write zero padding
        while offset % 16 != 0 {
            offset += 1;
            writer.write_u8(0)?;
        }
        Ok(())
    }
}

fn png_length<R: GameDataRead>(reader: &mut R) -> Result<u32, ReadError> {
    const MAGIC: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
    let reader_start = reader.seek(io::SeekFrom::Current(0))?;
    let mut buf = [0u8; 8];
    reader.read_exact(&mut buf)?;
    assert_eq!(buf, MAGIC);
    loop {
        let length = reader.read_u32::<BigEndian>()?;
        let mut chunk_type = [0u8; 4];
        reader.read_exact(&mut chunk_type)?;
        let crc_len = 4;
        reader.seek(io::SeekFrom::Current(length as i64 + crc_len))?;
        if &chunk_type == b"IEND" {
            break;
        }
    }
    let reader_end = reader.seek(io::SeekFrom::Current(0))?;
    let length = reader_end - reader_start;
    reader.seek(io::SeekFrom::Start(reader_start))?;
    trace!("Length of PNG is {}", length);
    Ok(length as u32)
}

fn read_png<R: GameDataRead>(reader: &mut R) -> Result<Vec<u8>, ReadError> {
    let length = png_length(reader)?;
    let buf = read_into_byte_vec(reader, length as usize)?;
    Ok(buf)
}
<|endoftext|>


<commit_before>use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use generic_array::{ArrayLength, GenericArray};
use std::io::{Result, Error, ErrorKind, Read, Write};
use std::mem::size_of;
use std::default::Default;

/// A message that can be encoded and decoded according to the LCM protocol.
pub trait Message {
    /// Encodes a message into a buffer, with the message hash at the beginning.
    fn encode_with_hash(&self) -> Result<Vec<u8>> where Self: Sized {
        let hash = Self::hash();
        let size = hash.size() + self.size();
        let mut buffer = Vec::with_capacity(size);
        hash.encode(&mut buffer)?;
        self.encode(&mut buffer)?;
        Ok(buffer)
    }

    /// Decodes a message from a buffer,
    /// and also checks that the hash at the beginning is correct.
    fn decode_with_hash(mut buffer: &mut Read) -> Result<Self>
        where Self: Sized
    {
        let hash: u64 = Message::decode(&mut buffer)?;
        if hash != Self::hash() {
            return Err(Error::new(ErrorKind::Other, "Invalid hash"));
        }
        Message::decode(buffer)
    }

    /// Returns the message hash for this type.
    /// Returns `0` for all primitive types.
    /// Generated `Lcm` types should implement this function.
    fn hash() -> u64 where Self: Sized {
        0
    }

    /// Encodes a message into a buffer.
    /// `Lcm` uses a `Vec<u8>` with its capacity set to the value returned by [`size()`].
    fn encode(&self, buffer: &mut Write) -> Result<()>;

    /// Decodes a message from a buffer.
    fn decode(buffer: &mut Read) -> Result<Self> where Self: Sized;

    /// Returns the number of bytes this message is expected to take when encoded.
    fn size(&self) -> usize;
}

impl Message for bool {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        let value: i8 = match *self {
            true => 1,
            false => 0,
        };
        value.encode(buffer)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        let value = buffer.read_i8()?;
        *self = match value {
            0 => false,
            1 => true,
            _ => {
                return Err(Error::new(ErrorKind::InvalidData,
                                      "Booleans should be encoded as 0 or 1"))
            }
        };
        Ok(())
    }

    fn size(&self) -> usize {
        size_of::<i8>()
    }
}

impl Message for u8 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_u8(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_u8()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for i8 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_i8(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_i8()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for i16 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_i16::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_i16::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for i32 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_i32::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_i32::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for i64 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_i64::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_i64::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for f32 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_f32::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_f32::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for f64 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_f64::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_f64::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for String {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        let len: i32 = self.len() as i32 + 1;
        len.encode(buffer)?;
        for &b in self.as_bytes() {
            b.encode(buffer)?;
        }
        (0 as u8).encode(buffer)?;
        Ok(())
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        let len = buffer.read_i32::<BigEndian>()? - 1;
        let mut buf = Vec::with_capacity(len as usize);
        buf.decode(buffer)?;
        *self = String::from_utf8(buf).map_err(|e| Error::new(ErrorKind::Other, e))?;
        match buffer.read_u8() {
            Ok(0) => Ok(()),
            Ok(_) => Err(Error::new(ErrorKind::InvalidData, "Expected null terminator")),
            Err(e) => Err(e),
        }
    }

    fn size(&self) -> usize {
        size_of::<i32>() + self.len() + 1
    }
}

impl<T> Message for Vec<T>
    where T: Message + Default
{
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        for val in self.iter() {
            val.encode(buffer)?;
        }
        Ok(())
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        while self.len() != self.capacity() {
            let mut element = T::default();
            element.decode(buffer)?;
            self.push(element);
        }
        Ok(())
    }

    fn size(&self) -> usize {
        self.iter().map(Message::size).sum()
    }
}

impl<T, N> Message for GenericArray<T, N>
    where T: Message + Default,
          N: ArrayLength<T>
{
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        for val in self.iter() {
            val.encode(buffer)?;
        }
        Ok(())
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        for i in 0..self.len() {
            let mut element = T::default();
            element.decode(buffer)?;
            self[i] = element;
        }
        Ok(())
    }

    fn size(&self) -> usize {
        self.iter().map(Message::size).sum()
    }
}
<commit_msg>Remove impl Message for Vec and GenericArray<commit_after>use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use generic_array::{ArrayLength, GenericArray};
use std::io::{Result, Error, ErrorKind, Read, Write};
use std::mem::size_of;
use std::default::Default;

/// A message that can be encoded and decoded according to the LCM protocol.
pub trait Message {
    /// Encodes a message into a buffer, with the message hash at the beginning.
    fn encode_with_hash(&self) -> Result<Vec<u8>> where Self: Sized {
        let hash = Self::hash();
        let size = hash.size() + self.size();
        let mut buffer = Vec::with_capacity(size);
        hash.encode(&mut buffer)?;
        self.encode(&mut buffer)?;
        Ok(buffer)
    }

    /// Decodes a message from a buffer,
    /// and also checks that the hash at the beginning is correct.
    fn decode_with_hash(mut buffer: &mut Read) -> Result<Self>
        where Self: Sized
    {
        let hash: u64 = Message::decode(&mut buffer)?;
        if hash != Self::hash() {
            return Err(Error::new(ErrorKind::Other, "Invalid hash"));
        }
        Message::decode(buffer)
    }

    /// Returns the message hash for this type.
    /// Returns `0` for all primitive types.
    /// Generated `Lcm` types should implement this function.
    fn hash() -> u64 where Self: Sized {
        0
    }

    /// Encodes a message into a buffer.
    /// `Lcm` uses a `Vec<u8>` with its capacity set to the value returned by [`size()`].
    fn encode(&self, buffer: &mut Write) -> Result<()>;

    /// Decodes a message from a buffer.
    fn decode(buffer: &mut Read) -> Result<Self> where Self: Sized;

    /// Returns the number of bytes this message is expected to take when encoded.
    fn size(&self) -> usize;
}

impl Message for bool {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        let value: i8 = match *self {
            true => 1,
            false => 0,
        };
        value.encode(buffer)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        let value = buffer.read_i8()?;
        *self = match value {
            0 => false,
            1 => true,
            _ => {
                return Err(Error::new(ErrorKind::InvalidData,
                                      "Booleans should be encoded as 0 or 1"))
            }
        };
        Ok(())
    }

    fn size(&self) -> usize {
        size_of::<i8>()
    }
}

impl Message for u8 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_u8(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_u8()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for i8 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_i8(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_i8()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for i16 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_i16::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_i16::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for i32 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_i32::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_i32::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for i64 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_i64::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_i64::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for f32 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_f32::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_f32::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for f64 {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        buffer.write_f64::<BigEndian>(*self)
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        Ok(*self = buffer.read_f64::<BigEndian>()?)
    }

    fn size(&self) -> usize {
        size_of::<Self>()
    }
}

impl Message for String {
    fn encode(&self, buffer: &mut Write) -> Result<()> {
        let len: i32 = self.len() as i32 + 1;
        len.encode(buffer)?;
        for &b in self.as_bytes() {
            b.encode(buffer)?;
        }
        (0 as u8).encode(buffer)?;
        Ok(())
    }

    fn decode(&mut self, buffer: &mut Read) -> Result<()> {
        let len = buffer.read_i32::<BigEndian>()? - 1;
        let mut buf = Vec::with_capacity(len as usize);
        buf.decode(buffer)?;
        *self = String::from_utf8(buf).map_err(|e| Error::new(ErrorKind::Other, e))?;
        match buffer.read_u8() {
            Ok(0) => Ok(()),
            Ok(_) => Err(Error::new(ErrorKind::InvalidData, "Expected null terminator")),
            Err(e) => Err(e),
        }
    }

    fn size(&self) -> usize {
        size_of::<i32>() + self.len() + 1
    }
}
<|endoftext|>


<commit_before>use self::build_tables::build_tables;
use self::parse_grammar::parse_grammar;
use self::prepare_grammar::prepare_grammar;
use self::render::render_c_code;
use crate::error::Result;
use regex::{Regex, RegexBuilder};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};

mod build_tables;
mod grammars;
mod nfa;
mod parse_grammar;
mod prepare_grammar;
mod properties;
mod render;
mod rules;
mod tables;

lazy_static! {
    static ref JSON_COMMENT_REGEX: Regex = RegexBuilder::new("^\\s*//.*")
        .multi_line(true)
        .build()
        .unwrap();
}

pub fn generate_parser_in_directory(
    repo_path: &PathBuf,
    minimize: bool,
    state_ids_to_log: Vec<usize>,
    properties_only: bool,
) -> Result<()> {
    if !properties_only {
        let grammar_json = load_js_grammar_file(&repo_path.join("grammar.js"));
        let c_code =
            generate_parser_for_grammar_with_opts(&grammar_json, minimize, state_ids_to_log)?;
        fs::write(repo_path.join("src").join("parser.c"), c_code)?;
    }
    properties::generate_property_sheets(repo_path)?;
    Ok(())
}

#[cfg(test)]
pub fn generate_parser_for_grammar(grammar_json: &String) -> Result<String> {
    let grammar_json = JSON_COMMENT_REGEX.replace_all(grammar_json, "\n");
    generate_parser_for_grammar_with_opts(&grammar_json, true, Vec::new())
}

fn generate_parser_for_grammar_with_opts(
    grammar_json: &str,
    minimize: bool,
    state_ids_to_log: Vec<usize>,
) -> Result<String> {
    let input_grammar = parse_grammar(grammar_json)?;
    let (syntax_grammar, lexical_grammar, inlines, simple_aliases) =
        prepare_grammar(&input_grammar)?;
    let (parse_table, main_lex_table, keyword_lex_table, keyword_capture_token) = build_tables(
        &syntax_grammar,
        &lexical_grammar,
        &simple_aliases,
        &inlines,
        minimize,
        state_ids_to_log,
    )?;
    Ok(render_c_code(
        &input_grammar.name,
        parse_table,
        main_lex_table,
        keyword_lex_table,
        keyword_capture_token,
        syntax_grammar,
        lexical_grammar,
        simple_aliases,
    ))
}

fn load_js_grammar_file(grammar_path: &PathBuf) -> String {
    let mut node_process = Command::new("node")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("Failed to run `node`");

    let js_prelude = include_str!("./dsl.js");
    let mut node_stdin = node_process
        .stdin
        .take()
        .expect("Failed to open stdin for node");
    write!(
        node_stdin,
        "{}\nconsole.log(JSON.stringify(require(\"{}\"), null, 2));\n",
        js_prelude,
        grammar_path.to_str().unwrap()
    )
    .expect("Failed to write to node's stdin");
    drop(node_stdin);
    let output = node_process
        .wait_with_output()
        .expect("Failed to read output from node");
    match output.status.code() {
        None => panic!("Node process was killed"),
        Some(0) => {}
        Some(code) => panic!(format!("Node process exited with status {}", code)),
    }

    String::from_utf8(output.stdout).expect("Got invalid UTF8 from node")
}
<commit_msg>Ensure 'src' directory exists before writing src/parser.c<commit_after>use self::build_tables::build_tables;
use self::parse_grammar::parse_grammar;
use self::prepare_grammar::prepare_grammar;
use self::render::render_c_code;
use crate::error::Result;
use regex::{Regex, RegexBuilder};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};

mod build_tables;
mod grammars;
mod nfa;
mod parse_grammar;
mod prepare_grammar;
mod properties;
mod render;
mod rules;
mod tables;

lazy_static! {
    static ref JSON_COMMENT_REGEX: Regex = RegexBuilder::new("^\\s*//.*")
        .multi_line(true)
        .build()
        .unwrap();
}

pub fn generate_parser_in_directory(
    repo_path: &PathBuf,
    minimize: bool,
    state_ids_to_log: Vec<usize>,
    properties_only: bool,
) -> Result<()> {
    if !properties_only {
        let grammar_json = load_js_grammar_file(&repo_path.join("grammar.js"));
        let c_code =
            generate_parser_for_grammar_with_opts(&grammar_json, minimize, state_ids_to_log)?;
        fs::create_dir_all("src")?;
        fs::write(repo_path.join("src").join("parser.c"), c_code)?;
    }
    properties::generate_property_sheets(repo_path)?;
    Ok(())
}

#[cfg(test)]
pub fn generate_parser_for_grammar(grammar_json: &String) -> Result<String> {
    let grammar_json = JSON_COMMENT_REGEX.replace_all(grammar_json, "\n");
    generate_parser_for_grammar_with_opts(&grammar_json, true, Vec::new())
}

fn generate_parser_for_grammar_with_opts(
    grammar_json: &str,
    minimize: bool,
    state_ids_to_log: Vec<usize>,
) -> Result<String> {
    let input_grammar = parse_grammar(grammar_json)?;
    let (syntax_grammar, lexical_grammar, inlines, simple_aliases) =
        prepare_grammar(&input_grammar)?;
    let (parse_table, main_lex_table, keyword_lex_table, keyword_capture_token) = build_tables(
        &syntax_grammar,
        &lexical_grammar,
        &simple_aliases,
        &inlines,
        minimize,
        state_ids_to_log,
    )?;
    Ok(render_c_code(
        &input_grammar.name,
        parse_table,
        main_lex_table,
        keyword_lex_table,
        keyword_capture_token,
        syntax_grammar,
        lexical_grammar,
        simple_aliases,
    ))
}

fn load_js_grammar_file(grammar_path: &PathBuf) -> String {
    let mut node_process = Command::new("node")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .expect("Failed to run `node`");

    let js_prelude = include_str!("./dsl.js");
    let mut node_stdin = node_process
        .stdin
        .take()
        .expect("Failed to open stdin for node");
    write!(
        node_stdin,
        "{}\nconsole.log(JSON.stringify(require(\"{}\"), null, 2));\n",
        js_prelude,
        grammar_path.to_str().unwrap()
    )
    .expect("Failed to write to node's stdin");
    drop(node_stdin);
    let output = node_process
        .wait_with_output()
        .expect("Failed to read output from node");
    match output.status.code() {
        None => panic!("Node process was killed"),
        Some(0) => {}
        Some(code) => panic!(format!("Node process exited with status {}", code)),
    }

    String::from_utf8(output.stdout).expect("Got invalid UTF8 from node")
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;
use std::string::ToString;

use comfy_table::presets::UTF8_HORIZONTAL_BORDERS_ONLY;
use comfy_table::*;

use pueue_lib::settings::Settings;
use pueue_lib::state::State;
use pueue_lib::task::{Task, TaskResult, TaskStatus};

use super::{colors::Colors, helper::*};
use crate::cli::SubCommand;

/// Print the current state of the daemon in a nicely formatted table.
pub fn print_state(state: State, cli_command: &SubCommand, colors: &Colors, settings: &Settings) {
    let (json, group_only) = match cli_command {
        SubCommand::Status { json, group } => (*json, group.clone()),
        _ => panic!(
            "Got wrong Subcommand {:?} in print_state. This shouldn't happen",
            cli_command
        ),
    };

    // If the json flag is specified, print the state as json and exit.
    if json {
        println!("{}", serde_json::to_string(&state).unwrap());
        return;
    }

    // Sort all tasks by their respective group;
    let sorted_tasks = sort_tasks_by_group(&state.tasks);

    if let Some(group) = group_only {
        print_single_group(state, settings, colors, sorted_tasks, group);
        return;
    }

    print_all_groups(state, settings, colors, sorted_tasks);
}

fn print_single_group(
    state: State,
    settings: &Settings,
    colors: &Colors,
    mut sorted_tasks: BTreeMap<String, BTreeMap<usize, Task>>,
    group: String,
) {
    // Only a single group is requested. Print that group and return.
    let tasks = sorted_tasks.entry(group.clone()).or_default();
    let headline = get_group_headline(
        &group,
        &state.groups.get(&group).unwrap(),
        *state.settings.daemon.groups.get(&group).unwrap(),
        colors,
    );
    println!("{}", headline);

    // Show a message if the requested group doesn't have any tasks.
    if tasks.is_empty() {
        println!(
            "Task list is empty. Add tasks with `pueue add -g {} -- [cmd]`",
            group
        );
        return;
    }
    print_table(&tasks, colors, settings);

    return;
}

fn print_all_groups(
    state: State,
    settings: &Settings,
    colors: &Colors,
    sorted_tasks: BTreeMap<String, BTreeMap<usize, Task>>,
) {
    // Early exit and hint if there are no tasks in the queue
    // Print the state of the default group anyway, since this is information one wants to
    // see most of the time anyway.
    if state.tasks.is_empty() {
        let headline = get_group_headline(
            &"default",
            &state.groups.get("default").unwrap(),
            *state.settings.daemon.groups.get("default").unwrap(),
            colors,
        );
        println!("{}\n", headline);
        println!("Task list is empty. Add tasks with `pueue add -- [cmd]`");
        return;
    }

    // Always print the default queue at the very top, if no specific group is requested.
    if sorted_tasks.get("default").is_some() {
        let tasks = sorted_tasks.get("default").unwrap();
        let headline = get_group_headline(
            &"default",
            &state.groups.get("default").unwrap(),
            *state.settings.daemon.groups.get("default").unwrap(),
            colors,
        );
        println!("{}", headline);
        print_table(&tasks, colors, settings);

        // Add a newline if there are further groups to be printed
        if sorted_tasks.len() > 1 {
            println!();
        }
    }

    // Print a table for every other group that has any tasks
    let mut sorted_iter = sorted_tasks.iter().peekable();
    while let Some((group, tasks)) = sorted_iter.next() {
        // We always want to print the default group at the very top.
        // That's why we print it before this loop and skip it in here.
        if group.eq("default") {
            continue;
        }

        let headline = get_group_headline(
            &group,
            &state.groups.get(group).unwrap(),
            *state.settings.daemon.groups.get(group).unwrap(),
            colors,
        );
        println!("{}", headline);
        print_table(&tasks, colors, settings);

        // Add a newline between groups
        if sorted_iter.peek().is_some() {
            println!();
        }
    }
}

/// Print some tasks into a nicely formatted table
fn print_table(tasks: &BTreeMap<usize, Task>, colors: &Colors, settings: &Settings) {
    let (has_delayed_tasks, has_dependencies, has_labels) = has_special_columns(tasks);

    // Create table header row
    let mut headers = vec![Cell::new("Index"), Cell::new("Status")];
    if has_delayed_tasks {
        headers.push(Cell::new("Enqueue At"));
    }
    if has_dependencies {
        headers.push(Cell::new("Deps"));
    }

    headers.push(Cell::new("Exitcode"));

    if has_labels {
        headers.push(Cell::new("Label"));
    }

    headers.append(&mut vec![
        Cell::new("Command"),
        Cell::new("Path"),
        Cell::new("Start"),
        Cell::new("End"),
    ]);

    // Initialize comfy table.
    let mut table = Table::new();
    table
        .set_content_arrangement(ContentArrangement::Dynamic)
        .load_preset(UTF8_HORIZONTAL_BORDERS_ONLY)
        .set_header(headers);

    // Add rows one by one.
    for (id, task) in tasks {
        let mut row = Row::new();
        if let Some(height) = settings.client.max_status_lines {
            row.max_height(height);
        }
        row.add_cell(Cell::new(&id.to_string()));

        // Determine the human readable task status representation and the respective color.
        let status_string = task.status.to_string();
        let (status_text, color) = match task.status {
            TaskStatus::Running => (status_string, colors.green()),
            TaskStatus::Paused | TaskStatus::Locked => (status_string, colors.white()),
            TaskStatus::Done => match &task.result {
                Some(TaskResult::Success) => (TaskResult::Success.to_string(), colors.green()),
                Some(TaskResult::DependencyFailed) => {
                    ("Dependency failed".to_string(), colors.red())
                }
                Some(TaskResult::FailedToSpawn(_)) => ("Failed to spawn".to_string(), colors.red()),
                Some(result) => (result.to_string(), colors.red()),
                None => panic!("Got a 'Done' task without a task result. Please report this bug."),
            },
            _ => (status_string, colors.yellow()),
        };
        row.add_cell(Cell::new(status_text).fg(color));

        if has_delayed_tasks {
            if let Some(enqueue_at) = task.enqueue_at {
                row.add_cell(Cell::new(enqueue_at.format("%Y-%m-%d\n%H:%M:%S")));
            } else {
                row.add_cell(Cell::new(""));
            }
        }

        if has_dependencies {
            let text = task
                .dependencies
                .iter()
                .map(|id| id.to_string())
                .collect::<Vec<String>>()
                .join(", ");
            row.add_cell(Cell::new(text));
        }

        // Match the color of the exit code.
        // If the exit_code is none, it has been killed by the task handler.
        let exit_code_cell = match task.result {
            Some(TaskResult::Success) => Cell::new("0").fg(colors.green()),
            Some(TaskResult::Failed(code)) => Cell::new(&code.to_string()).fg(colors.red()),
            _ => Cell::new(""),
        };
        row.add_cell(exit_code_cell);
        if has_labels {
            if let Some(label) = &task.label {
                row.add_cell(label.to_cell());
            } else {
                row.add_cell(Cell::new(""));
            }
        }

        // Add command and path.
        if settings.client.show_expanded_aliases {
            row.add_cell(Cell::new(&task.command));
        } else {
            row.add_cell(Cell::new(&task.original_command));
        }
        row.add_cell(Cell::new(&task.path));

        // Add start time, if already set.
        if let Some(start) = task.start {
            let formatted = start.format("%H:%M").to_string();
            row.add_cell(Cell::new(&formatted));
        } else {
            row.add_cell(Cell::new(""));
        }

        // Add finish time, if already set.
        if let Some(end) = task.end {
            let formatted = end.format("%H:%M").to_string();
            row.add_cell(Cell::new(&formatted));
        } else {
            row.add_cell(Cell::new(""));
        }

        table.add_row(row);
    }

    // Print the table.
    println!("{}", table);
}
<commit_msg>Remove unnecessary return<commit_after>use std::collections::BTreeMap;
use std::string::ToString;

use comfy_table::presets::UTF8_HORIZONTAL_BORDERS_ONLY;
use comfy_table::*;

use pueue_lib::settings::Settings;
use pueue_lib::state::State;
use pueue_lib::task::{Task, TaskResult, TaskStatus};

use super::{colors::Colors, helper::*};
use crate::cli::SubCommand;

/// Print the current state of the daemon in a nicely formatted table.
pub fn print_state(state: State, cli_command: &SubCommand, colors: &Colors, settings: &Settings) {
    let (json, group_only) = match cli_command {
        SubCommand::Status { json, group } => (*json, group.clone()),
        _ => panic!(
            "Got wrong Subcommand {:?} in print_state. This shouldn't happen",
            cli_command
        ),
    };

    // If the json flag is specified, print the state as json and exit.
    if json {
        println!("{}", serde_json::to_string(&state).unwrap());
        return;
    }

    // Sort all tasks by their respective group;
    let sorted_tasks = sort_tasks_by_group(&state.tasks);

    if let Some(group) = group_only {
        print_single_group(state, settings, colors, sorted_tasks, group);
        return;
    }

    print_all_groups(state, settings, colors, sorted_tasks);
}

fn print_single_group(
    state: State,
    settings: &Settings,
    colors: &Colors,
    mut sorted_tasks: BTreeMap<String, BTreeMap<usize, Task>>,
    group: String,
) {
    // Only a single group is requested. Print that group and return.
    let tasks = sorted_tasks.entry(group.clone()).or_default();
    let headline = get_group_headline(
        &group,
        &state.groups.get(&group).unwrap(),
        *state.settings.daemon.groups.get(&group).unwrap(),
        colors,
    );
    println!("{}", headline);

    // Show a message if the requested group doesn't have any tasks.
    if tasks.is_empty() {
        println!(
            "Task list is empty. Add tasks with `pueue add -g {} -- [cmd]`",
            group
        );
        return;
    }
    print_table(&tasks, colors, settings);
}

fn print_all_groups(
    state: State,
    settings: &Settings,
    colors: &Colors,
    sorted_tasks: BTreeMap<String, BTreeMap<usize, Task>>,
) {
    // Early exit and hint if there are no tasks in the queue
    // Print the state of the default group anyway, since this is information one wants to
    // see most of the time anyway.
    if state.tasks.is_empty() {
        let headline = get_group_headline(
            &"default",
            &state.groups.get("default").unwrap(),
            *state.settings.daemon.groups.get("default").unwrap(),
            colors,
        );
        println!("{}\n", headline);
        println!("Task list is empty. Add tasks with `pueue add -- [cmd]`");
        return;
    }

    // Always print the default queue at the very top, if no specific group is requested.
    if sorted_tasks.get("default").is_some() {
        let tasks = sorted_tasks.get("default").unwrap();
        let headline = get_group_headline(
            &"default",
            &state.groups.get("default").unwrap(),
            *state.settings.daemon.groups.get("default").unwrap(),
            colors,
        );
        println!("{}", headline);
        print_table(&tasks, colors, settings);

        // Add a newline if there are further groups to be printed
        if sorted_tasks.len() > 1 {
            println!();
        }
    }

    // Print a table for every other group that has any tasks
    let mut sorted_iter = sorted_tasks.iter().peekable();
    while let Some((group, tasks)) = sorted_iter.next() {
        // We always want to print the default group at the very top.
        // That's why we print it before this loop and skip it in here.
        if group.eq("default") {
            continue;
        }

        let headline = get_group_headline(
            &group,
            &state.groups.get(group).unwrap(),
            *state.settings.daemon.groups.get(group).unwrap(),
            colors,
        );
        println!("{}", headline);
        print_table(&tasks, colors, settings);

        // Add a newline between groups
        if sorted_iter.peek().is_some() {
            println!();
        }
    }
}

/// Print some tasks into a nicely formatted table
fn print_table(tasks: &BTreeMap<usize, Task>, colors: &Colors, settings: &Settings) {
    let (has_delayed_tasks, has_dependencies, has_labels) = has_special_columns(tasks);

    // Create table header row
    let mut headers = vec![Cell::new("Index"), Cell::new("Status")];
    if has_delayed_tasks {
        headers.push(Cell::new("Enqueue At"));
    }
    if has_dependencies {
        headers.push(Cell::new("Deps"));
    }

    headers.push(Cell::new("Exitcode"));

    if has_labels {
        headers.push(Cell::new("Label"));
    }

    headers.append(&mut vec![
        Cell::new("Command"),
        Cell::new("Path"),
        Cell::new("Start"),
        Cell::new("End"),
    ]);

    // Initialize comfy table.
    let mut table = Table::new();
    table
        .set_content_arrangement(ContentArrangement::Dynamic)
        .load_preset(UTF8_HORIZONTAL_BORDERS_ONLY)
        .set_header(headers);

    // Add rows one by one.
    for (id, task) in tasks {
        let mut row = Row::new();
        if let Some(height) = settings.client.max_status_lines {
            row.max_height(height);
        }
        row.add_cell(Cell::new(&id.to_string()));

        // Determine the human readable task status representation and the respective color.
        let status_string = task.status.to_string();
        let (status_text, color) = match task.status {
            TaskStatus::Running => (status_string, colors.green()),
            TaskStatus::Paused | TaskStatus::Locked => (status_string, colors.white()),
            TaskStatus::Done => match &task.result {
                Some(TaskResult::Success) => (TaskResult::Success.to_string(), colors.green()),
                Some(TaskResult::DependencyFailed) => {
                    ("Dependency failed".to_string(), colors.red())
                }
                Some(TaskResult::FailedToSpawn(_)) => ("Failed to spawn".to_string(), colors.red()),
                Some(result) => (result.to_string(), colors.red()),
                None => panic!("Got a 'Done' task without a task result. Please report this bug."),
            },
            _ => (status_string, colors.yellow()),
        };
        row.add_cell(Cell::new(status_text).fg(color));

        if has_delayed_tasks {
            if let Some(enqueue_at) = task.enqueue_at {
                row.add_cell(Cell::new(enqueue_at.format("%Y-%m-%d\n%H:%M:%S")));
            } else {
                row.add_cell(Cell::new(""));
            }
        }

        if has_dependencies {
            let text = task
                .dependencies
                .iter()
                .map(|id| id.to_string())
                .collect::<Vec<String>>()
                .join(", ");
            row.add_cell(Cell::new(text));
        }

        // Match the color of the exit code.
        // If the exit_code is none, it has been killed by the task handler.
        let exit_code_cell = match task.result {
            Some(TaskResult::Success) => Cell::new("0").fg(colors.green()),
            Some(TaskResult::Failed(code)) => Cell::new(&code.to_string()).fg(colors.red()),
            _ => Cell::new(""),
        };
        row.add_cell(exit_code_cell);
        if has_labels {
            if let Some(label) = &task.label {
                row.add_cell(label.to_cell());
            } else {
                row.add_cell(Cell::new(""));
            }
        }

        // Add command and path.
        if settings.client.show_expanded_aliases {
            row.add_cell(Cell::new(&task.command));
        } else {
            row.add_cell(Cell::new(&task.original_command));
        }
        row.add_cell(Cell::new(&task.path));

        // Add start time, if already set.
        if let Some(start) = task.start {
            let formatted = start.format("%H:%M").to_string();
            row.add_cell(Cell::new(&formatted));
        } else {
            row.add_cell(Cell::new(""));
        }

        // Add finish time, if already set.
        if let Some(end) = task.end {
            let formatted = end.format("%H:%M").to_string();
            row.add_cell(Cell::new(&formatted));
        } else {
            row.add_cell(Cell::new(""));
        }

        table.add_row(row);
    }

    // Print the table.
    println!("{}", table);
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate collect_mac;
extern crate env_logger;
#[macro_use]
extern crate pretty_assertions;

extern crate gluon_base as base;
extern crate gluon_parser as parser;
extern crate gluon_check as check;

use base::pos::Spanned;
use base::types::Type;

mod support;

macro_rules! assert_err {
    ($e: expr, $($id: pat),+) => {{
        use check::typecheck::TypeError::*;
        #[allow(unused_imports)]
        use check::unify::Error::{TypeMismatch, Occurs, Other};
        #[allow(unused_imports)]
        use check::unify_type::TypeError::FieldMismatch;

        match $e {
            Ok(x) => assert!(false, "Expected error, got {}", x),
            Err(err) => {
                let errors = err.errors();
                let mut iter = (&errors).into_iter();
                $(
                match iter.next() {
                    Some(&Spanned { value: $id, .. }) => (),
                    _ => assert!(false, "Found errors:\n{}\nbut expected {}",
                                        errors, stringify!($id)),
                }
                )+
                assert!(iter.count() == 0, "Found more errors than expected\n{}", errors);
            }
        }
    }}
}

macro_rules! assert_unify_err {
    ($e: expr, $($id: pat),+) => {{
        use check::typecheck::TypeError::*;
        #[allow(unused_imports)]
        use check::unify::Error::{TypeMismatch, Occurs, Other};
        #[allow(unused_imports)]
        use check::unify_type::TypeError::{FieldMismatch, SelfRecursive, MissingFields};

        match $e {
            Ok(x) => assert!(false, "Expected error, got {}", x),
            Err(err) => {
                for error in err.errors() {
                    match error {
                        Spanned { value: Unification(_, _, ref errors), .. } => {
                            let mut iter = errors.iter();
                            $(
                            match iter.next() {
                                Some(&$id) => (),
                                Some(error2) => {
                                    assert!(false, "Found errors:\n{}\nExpected:\n{}\nFound\n:{:?}",
                                            error, stringify!($id), error2);
                                }
                                None => {
                                    assert!(false, "Found errors:\n{}\nbut expected {}",
                                            error, stringify!($id));
                                }
                            }
                            )+
                            assert!(iter.count() == 0,
                                    "Found more errors than expected\n{}",
                                    error);
                        }
                        _ => assert!(false,
                                     "Found errors:\n{}\nbut expected an unification error",
                                     error)
                    }
                }
            }
        }
    }}
}

#[test]
fn record_missing_field() {
    let _ = env_logger::init();
    let text = r"
match { x = 1 } with
| { x, y } -> 1
";
    let result = support::typecheck(text);

    assert_unify_err!(result, Other(MissingFields(..)));
}

#[test]
fn undefined_type() {
    let _ = env_logger::init();
    let text = r#"
let x =
    type Test = | Test String Int
    in { Test, x = 1 }
in
type Test2 = Test
in x
"#;
    let result = support::typecheck(text);

    assert_err!(result, UndefinedType(..));
}

#[test]
fn undefined_variant() {
    let _ = env_logger::init();
    let text = r#"
let x =
    type Test = | Test String Int
    { Test, x = 1 }
Test "" 2
"#;
    let result = support::typecheck(text);

    assert_err!(result, UndefinedVariable(..));
}

#[test]
fn mutually_recursive_types_error() {
    let _ = env_logger::init();
    let text = r#"
type List a = | Empty | Node (a (Data a))
and Data a = { value: a, list: List a }
in 1
"#;
    let result = support::typecheck(text);

    assert_err!(result, KindError(TypeMismatch(..)));
}

#[test]
fn unpack_field_which_does_not_exist() {
    let _ = env_logger::init();
    let text = r#"
let { y } = { x = 1 }
2
"#;
    let result = support::typecheck(text);

    assert_unify_err!(result, Other(MissingFields(..)));
}

#[test]
fn unpack_type_field_which_does_not_exist() {
    let _ = env_logger::init();
    let text = r#"
type Test = Int
let { Test2 } = { Test }
2
"#;
    let result = support::typecheck(text);

    assert_err!(result, UndefinedField(..));
}

#[test]
fn duplicate_type_definition() {
    let _ = env_logger::init();
    let text = r#"
type Test = Int
in
type Test = Float
in 1
"#;
    let result = support::typecheck(text);

    assert_err!(result, DuplicateTypeDefinition(..));
}

#[test]
fn no_matching_overloaded_binding() {
    let _ = env_logger::init();
    let text = r#"
let f x = x #Int+ 1
in
let f x = x #Float+ 1.0
in f ""
"#;
    let result = support::typecheck(text);

    assert_err!(result, Rename(..));
}

#[test]
fn no_matching_binop_binding() {
    let _ = env_logger::init();
    let text = r#"
let (++) x y = x #Int+ y
let (++) x y = x #Float+ y
"" ++ ""
"#;
    let result = support::typecheck(text);

    assert_err!(result, Rename(..));
}

#[test]
fn not_enough_information_to_decide_overload() {
    let _ = env_logger::init();
    let text = r#"
let f x = x #Int+ 1
let f x = x #Float+ 1.0
\x -> f x
"#;
    let result = support::typecheck(text);

    assert_err!(result, Rename(..));
}

#[test]
fn type_field_mismatch() {
    let _ = env_logger::init();
    let text = r#"
if True then
    type Test = Int
    { Test }
else
    type Test = Float
    { Test }
"#;
    let result = support::typecheck(text);

    assert_unify_err!(result, TypeMismatch(..));
}

#[test]
fn arguments_need_to_be_instantiated_before_any_access() {
    let _ = env_logger::init();
    // test_fn: forall a. (a -> ()) -> ()
    // To allow any type to be passed to `f` it should be
    // test_fn: (forall a. a -> ()) -> ()
    let text = r#"
let test_fn f: (a -> ()) -> () =
    f 2.0
1
"#;
    let result = support::typecheck(text);

    assert_unify_err!(result, TypeMismatch(..));
}

#[test]
fn infer_ord_int() {
    let _ = env_logger::init();
    let text = r#"
type Ordering = | LT | EQ | GT
type Ord a = {
    compare : a -> a -> Ordering
}
let ord_Int = {
    compare = \l r ->
        if l #Int< r
        then LT
        else if l #Int== r
        then EQ
        else GT
}
let make_Ord ord =
    let compare = ord.compare
    in {
        (<=) = \l r ->
            match compare l r with
                | LT -> True
                | EQ -> True
                | GT -> False
    }
let (<=) = (make_Ord ord_Int).(<=)

"" <= ""
"#;
    let result = support::typecheck(text);

    assert_unify_err!(result, TypeMismatch(..), TypeMismatch(..));
}

#[test]
fn recursive_types_with_differing_aliases() {
    let _ = env_logger::init();
    let text = r"
type Option a = | None | Some a
type R1 = Option R1
and R2 = Option R2

let x: R1 = None
let y: R2 = x
y
";
    let result = support::typecheck(text);

    assert_unify_err!(result, Other(SelfRecursive(..)));
}

#[test]
fn detect_self_recursive_aliases() {
    let _ = env_logger::init();
    let text = r"
type A a = A a

let g x: A a -> () = x
1
";
    let result = support::typecheck(text);

    assert_unify_err!(result, Other(SelfRecursive(..)));
}

#[test]
fn declared_generic_variables_may_not_make_outer_bindings_more_general() {
    let _ = ::env_logger::init();
    let text = r#"
let make m =
    let m2: m = m
    m

make
"#;
    let result = support::typecheck(text);
    assert!(result.is_err());
}

#[test]
fn duplicate_fields() {
    let _ = ::env_logger::init();
    let text = r#"
type Test = Int
let x = ""
{ Test, Test, x = 1, x }
"#;
    let result = support::typecheck(text);
    assert_err!(result, DuplicateField(..), DuplicateField(..));
}

#[test]
fn duplicate_fields_pattern() {
    let _ = ::env_logger::init();
    let text = r#"
type Test = Int
let { Test, Test, x = y, x } = { Test, x = 1 }
()
"#;
    let result = support::typecheck(text);
    assert_err!(result, DuplicateField(..), DuplicateField(..));
}

#[test]
fn type_alias_with_explicit_type_kind() {
    let _ = ::env_logger::init();
    let text = r#"
type Test (a : Type) = a
type Foo a = a
type Bar = Test Foo
()
"#;
    let result = support::typecheck(text);
    assert_err!(result, KindError(TypeMismatch(..)));
}

#[test]
fn type_alias_with_explicit_row_kind() {
    let _ = ::env_logger::init();
    let text = r#"
type Test (a : Row) = a
type Bar = Test Int
()
"#;
    let result = support::typecheck(text);
    assert_err!(result, KindError(TypeMismatch(..)));
}

#[test]
fn type_alias_with_explicit_function_kind() {
    let _ = ::env_logger::init();
    let text = r#"
type Test (a : Type -> Type) = a Int
type Foo = Test Int
()
"#;
    let result = support::typecheck(text);
    assert_err!(result, KindError(TypeMismatch(..)));
}

#[test]
fn type_error_span() {
    use base::pos::Span;

    let _ = ::env_logger::init();
    let text = r#"
let y = 1.0
y
"#;
    let result = support::typecheck_expr_expected(text, Some(&Type::int())).1;
    let errors: Vec<_> = result.unwrap_err().errors().into();
    assert_eq!(errors.len(), 1);
    assert_eq!(
        errors[0].span.map(|loc| loc.absolute),
        Span::new(13.into(), 14.into())
    );
}

#[test]
fn issue_286() {
    let _ = ::env_logger::init();
    let text = r#"
let Test = 1
1
"#;
    let result = support::typecheck(text);
    assert_err!(result, UndefinedVariable(..));
}

#[test]
fn no_inference_variable_in_error() {
    let _ = ::env_logger::init();
    let text = r#"
() 1
"#;
    let result = support::typecheck(text);

    assert_eq!(
        &*format!("{}", result.unwrap_err()).replace("\t", "        "),
        r#"test:Line: 2, Column: 1: Expected the following types to be equal
Expected: b0 -> b1
Found: {}
1 errors were found during unification:
Types do not match:
        Expected: b0 -> b1
        Found: {}
() 1
^~~~
"#
    );
}
<commit_msg>Unit are formatted as ()<commit_after>#[macro_use]
extern crate collect_mac;
extern crate env_logger;
#[macro_use]
extern crate pretty_assertions;

extern crate gluon_base as base;
extern crate gluon_parser as parser;
extern crate gluon_check as check;

use base::pos::Spanned;
use base::types::Type;

mod support;

macro_rules! assert_err {
    ($e: expr, $($id: pat),+) => {{
        use check::typecheck::TypeError::*;
        #[allow(unused_imports)]
        use check::unify::Error::{TypeMismatch, Occurs, Other};
        #[allow(unused_imports)]
        use check::unify_type::TypeError::FieldMismatch;

        match $e {
            Ok(x) => assert!(false, "Expected error, got {}", x),
            Err(err) => {
                let errors = err.errors();
                let mut iter = (&errors).into_iter();
                $(
                match iter.next() {
                    Some(&Spanned { value: $id, .. }) => (),
                    _ => assert!(false, "Found errors:\n{}\nbut expected {}",
                                        errors, stringify!($id)),
                }
                )+
                assert!(iter.count() == 0, "Found more errors than expected\n{}", errors);
            }
        }
    }}
}

macro_rules! assert_unify_err {
    ($e: expr, $($id: pat),+) => {{
        use check::typecheck::TypeError::*;
        #[allow(unused_imports)]
        use check::unify::Error::{TypeMismatch, Occurs, Other};
        #[allow(unused_imports)]
        use check::unify_type::TypeError::{FieldMismatch, SelfRecursive, MissingFields};

        match $e {
            Ok(x) => assert!(false, "Expected error, got {}", x),
            Err(err) => {
                for error in err.errors() {
                    match error {
                        Spanned { value: Unification(_, _, ref errors), .. } => {
                            let mut iter = errors.iter();
                            $(
                            match iter.next() {
                                Some(&$id) => (),
                                Some(error2) => {
                                    assert!(false, "Found errors:\n{}\nExpected:\n{}\nFound\n:{:?}",
                                            error, stringify!($id), error2);
                                }
                                None => {
                                    assert!(false, "Found errors:\n{}\nbut expected {}",
                                            error, stringify!($id));
                                }
                            }
                            )+
                            assert!(iter.count() == 0,
                                    "Found more errors than expected\n{}",
                                    error);
                        }
                        _ => assert!(false,
                                     "Found errors:\n{}\nbut expected an unification error",
                                     error)
                    }
                }
            }
        }
    }}
}

#[test]
fn record_missing_field() {
    let _ = env_logger::init();
    let text = r"
match { x = 1 } with
| { x, y } -> 1
";
    let result = support::typecheck(text);

    assert_unify_err!(result, Other(MissingFields(..)));
}

#[test]
fn undefined_type() {
    let _ = env_logger::init();
    let text = r#"
let x =
    type Test = | Test String Int
    in { Test, x = 1 }
in
type Test2 = Test
in x
"#;
    let result = support::typecheck(text);

    assert_err!(result, UndefinedType(..));
}

#[test]
fn undefined_variant() {
    let _ = env_logger::init();
    let text = r#"
let x =
    type Test = | Test String Int
    { Test, x = 1 }
Test "" 2
"#;
    let result = support::typecheck(text);

    assert_err!(result, UndefinedVariable(..));
}

#[test]
fn mutually_recursive_types_error() {
    let _ = env_logger::init();
    let text = r#"
type List a = | Empty | Node (a (Data a))
and Data a = { value: a, list: List a }
in 1
"#;
    let result = support::typecheck(text);

    assert_err!(result, KindError(TypeMismatch(..)));
}

#[test]
fn unpack_field_which_does_not_exist() {
    let _ = env_logger::init();
    let text = r#"
let { y } = { x = 1 }
2
"#;
    let result = support::typecheck(text);

    assert_unify_err!(result, Other(MissingFields(..)));
}

#[test]
fn unpack_type_field_which_does_not_exist() {
    let _ = env_logger::init();
    let text = r#"
type Test = Int
let { Test2 } = { Test }
2
"#;
    let result = support::typecheck(text);

    assert_err!(result, UndefinedField(..));
}

#[test]
fn duplicate_type_definition() {
    let _ = env_logger::init();
    let text = r#"
type Test = Int
in
type Test = Float
in 1
"#;
    let result = support::typecheck(text);

    assert_err!(result, DuplicateTypeDefinition(..));
}

#[test]
fn no_matching_overloaded_binding() {
    let _ = env_logger::init();
    let text = r#"
let f x = x #Int+ 1
in
let f x = x #Float+ 1.0
in f ""
"#;
    let result = support::typecheck(text);

    assert_err!(result, Rename(..));
}

#[test]
fn no_matching_binop_binding() {
    let _ = env_logger::init();
    let text = r#"
let (++) x y = x #Int+ y
let (++) x y = x #Float+ y
"" ++ ""
"#;
    let result = support::typecheck(text);

    assert_err!(result, Rename(..));
}

#[test]
fn not_enough_information_to_decide_overload() {
    let _ = env_logger::init();
    let text = r#"
let f x = x #Int+ 1
let f x = x #Float+ 1.0
\x -> f x
"#;
    let result = support::typecheck(text);

    assert_err!(result, Rename(..));
}

#[test]
fn type_field_mismatch() {
    let _ = env_logger::init();
    let text = r#"
if True then
    type Test = Int
    { Test }
else
    type Test = Float
    { Test }
"#;
    let result = support::typecheck(text);

    assert_unify_err!(result, TypeMismatch(..));
}

#[test]
fn arguments_need_to_be_instantiated_before_any_access() {
    let _ = env_logger::init();
    // test_fn: forall a. (a -> ()) -> ()
    // To allow any type to be passed to `f` it should be
    // test_fn: (forall a. a -> ()) -> ()
    let text = r#"
let test_fn f: (a -> ()) -> () =
    f 2.0
1
"#;
    let result = support::typecheck(text);

    assert_unify_err!(result, TypeMismatch(..));
}

#[test]
fn infer_ord_int() {
    let _ = env_logger::init();
    let text = r#"
type Ordering = | LT | EQ | GT
type Ord a = {
    compare : a -> a -> Ordering
}
let ord_Int = {
    compare = \l r ->
        if l #Int< r
        then LT
        else if l #Int== r
        then EQ
        else GT
}
let make_Ord ord =
    let compare = ord.compare
    in {
        (<=) = \l r ->
            match compare l r with
                | LT -> True
                | EQ -> True
                | GT -> False
    }
let (<=) = (make_Ord ord_Int).(<=)

"" <= ""
"#;
    let result = support::typecheck(text);

    assert_unify_err!(result, TypeMismatch(..), TypeMismatch(..));
}

#[test]
fn recursive_types_with_differing_aliases() {
    let _ = env_logger::init();
    let text = r"
type Option a = | None | Some a
type R1 = Option R1
and R2 = Option R2

let x: R1 = None
let y: R2 = x
y
";
    let result = support::typecheck(text);

    assert_unify_err!(result, Other(SelfRecursive(..)));
}

#[test]
fn detect_self_recursive_aliases() {
    let _ = env_logger::init();
    let text = r"
type A a = A a

let g x: A a -> () = x
1
";
    let result = support::typecheck(text);

    assert_unify_err!(result, Other(SelfRecursive(..)));
}

#[test]
fn declared_generic_variables_may_not_make_outer_bindings_more_general() {
    let _ = ::env_logger::init();
    let text = r#"
let make m =
    let m2: m = m
    m

make
"#;
    let result = support::typecheck(text);
    assert!(result.is_err());
}

#[test]
fn duplicate_fields() {
    let _ = ::env_logger::init();
    let text = r#"
type Test = Int
let x = ""
{ Test, Test, x = 1, x }
"#;
    let result = support::typecheck(text);
    assert_err!(result, DuplicateField(..), DuplicateField(..));
}

#[test]
fn duplicate_fields_pattern() {
    let _ = ::env_logger::init();
    let text = r#"
type Test = Int
let { Test, Test, x = y, x } = { Test, x = 1 }
()
"#;
    let result = support::typecheck(text);
    assert_err!(result, DuplicateField(..), DuplicateField(..));
}

#[test]
fn type_alias_with_explicit_type_kind() {
    let _ = ::env_logger::init();
    let text = r#"
type Test (a : Type) = a
type Foo a = a
type Bar = Test Foo
()
"#;
    let result = support::typecheck(text);
    assert_err!(result, KindError(TypeMismatch(..)));
}

#[test]
fn type_alias_with_explicit_row_kind() {
    let _ = ::env_logger::init();
    let text = r#"
type Test (a : Row) = a
type Bar = Test Int
()
"#;
    let result = support::typecheck(text);
    assert_err!(result, KindError(TypeMismatch(..)));
}

#[test]
fn type_alias_with_explicit_function_kind() {
    let _ = ::env_logger::init();
    let text = r#"
type Test (a : Type -> Type) = a Int
type Foo = Test Int
()
"#;
    let result = support::typecheck(text);
    assert_err!(result, KindError(TypeMismatch(..)));
}

#[test]
fn type_error_span() {
    use base::pos::Span;

    let _ = ::env_logger::init();
    let text = r#"
let y = 1.0
y
"#;
    let result = support::typecheck_expr_expected(text, Some(&Type::int())).1;
    let errors: Vec<_> = result.unwrap_err().errors().into();
    assert_eq!(errors.len(), 1);
    assert_eq!(
        errors[0].span.map(|loc| loc.absolute),
        Span::new(13.into(), 14.into())
    );
}

#[test]
fn issue_286() {
    let _ = ::env_logger::init();
    let text = r#"
let Test = 1
1
"#;
    let result = support::typecheck(text);
    assert_err!(result, UndefinedVariable(..));
}

#[test]
fn no_inference_variable_in_error() {
    let _ = ::env_logger::init();
    let text = r#"
() 1
"#;
    let result = support::typecheck(text);

    assert_eq!(
        &*format!("{}", result.unwrap_err()).replace("\t", "        "),
        r#"test:Line: 2, Column: 1: Expected the following types to be equal
Expected: b0 -> b1
Found: ()
1 errors were found during unification:
Types do not match:
        Expected: b0 -> b1
        Found: ()
() 1
^~~~
"#
    );
}
<|endoftext|>


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

//! Common handling of keyboard input and state management for text input controls

use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods;
use dom::bindings::js::JSRef;
use dom::keyboardevent::KeyboardEvent;
use servo_util::str::DOMString;

use std::cmp::{min, max};
use std::default::Default;
use std::num::SignedInt;

#[jstraceable]
#[deriving(Copy)]
struct TextPoint {
    /// 0-based line number
    line: uint,
    /// 0-based column number
    index: uint,
}

/// Encapsulated state for handling keyboard input in a single or multiline text input control.
#[jstraceable]
pub struct TextInput {
    /// Current text input content, split across lines without trailing '\n'
    lines: Vec<DOMString>,
    /// Current cursor input point
    edit_point: TextPoint,
    /// Beginning of selection range with edit_point as end that can span multiple lines.
    selection_begin: Option<TextPoint>,
    /// Is this a multiline input?
    multiline: bool,
}

/// Resulting action to be taken by the owner of a text input that is handling an event.
pub enum KeyReaction {
    TriggerDefaultAction,
    DispatchInput,
    Nothing,
}

impl Default for TextPoint {
    fn default() -> TextPoint {
        TextPoint {
            line: 0,
            index: 0,
        }
    }
}

/// Control whether this control should allow multiple lines.
#[deriving(PartialEq)]
pub enum Lines {
    Single,
    Multiple,
}

/// The direction in which to delete a character.
#[deriving(PartialEq)]
enum DeleteDir {
    Forward,
    Backward
}

/// Was the keyboard event accompanied by the standard control modifier,
/// i.e. cmd on Mac OS or ctrl on other platforms.
#[cfg(target_os="macos")]
fn is_control_key(event: JSRef<KeyboardEvent>) -> bool {
    event.MetaKey() && !event.CtrlKey() && !event.AltKey()
}

#[cfg(not(target_os="macos"))]
fn is_control_key(event: JSRef<KeyboardEvent>) -> bool {
    event.CtrlKey() && !event.MetaKey() && !event.AltKey()
}

impl TextInput {
    /// Instantiate a new text input control
    pub fn new(lines: Lines, initial: DOMString) -> TextInput {
        let mut i = TextInput {
            lines: vec!(),
            edit_point: Default::default(),
            selection_begin: None,
            multiline: lines == Lines::Multiple,
        };
        i.set_content(initial);
        i
    }

    /// Remove a character at the current editing point
    fn delete_char(&mut self, dir: DeleteDir) {
        if self.selection_begin.is_none() {
            self.adjust_horizontal(if dir == DeleteDir::Forward {
                1
            } else {
                -1
            }, true);
        }
        self.replace_selection("".into_string());
    }

    /// Insert a character at the current editing point
    fn insert_char(&mut self, ch: char) {
        if self.selection_begin.is_none() {
            self.selection_begin = Some(self.edit_point);
        }
        self.replace_selection(ch.to_string());
    }

    fn get_sorted_selection(&self) -> (TextPoint, TextPoint) {
        let begin = self.selection_begin.unwrap();
        let end = self.edit_point;

        if begin.line < end.line || (begin.line == end.line && begin.index < end.index) {
            (begin, end)
        } else {
            (end, begin)
        }
    }

    fn replace_selection(&mut self, insert: String) {
        let (begin, end) = self.get_sorted_selection();
        self.selection_begin = None;

        let new_lines = {
            let prefix = self.lines[begin.line].slice_chars(0, begin.index);
            let suffix = self.lines[end.line].slice_chars(end.index, self.lines[end.line].char_len());
            let lines_prefix = self.lines.slice(0, begin.line);
            let lines_suffix = self.lines.slice(end.line + 1, self.lines.len());

            let mut insert_lines = if self.multiline {
                insert.as_slice().split('\n').map(|s| s.into_string()).collect()
            } else {
                vec!(insert)
            };

            let mut new_line = prefix.into_string();
            new_line.push_str(insert_lines[0].as_slice());
            insert_lines[0] = new_line;

            let last_insert_lines_index = insert_lines.len() - 1;
            self.edit_point.index = insert_lines[last_insert_lines_index].char_len();
            self.edit_point.line = begin.line + last_insert_lines_index;

            insert_lines[last_insert_lines_index].push_str(suffix);

            let mut new_lines = vec!();
            new_lines.push_all(lines_prefix);
            new_lines.push_all(insert_lines.as_slice());
            new_lines.push_all(lines_suffix);
            new_lines
        };

        self.lines = new_lines;
    }

    /// Return the length of the current line under the editing point.
    fn current_line_length(&self) -> uint {
        self.lines[self.edit_point.line].char_len()
    }

    /// Adjust the editing point position by a given of lines. The resulting column is
    /// as close to the original column position as possible.
    fn adjust_vertical(&mut self, adjust: int, select: bool) {
        if !self.multiline {
            return;
        }

        if select {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            self.selection_begin = None;
        }

        assert!(self.edit_point.line < self.lines.len());

        let target_line: int = self.edit_point.line as int + adjust;

        if target_line < 0 {
            self.edit_point.index = 0;
            self.edit_point.line = 0;
            return;
        } else if target_line as uint >= self.lines.len() {
            self.edit_point.line = self.lines.len() - 1;
            self.edit_point.index = self.current_line_length();
            return;
        }

        self.edit_point.line = target_line as uint;
        self.edit_point.index = min(self.current_line_length(), self.edit_point.index);
    }

    /// Adjust the editing point position by a given number of columns. If the adjustment
    /// requested is larger than is available in the current line, the editing point is
    /// adjusted vertically and the process repeats with the remaining adjustment requested.
    fn adjust_horizontal(&mut self, adjust: int, select: bool) {
        if select {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            if self.selection_begin.is_some() {
                let (begin, end) = self.get_sorted_selection();
                self.edit_point = if adjust < 0 {begin} else {end};
                self.selection_begin = None;
                return
            }
        }

        if adjust < 0 {
            let remaining = self.edit_point.index;
            if adjust.abs() as uint > remaining && self.edit_point.line > 0 {
                self.adjust_vertical(-1, select);
                self.edit_point.index = self.current_line_length();
                self.adjust_horizontal(adjust + remaining as int + 1, select);
            } else {
                self.edit_point.index = max(0, self.edit_point.index as int + adjust) as uint;
            }
        } else {
            let remaining = self.current_line_length() - self.edit_point.index;
            if adjust as uint > remaining && self.lines.len() > self.edit_point.line + 1 {
                self.adjust_vertical(1, select);
                self.edit_point.index = 0;
                // one shift is consumed by the change of line, hence the -1
                self.adjust_horizontal(adjust - remaining as int - 1, select);
            } else {
                self.edit_point.index = min(self.current_line_length(),
                                            self.edit_point.index + adjust as uint);
            }
        }
    }

    /// Deal with a newline input.
    fn handle_return(&mut self) -> KeyReaction {
        if !self.multiline {
            return KeyReaction::TriggerDefaultAction;
        }
        self.insert_char('\n');
        return KeyReaction::DispatchInput;
    }

    /// Select all text in the input control.
    fn select_all(&mut self) {
        self.selection_begin = Some(TextPoint {
            line: 0,
            index: 0,
        });
        let last_line = self.lines.len() - 1;
        self.edit_point.line = last_line;
        self.edit_point.index = self.lines[last_line].char_len();
    }

    /// Process a given `KeyboardEvent` and return an action for the caller to execute.
    pub fn handle_keydown(&mut self, event: JSRef<KeyboardEvent>) -> KeyReaction {
        match event.Key().as_slice() {
           "a" if is_control_key(event) => {
                self.select_all();
                KeyReaction::Nothing
            },
            // printable characters have single-character key values
            c if c.len() == 1 => {
                self.insert_char(c.char_at(0));
                return KeyReaction::DispatchInput;
            }
            "Space" => {
                self.insert_char(' ');
                KeyReaction::DispatchInput
            }
            "Delete" => {
                self.delete_char(DeleteDir::Forward);
                KeyReaction::DispatchInput
            }
            "Backspace" => {
                self.delete_char(DeleteDir::Backward);
                KeyReaction::DispatchInput
            }
            "ArrowLeft" => {
                self.adjust_horizontal(-1, event.ShiftKey());
                KeyReaction::Nothing
            }
            "ArrowRight" => {
                self.adjust_horizontal(1, event.ShiftKey());
                KeyReaction::Nothing
            }
            "ArrowUp" => {
                self.adjust_vertical(-1, event.ShiftKey());
                KeyReaction::Nothing
            }
            "ArrowDown" => {
                self.adjust_vertical(1, event.ShiftKey());
                KeyReaction::Nothing
            }
            "Enter" => self.handle_return(),
            "Home" => {
                self.edit_point.index = 0;
                KeyReaction::Nothing
            }
            "End" => {
                self.edit_point.index = self.current_line_length();
                KeyReaction::Nothing
            }
            "PageUp" => {
                self.adjust_vertical(-28, event.ShiftKey());
                KeyReaction::Nothing
            }
            "PageDown" => {
                self.adjust_vertical(28, event.ShiftKey());
                KeyReaction::Nothing
            }
            "Tab" => KeyReaction::TriggerDefaultAction,
            _ => KeyReaction::Nothing,
        }
    }

    /// Get the current contents of the text input. Multiple lines are joined by \n.
    pub fn get_content(&self) -> DOMString {
        let mut content = "".into_string();
        for (i, line) in self.lines.iter().enumerate() {
            content.push_str(line.as_slice());
            if i < self.lines.len() - 1 {
                content.push('\n');
            }
        }
        content
    }

    /// Set the current contents of the text input. If this is control supports multiple lines,
    /// any \n encountered will be stripped and force a new logical line.
    pub fn set_content(&mut self, content: DOMString) {
        self.lines = if self.multiline {
            content.as_slice().split('\n').map(|s| s.into_string()).collect()
        } else {
            vec!(content)
        };
        self.edit_point.line = min(self.edit_point.line, self.lines.len() - 1);

        if self.current_line_length() == 0 {
            self.edit_point.index = 0;
        }
        else {
            self.edit_point.index = min(self.edit_point.index, self.current_line_length() - 1);
        }
    }
}
<commit_msg>auto merge of #4579 : mattnenterprise/servo/textinput-select-refactor, r=jdm<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Common handling of keyboard input and state management for text input controls

use dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods;
use dom::bindings::js::JSRef;
use dom::keyboardevent::KeyboardEvent;
use servo_util::str::DOMString;

use std::cmp::{min, max};
use std::default::Default;
use std::num::SignedInt;

#[deriving(Copy, PartialEq)]
enum Selection {
    Selected,
    NotSelected
}

#[jstraceable]
#[deriving(Copy)]
struct TextPoint {
    /// 0-based line number
    line: uint,
    /// 0-based column number
    index: uint,
}

/// Encapsulated state for handling keyboard input in a single or multiline text input control.
#[jstraceable]
pub struct TextInput {
    /// Current text input content, split across lines without trailing '\n'
    lines: Vec<DOMString>,
    /// Current cursor input point
    edit_point: TextPoint,
    /// Beginning of selection range with edit_point as end that can span multiple lines.
    selection_begin: Option<TextPoint>,
    /// Is this a multiline input?
    multiline: bool,
}

/// Resulting action to be taken by the owner of a text input that is handling an event.
pub enum KeyReaction {
    TriggerDefaultAction,
    DispatchInput,
    Nothing,
}

impl Default for TextPoint {
    fn default() -> TextPoint {
        TextPoint {
            line: 0,
            index: 0,
        }
    }
}

/// Control whether this control should allow multiple lines.
#[deriving(PartialEq)]
pub enum Lines {
    Single,
    Multiple,
}

/// The direction in which to delete a character.
#[deriving(PartialEq)]
enum DeleteDir {
    Forward,
    Backward
}

/// Was the keyboard event accompanied by the standard control modifier,
/// i.e. cmd on Mac OS or ctrl on other platforms.
#[cfg(target_os="macos")]
fn is_control_key(event: JSRef<KeyboardEvent>) -> bool {
    event.MetaKey() && !event.CtrlKey() && !event.AltKey()
}

#[cfg(not(target_os="macos"))]
fn is_control_key(event: JSRef<KeyboardEvent>) -> bool {
    event.CtrlKey() && !event.MetaKey() && !event.AltKey()
}

impl TextInput {
    /// Instantiate a new text input control
    pub fn new(lines: Lines, initial: DOMString) -> TextInput {
        let mut i = TextInput {
            lines: vec!(),
            edit_point: Default::default(),
            selection_begin: None,
            multiline: lines == Lines::Multiple,
        };
        i.set_content(initial);
        i
    }

    /// Remove a character at the current editing point
    fn delete_char(&mut self, dir: DeleteDir) {
        if self.selection_begin.is_none() {
            self.adjust_horizontal(if dir == DeleteDir::Forward {
                1
            } else {
                -1
            }, Selection::Selected);
        }
        self.replace_selection("".into_string());
    }

    /// Insert a character at the current editing point
    fn insert_char(&mut self, ch: char) {
        if self.selection_begin.is_none() {
            self.selection_begin = Some(self.edit_point);
        }
        self.replace_selection(ch.to_string());
    }

    fn get_sorted_selection(&self) -> (TextPoint, TextPoint) {
        let begin = self.selection_begin.unwrap();
        let end = self.edit_point;

        if begin.line < end.line || (begin.line == end.line && begin.index < end.index) {
            (begin, end)
        } else {
            (end, begin)
        }
    }

    fn replace_selection(&mut self, insert: String) {
        let (begin, end) = self.get_sorted_selection();
        self.selection_begin = None;

        let new_lines = {
            let prefix = self.lines[begin.line].slice_chars(0, begin.index);
            let suffix = self.lines[end.line].slice_chars(end.index, self.lines[end.line].char_len());
            let lines_prefix = self.lines.slice(0, begin.line);
            let lines_suffix = self.lines.slice(end.line + 1, self.lines.len());

            let mut insert_lines = if self.multiline {
                insert.as_slice().split('\n').map(|s| s.into_string()).collect()
            } else {
                vec!(insert)
            };

            let mut new_line = prefix.into_string();
            new_line.push_str(insert_lines[0].as_slice());
            insert_lines[0] = new_line;

            let last_insert_lines_index = insert_lines.len() - 1;
            self.edit_point.index = insert_lines[last_insert_lines_index].char_len();
            self.edit_point.line = begin.line + last_insert_lines_index;

            insert_lines[last_insert_lines_index].push_str(suffix);

            let mut new_lines = vec!();
            new_lines.push_all(lines_prefix);
            new_lines.push_all(insert_lines.as_slice());
            new_lines.push_all(lines_suffix);
            new_lines
        };

        self.lines = new_lines;
    }

    /// Return the length of the current line under the editing point.
    fn current_line_length(&self) -> uint {
        self.lines[self.edit_point.line].char_len()
    }

    /// Adjust the editing point position by a given of lines. The resulting column is
    /// as close to the original column position as possible.
    fn adjust_vertical(&mut self, adjust: int, select: Selection) {
        if !self.multiline {
            return;
        }

        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            self.selection_begin = None;
        }

        assert!(self.edit_point.line < self.lines.len());

        let target_line: int = self.edit_point.line as int + adjust;

        if target_line < 0 {
            self.edit_point.index = 0;
            self.edit_point.line = 0;
            return;
        } else if target_line as uint >= self.lines.len() {
            self.edit_point.line = self.lines.len() - 1;
            self.edit_point.index = self.current_line_length();
            return;
        }

        self.edit_point.line = target_line as uint;
        self.edit_point.index = min(self.current_line_length(), self.edit_point.index);
    }

    /// Adjust the editing point position by a given number of columns. If the adjustment
    /// requested is larger than is available in the current line, the editing point is
    /// adjusted vertically and the process repeats with the remaining adjustment requested.
    fn adjust_horizontal(&mut self, adjust: int, select: Selection) {
        if select == Selection::Selected {
            if self.selection_begin.is_none() {
                self.selection_begin = Some(self.edit_point);
            }
        } else {
            if self.selection_begin.is_some() {
                let (begin, end) = self.get_sorted_selection();
                self.edit_point = if adjust < 0 {begin} else {end};
                self.selection_begin = None;
                return
            }
        }

        if adjust < 0 {
            let remaining = self.edit_point.index;
            if adjust.abs() as uint > remaining && self.edit_point.line > 0 {
                self.adjust_vertical(-1, select);
                self.edit_point.index = self.current_line_length();
                self.adjust_horizontal(adjust + remaining as int + 1, select);
            } else {
                self.edit_point.index = max(0, self.edit_point.index as int + adjust) as uint;
            }
        } else {
            let remaining = self.current_line_length() - self.edit_point.index;
            if adjust as uint > remaining && self.lines.len() > self.edit_point.line + 1 {
                self.adjust_vertical(1, select);
                self.edit_point.index = 0;
                // one shift is consumed by the change of line, hence the -1
                self.adjust_horizontal(adjust - remaining as int - 1, select);
            } else {
                self.edit_point.index = min(self.current_line_length(),
                                            self.edit_point.index + adjust as uint);
            }
        }
    }

    /// Deal with a newline input.
    fn handle_return(&mut self) -> KeyReaction {
        if !self.multiline {
            return KeyReaction::TriggerDefaultAction;
        }
        self.insert_char('\n');
        return KeyReaction::DispatchInput;
    }

    /// Select all text in the input control.
    fn select_all(&mut self) {
        self.selection_begin = Some(TextPoint {
            line: 0,
            index: 0,
        });
        let last_line = self.lines.len() - 1;
        self.edit_point.line = last_line;
        self.edit_point.index = self.lines[last_line].char_len();
    }

    /// Process a given `KeyboardEvent` and return an action for the caller to execute.
    pub fn handle_keydown(&mut self, event: JSRef<KeyboardEvent>) -> KeyReaction {
        //A simple way to convert an event to a selection
        fn maybe_select(event: JSRef<KeyboardEvent>) -> Selection {
            if event.ShiftKey() {
                return Selection::Selected
            }
            return Selection::NotSelected
        }
        match event.Key().as_slice() {
           "a" if is_control_key(event) => {
                self.select_all();
                KeyReaction::Nothing
            },
            // printable characters have single-character key values
            c if c.len() == 1 => {
                self.insert_char(c.char_at(0));
                return KeyReaction::DispatchInput;
            }
            "Space" => {
                self.insert_char(' ');
                KeyReaction::DispatchInput
            }
            "Delete" => {
                self.delete_char(DeleteDir::Forward);
                KeyReaction::DispatchInput
            }
            "Backspace" => {
                self.delete_char(DeleteDir::Backward);
                KeyReaction::DispatchInput
            }
            "ArrowLeft" => {
                self.adjust_horizontal(-1, maybe_select(event));
                KeyReaction::Nothing
            }
            "ArrowRight" => {
                self.adjust_horizontal(1, maybe_select(event));
                KeyReaction::Nothing
            }
            "ArrowUp" => {
                self.adjust_vertical(-1, maybe_select(event));
                KeyReaction::Nothing
            }
            "ArrowDown" => {
                self.adjust_vertical(1, maybe_select(event));
                KeyReaction::Nothing
            }
            "Enter" => self.handle_return(),
            "Home" => {
                self.edit_point.index = 0;
                KeyReaction::Nothing
            }
            "End" => {
                self.edit_point.index = self.current_line_length();
                KeyReaction::Nothing
            }
            "PageUp" => {
                self.adjust_vertical(-28, maybe_select(event));
                KeyReaction::Nothing
            }
            "PageDown" => {
                self.adjust_vertical(28, maybe_select(event));
                KeyReaction::Nothing
            }
            "Tab" => KeyReaction::TriggerDefaultAction,
            _ => KeyReaction::Nothing,
        }
    }

    /// Get the current contents of the text input. Multiple lines are joined by \n.
    pub fn get_content(&self) -> DOMString {
        let mut content = "".into_string();
        for (i, line) in self.lines.iter().enumerate() {
            content.push_str(line.as_slice());
            if i < self.lines.len() - 1 {
                content.push('\n');
            }
        }
        content
    }

    /// Set the current contents of the text input. If this is control supports multiple lines,
    /// any \n encountered will be stripped and force a new logical line.
    pub fn set_content(&mut self, content: DOMString) {
        self.lines = if self.multiline {
            content.as_slice().split('\n').map(|s| s.into_string()).collect()
        } else {
            vec!(content)
        };
        self.edit_point.line = min(self.edit_point.line, self.lines.len() - 1);

        if self.current_line_length() == 0 {
            self.edit_point.index = 0;
        }
        else {
            self.edit_point.index = min(self.edit_point.index, self.current_line_length() - 1);
        }
    }
}
<|endoftext|>