<commit_before>use token::Token;
use token::Token::*;

#[derive(Default)]
struct CompileState {
    output: String,
    loopnum: i32,
    ifnum: i32,
    outbuffsize: i32,
}

fn compile_iter(state: &mut CompileState, tokens: Vec<Token>) {
    let mut outbuffpos = 0;
    for token in tokens {
        match token {
            Add(offset, value) => {
                let dest = if offset == 0 {
                    "%r12".to_string()
                } else {
                    format!("{}(%rbx)", (offset * 8))
                };
                if value == 1 && dest == "%r12" {
                    state.output.push_str("    inc %r12\n");
                } else if value >= 1 {
                    state.output.push_str(&format!("    addq ${}, {}\n", value, dest));
                } else if value == -1 && dest == "%r12" {
                    state.output.push_str("    dec %r12\n");
                } else if value <= -1 {
                    state.output.push_str(&format!("    subq ${}, {}\n", -value, dest));
                }
            }
            MulCopy(src_idx, dest_idx, mul) => {
                let mut src = if src_idx == 0 {
                    "%r12".to_string()
                } else {
                    format!("{}(%rbx)", (src_idx * 8))
                };
                let dest = if dest_idx == 0 {
                    "%r12".to_string()
                } else {
                    format!("{}(%rbx)", (dest_idx * 8))
                };

                if mul != -1 && mul != 1 {
                    state.output.push_str(&format!(concat!("    movq {}, %rax\n",
                                                           "    movq ${}, %rdx\n",
                                                           "    mulq %rdx\n"),
                                                   src,
                                                   mul.abs()));
                    src = "%rax".to_string();
                } else if src != "%r12" && dest != "%r12" {
                    // x86 cannot move memory to memory
                    state.output.push_str(&format!("    movq {}, %rax\n", src));
                    src = "%rax".to_string();
                }

                if mul > 0 {
                    state.output.push_str(&format!("    addq {}, {}\n", src, dest));
                } else {
                    state.output.push_str(&format!("    subq {}, {}\n", src, dest));
                }
            }
            Set(offset, value) => {
                if offset == 0 && value == 0 {
                    state.output.push_str("    xor %r12, %r12\n");
                } else if offset == 0 {
                    state.output.push_str(&format!("    movq ${}, %r12\n", value));
                } else {
                    state.output.push_str(&format!("    movq ${}, {}(%rbx)\n", value, offset * 8));
                }
            }
            Move(offset) => {
                if offset != 0 {
                    state.output.push_str("    movq %r12, (%rbx)\n");
                    if offset > 0 {
                        state.output.push_str(&format!("    addq ${}, %rbx\n", offset * 8));
                    } else {
                        state.output.push_str(&format!("    subq ${}, %rbx\n", -offset * 8));
                    }
                    state.output.push_str("    movq (%rbx), %r12\n");
                }
            }
            Loop(content) => {
                state.loopnum += 1;
                let curloop = state.loopnum;
                state.output.push_str(&format!(concat!("    jmp endloop{}\n", "    loop{}:\n"),
                                               curloop,
                                               curloop));

                compile_iter(state, content);

                state.output.push_str(&format!(concat!("    endloop{}:\n",
                                                       "    test %r12, %r12\n",
                                                       "    jnz loop{}\n"),
                                               curloop,
                                               curloop))
            }
            If(offset, content) => {
                state.ifnum += 1;
                let curif = state.ifnum;
                if offset == 0 {
                    state.output.push_str("    test %r12, %r12\n");
                } else {
                    state.output.push_str(&format!("    cmpq $0, {}(%rbx)\n", offset * 8));
                }
                state.output.push_str(&format!("    jz endif{}\n", curif));

                compile_iter(state, content);

                state.output.push_str(&format!("    endif{}:\n", curif))
            }
            Scan(offset) => {
                // Slighly more optimal than normal loop and move
                state.loopnum += 1;
                state.output.push_str(&format!(concat!("    movq %r12, (%rbx)\n",
                                                       "    jmp endloop{num}\n",
                                                       "    loop{num}:\n",
                                                       "    {add_sub} ${shift}, %rbx\n",
                                                       "    endloop{num}:\n",
                                                       "    cmp $0, (%rbx)\n",
                                                       "    jnz loop{num}\n",
                                                       "    movq (%rbx), %r12\n"),
                                               num = state.loopnum,
                                               add_sub = if offset > 0 {"addq"} else {"subq"},
                                               shift = offset.abs() * 8));
            }
            Input => {
                state.output.push_str(concat!("\n    xor %rax, %rax\n",
                                              "    xor %rdi, %rdi\n",
                                              "    movq %rbx, %rsi\n",
                                              "    movq $1, %rdx\n",
                                              "    syscall\n",
                                              "    movq (%rbx), %r12\n\n"))
            }
            LoadOut(offset, add) => {
                let outaddr = format!("(strbuff+{})", outbuffpos);
                if offset == 0 {
                    state.output.push_str(&format!("    movq %r12, {}\n", outaddr));
                } else {
                    state.output.push_str(&format!("    movq {}(%rbx), %rax\n", offset * 8));
                    state.output.push_str(&format!("    movq %rax, {}\n", outaddr));
                }
                if add > 0 {
                    state.output.push_str(&format!("    addb ${}, {}\n", add, outaddr));
                } else if add < 0 {
                    state.output.push_str(&format!("    subb ${}, {}\n", -add, outaddr));
                }
                outbuffpos += 1;
            }
            LoadOutSet(value) => {
                let outaddr = format!("(strbuff+{})", outbuffpos);
                state.output.push_str(&format!("    movq ${}, {}\n", value, outaddr));
                outbuffpos += 1;
            }
            Output => {
                state.output.push_str(&format!(concat!("    movq $1, %rax\n",
                                                       "    movq $1, %rdi\n",
                                                       "    movq $strbuff, %rsi\n",
                                                       "    movq ${}, %rdx\n",
                                                       "    syscall\n\n"),
                                               outbuffpos));

                if state.outbuffsize < outbuffpos + 8 {
                    state.outbuffsize = outbuffpos + 8;
                }
                outbuffpos = 0;
            }
        }
    }
}

pub fn compile(tokens: Vec<Token>, tape_size: i32) -> String {
    let mut state = CompileState::default();

    compile_iter(&mut state, tokens);

    format!(concat!(".section .bss\n",
                    "    .lcomm strbuff, {outbuffsize}\n",
                    "    .lcomm mem, {}\n",
                    "    .set startidx, mem + {}\n",
                    ".section .text\n",
                    ".global _start\n",
                    "_start:\n",
                    "    xor %r12, %r12\n",
                    "    movq $startidx, %rbx\n\n",
                    "{}\n",
                    // Exit syscall
                    "    movq $60, %rax\n",
                    "    movq $0, %rdi\n",
                    "    syscall\n"),
            tape_size,
            tape_size / 2,
            state.output,
            outbuffsize = state.outbuffsize)
}
<commit_msg>Run "cargo fmt"<commit_after>use token::Token;
use token::Token::*;

#[derive(Default)]
struct CompileState {
    output: String,
    loopnum: i32,
    ifnum: i32,
    outbuffsize: i32,
}

fn compile_iter(state: &mut CompileState, tokens: Vec<Token>) {
    let mut outbuffpos = 0;
    for token in tokens {
        match token {
            Add(offset, value) => {
                let dest = if offset == 0 {
                    "%r12".to_string()
                } else {
                    format!("{}(%rbx)", (offset * 8))
                };
                if value == 1 && dest == "%r12" {
                    state.output.push_str("    inc %r12\n");
                } else if value >= 1 {
                    state.output.push_str(&format!("    addq ${}, {}\n", value, dest));
                } else if value == -1 && dest == "%r12" {
                    state.output.push_str("    dec %r12\n");
                } else if value <= -1 {
                    state.output.push_str(&format!("    subq ${}, {}\n", -value, dest));
                }
            }
            MulCopy(src_idx, dest_idx, mul) => {
                let mut src = if src_idx == 0 {
                    "%r12".to_string()
                } else {
                    format!("{}(%rbx)", (src_idx * 8))
                };
                let dest = if dest_idx == 0 {
                    "%r12".to_string()
                } else {
                    format!("{}(%rbx)", (dest_idx * 8))
                };

                if mul != -1 && mul != 1 {
                    state.output.push_str(&format!(concat!("    movq {}, %rax\n",
                                                           "    movq ${}, %rdx\n",
                                                           "    mulq %rdx\n"),
                                                   src,
                                                   mul.abs()));
                    src = "%rax".to_string();
                } else if src != "%r12" && dest != "%r12" {
                    // x86 cannot move memory to memory
                    state.output.push_str(&format!("    movq {}, %rax\n", src));
                    src = "%rax".to_string();
                }

                if mul > 0 {
                    state.output.push_str(&format!("    addq {}, {}\n", src, dest));
                } else {
                    state.output.push_str(&format!("    subq {}, {}\n", src, dest));
                }
            }
            Set(offset, value) => {
                if offset == 0 && value == 0 {
                    state.output.push_str("    xor %r12, %r12\n");
                } else if offset == 0 {
                    state.output.push_str(&format!("    movq ${}, %r12\n", value));
                } else {
                    state.output.push_str(&format!("    movq ${}, {}(%rbx)\n", value, offset * 8));
                }
            }
            Move(offset) => {
                if offset != 0 {
                    state.output.push_str("    movq %r12, (%rbx)\n");
                    if offset > 0 {
                        state.output.push_str(&format!("    addq ${}, %rbx\n", offset * 8));
                    } else {
                        state.output.push_str(&format!("    subq ${}, %rbx\n", -offset * 8));
                    }
                    state.output.push_str("    movq (%rbx), %r12\n");
                }
            }
            Loop(content) => {
                state.loopnum += 1;
                let curloop = state.loopnum;
                state.output.push_str(&format!(concat!("    jmp endloop{}\n", "    loop{}:\n"),
                                               curloop,
                                               curloop));

                compile_iter(state, content);

                state.output.push_str(&format!(concat!("    endloop{}:\n",
                                                       "    test %r12, %r12\n",
                                                       "    jnz loop{}\n"),
                                               curloop,
                                               curloop))
            }
            If(offset, content) => {
                state.ifnum += 1;
                let curif = state.ifnum;
                if offset == 0 {
                    state.output.push_str("    test %r12, %r12\n");
                } else {
                    state.output.push_str(&format!("    cmpq $0, {}(%rbx)\n", offset * 8));
                }
                state.output.push_str(&format!("    jz endif{}\n", curif));

                compile_iter(state, content);

                state.output.push_str(&format!("    endif{}:\n", curif))
            }
            Scan(offset) => {
                // Slighly more optimal than normal loop and move
                state.loopnum += 1;
                state.output.push_str(&format!(concat!("    movq %r12, (%rbx)\n",
                                                       "    jmp endloop{num}\n",
                                                       "    loop{num}:\n",
                                                       "    {add_sub} ${shift}, %rbx\n",
                                                       "    endloop{num}:\n",
                                                       "    cmp $0, (%rbx)\n",
                                                       "    jnz loop{num}\n",
                                                       "    movq (%rbx), %r12\n"),
                                               num = state.loopnum,
                                               add_sub = if offset > 0 { "addq" } else { "subq" },
                                               shift = offset.abs() * 8));
            }
            Input => {
                state.output.push_str(concat!("\n    xor %rax, %rax\n",
                                              "    xor %rdi, %rdi\n",
                                              "    movq %rbx, %rsi\n",
                                              "    movq $1, %rdx\n",
                                              "    syscall\n",
                                              "    movq (%rbx), %r12\n\n"))
            }
            LoadOut(offset, add) => {
                let outaddr = format!("(strbuff+{})", outbuffpos);
                if offset == 0 {
                    state.output.push_str(&format!("    movq %r12, {}\n", outaddr));
                } else {
                    state.output.push_str(&format!("    movq {}(%rbx), %rax\n", offset * 8));
                    state.output.push_str(&format!("    movq %rax, {}\n", outaddr));
                }
                if add > 0 {
                    state.output.push_str(&format!("    addb ${}, {}\n", add, outaddr));
                } else if add < 0 {
                    state.output.push_str(&format!("    subb ${}, {}\n", -add, outaddr));
                }
                outbuffpos += 1;
            }
            LoadOutSet(value) => {
                let outaddr = format!("(strbuff+{})", outbuffpos);
                state.output.push_str(&format!("    movq ${}, {}\n", value, outaddr));
                outbuffpos += 1;
            }
            Output => {
                state.output.push_str(&format!(concat!("    movq $1, %rax\n",
                                                       "    movq $1, %rdi\n",
                                                       "    movq $strbuff, %rsi\n",
                                                       "    movq ${}, %rdx\n",
                                                       "    syscall\n\n"),
                                               outbuffpos));

                if state.outbuffsize < outbuffpos + 8 {
                    state.outbuffsize = outbuffpos + 8;
                }
                outbuffpos = 0;
            }
        }
    }
}

pub fn compile(tokens: Vec<Token>, tape_size: i32) -> String {
    let mut state = CompileState::default();

    compile_iter(&mut state, tokens);

    format!(concat!(".section .bss\n",
                    "    .lcomm strbuff, {outbuffsize}\n",
                    "    .lcomm mem, {}\n",
                    "    .set startidx, mem + {}\n",
                    ".section .text\n",
                    ".global _start\n",
                    "_start:\n",
                    "    xor %r12, %r12\n",
                    "    movq $startidx, %rbx\n\n",
                    "{}\n",
                    // Exit syscall
                    "    movq $60, %rax\n",
                    "    movq $0, %rdi\n",
                    "    syscall\n"),
            tape_size,
            tape_size / 2,
            state.output,
            outbuffsize = state.outbuffsize)
}
<|endoftext|>


<commit_before>use std::fmt::Write;

use crate::token::Token;
use crate::token::Token::*;
use crate::IsbfcIR;

#[cfg(target_os = "redox")]
extern crate syscall;

#[derive(Default)]
struct CompileState {
    output: String,
    loopnum: i32,
    ifnum: i32,
    outbuffsize: i32,
    level: usize,
}

/// Takes an offset from the current cell, and returns a string in assembly code
/// representing the register or memory region it is stored in
fn offset_to_operand(offset: i32) -> String {
    if offset == 0 {
        "%r12".to_string()
    } else {
        format!("{}(%rbx)", (offset * 8))
    }
}

fn compile_iter(state: &mut CompileState, tokens: &Vec<Token>) {
    state.level += 1;

    let mut outbuffpos = 0;
    for token in tokens {
        match *token {
            Add(offset, value) => {
                let dest = offset_to_operand(offset);
                if value == 1 && dest == "%r12" {
                    push_asm!(state, "inc %r12");
                } else if value >= 1 {
                    push_asm!(state, "addq ${}, {}", value, dest);
                } else if value == -1 && dest == "%r12" {
                    push_asm!(state, "dec %r12");
                } else if value <= -1 {
                    push_asm!(state, "subq ${}, {}", -value, dest);
                }
            }
            MulCopy(src_idx, dest_idx, mul) => {
                let mut src = offset_to_operand(src_idx);
                let dest = offset_to_operand(dest_idx);

                if mul != -1 && mul != 1 {
                    push_asm!(state, "movq {}, %rax", src);
                    push_asm!(state, "movq ${}, %rdx", mul.abs());
                    push_asm!(state, "mulq %rdx");
                    src = "%rax".to_string();
                } else if src != "%r12" && dest != "%r12" {
                    // x86 cannot move memory to memory
                    push_asm!(state, "movq {}, %rax", src);
                    src = "%rax".to_string();
                }

                if mul > 0 {
                    push_asm!(state, "addq {}, {}", src, dest);
                } else {
                    push_asm!(state, "subq {}, {}", src, dest);
                }
            }
            Set(offset, value) => {
                if offset == 0 && value == 0 {
                    push_asm!(state, "xor %r12, %r12");
                } else {
                    push_asm!(state, "movq ${}, {}", value, offset_to_operand(offset));
                }
            }
            Move(offset) => {
                if offset != 0 {
                    push_asm!(state, "movq %r12, (%rbx)");
                    push_asm!(
                        state,
                        "{add_sub} ${shift}, %rbx",
                        add_sub = if offset > 0 { "addq" } else { "subq" },
                        shift = offset.abs() * 8
                    );
                    push_asm!(state, "movq (%rbx), %r12");
                }
            }
            Loop(ref content) => {
                state.loopnum += 1;
                let curloop = state.loopnum;
                push_asm!(state, "jmp endloop{}", curloop);
                push_asm!(state, "loop{}:", curloop);

                compile_iter(state, &content);

                push_asm!(state, "endloop{}:", curloop);
                push_asm!(state, "test %r12, %r12");
                push_asm!(state, "jnz loop{}", curloop);
            }
            If(offset, ref content) => {
                state.ifnum += 1;
                let curif = state.ifnum;
                if offset == 0 {
                    push_asm!(state, "test %r12, %r12");
                } else {
                    push_asm!(state, "cmpq $0, {}(%rbx)", offset * 8);
                }
                push_asm!(state, "jz endif{}", curif);

                compile_iter(state, &content);

                push_asm!(state, "endif{}:\n", curif);
            }
            Scan(offset) => {
                // Slighly more optimal than normal loop and move
                state.loopnum += 1;
                push_asm!(state, "movq %r12, (%rbx)");
                push_asm!(state, "jmp endloop{}", state.loopnum);
                push_asm!(state, "loop{}:", state.loopnum);
                push_asm!(
                    state,
                    "{add_sub} ${shift}, %rbx",
                    add_sub = if offset > 0 { "addq" } else { "subq" },
                    shift = offset.abs() * 8
                );
                push_asm!(state, "endloop{}:", state.loopnum);
                push_asm!(state, "cmp $0, (%rbx)");
                push_asm!(state, "jnz loop{}", state.loopnum);
                push_asm!(state, "movq (%rbx), %r12");
            }
            Input => {
                push_asm!(state, "");

                #[cfg(target_os = "redox")]
                {
                    push_asm!(state, "movq ${}, %rax", syscall::SYS_READ);
                    push_asm!(state, "movq %rbx, %rcx");
                    push_asm!(state, "xor %rbx, %rbx");
                    push_asm!(state, "movq $1, %rdx");
                    push_asm!(state, "int $0x80");
                    push_asm!(state, "movq %rcx, %rbx");
                }

                #[cfg(not(target_os = "redox"))]
                {
                    push_asm!(state, "xor %rax, %rax");
                    push_asm!(state, "xor %rdi, %rdi");
                    push_asm!(state, "movq %rbx, %rsi");
                    push_asm!(state, "movq $1, %rdx");
                    push_asm!(state, "syscall");
                }

                push_asm!(state, "movq (%rbx), %r12\n");
            }
            LoadOut(offset, add) => {
                let outaddr = format!("(strbuff+{})", outbuffpos);
                if offset == 0 {
                    push_asm!(state, "movq %r12, {}", outaddr);
                } else {
                    push_asm!(state, "movq {}(%rbx), %rax", offset * 8);
                    push_asm!(state, "movq %rax, {}", outaddr);
                }
                if add > 0 {
                    push_asm!(state, "addb ${}, {}", add, outaddr);
                } else if add < 0 {
                    push_asm!(state, "subb ${}, {}", -add, outaddr);
                }
                outbuffpos += 1;
            }
            LoadOutSet(value) => {
                let outaddr = format!("(strbuff+{})", outbuffpos);
                push_asm!(state, "movq ${}, {}", value, outaddr);
                outbuffpos += 1;
            }
            Output => {
                #[cfg(target_os = "redox")]
                {
                    push_asm!(state, "movq ${}, %rax", syscall::SYS_WRITE);
                    push_asm!(state, "movq %rbx, %r11");
                    push_asm!(state, "movq $1, %rbx");
                    push_asm!(state, "movq $strbuff, %rcx");
                    push_asm!(state, "movq ${}, %rdx", outbuffpos);
                    push_asm!(state, "int $0x80");
                    push_asm!(state, "movq %r11, %rbx\n");
                }

                #[cfg(not(target_os = "redox"))]
                {
                    push_asm!(state, "movq $1, %rax");
                    push_asm!(state, "movq $1, %rdi");
                    push_asm!(state, "movq $strbuff, %rsi");
                    push_asm!(state, "movq ${}, %rdx", outbuffpos);
                    push_asm!(state, "syscall\n");
                }

                if state.outbuffsize < outbuffpos + 8 {
                    state.outbuffsize = outbuffpos + 8;
                }
                outbuffpos = 0;
            }
        }
    }

    state.level -= 1;
}

impl IsbfcIR {
    /// Compiles the intermediate representation to x86_64 Linux assembly
    /// returning a String
    pub fn compile(&self, tape_size: i32) -> String {
        let mut state = CompileState::default();

        compile_iter(&mut state, &self.tokens);

        // Exit syscall
        #[cfg(not(target_os = "redox"))]
        let exit = concat!(
            "    movq $60, %rax\n",
            "    movq $0, %rdi\n",
            "    syscall\n"
        );
        #[cfg(target_os = "redox")]
        let exit = format!(
            concat!(
                "    movq ${}, %rax\n",
                "    movq $0, %rdi\n",
                "    int $0x80\n"
            ),
            syscall::SYS_EXIT
        );

        format!(
            concat!(
                ".section .bss\n",
                "    .lcomm strbuff, {outbuffsize}\n",
                "    .lcomm mem, {}\n",
                "    .set startidx, mem + {}\n",
                ".section .text\n",
                ".global _start\n",
                "_start:\n",
                "    xor %r12, %r12\n",
                "    movq $startidx, %rbx\n\n",
                "{}\n",
                "{exit_syscall}\n",
            ),
            tape_size * 8,
            (tape_size / 2) * 8,
            state.output,
            outbuffsize = state.outbuffsize,
            exit_syscall = exit
        )
    }
}
<commit_msg>inc and dec can be used on memory addresses<commit_after>use std::fmt::Write;

use crate::token::Token;
use crate::token::Token::*;
use crate::IsbfcIR;

#[cfg(target_os = "redox")]
extern crate syscall;

#[derive(Default)]
struct CompileState {
    output: String,
    loopnum: i32,
    ifnum: i32,
    outbuffsize: i32,
    level: usize,
}

/// Takes an offset from the current cell, and returns a string in assembly code
/// representing the register or memory region it is stored in
fn offset_to_operand(offset: i32) -> String {
    if offset == 0 {
        "%r12".to_string()
    } else {
        format!("{}(%rbx)", (offset * 8))
    }
}

fn compile_iter(state: &mut CompileState, tokens: &Vec<Token>) {
    state.level += 1;

    let mut outbuffpos = 0;
    for token in tokens {
        match *token {
            Add(offset, value) => {
                let dest = offset_to_operand(offset);
                if value == 1 {
                    push_asm!(state, "incq {}", dest);
                } else if value == -1 {
                    push_asm!(state, "decq {}", dest);
                } else if value > 1 {
                    push_asm!(state, "addq ${}, {}", value, dest);
                } else if value < -1 {
                    push_asm!(state, "subq ${}, {}", -value, dest);
                }
            }
            MulCopy(src_idx, dest_idx, mul) => {
                let mut src = offset_to_operand(src_idx);
                let dest = offset_to_operand(dest_idx);

                if mul != -1 && mul != 1 {
                    push_asm!(state, "movq {}, %rax", src);
                    push_asm!(state, "movq ${}, %rdx", mul.abs());
                    push_asm!(state, "mulq %rdx");
                    src = "%rax".to_string();
                } else if src != "%r12" && dest != "%r12" {
                    // x86 cannot move memory to memory
                    push_asm!(state, "movq {}, %rax", src);
                    src = "%rax".to_string();
                }

                if mul > 0 {
                    push_asm!(state, "addq {}, {}", src, dest);
                } else {
                    push_asm!(state, "subq {}, {}", src, dest);
                }
            }
            Set(offset, value) => {
                if offset == 0 && value == 0 {
                    push_asm!(state, "xor %r12, %r12");
                } else {
                    push_asm!(state, "movq ${}, {}", value, offset_to_operand(offset));
                }
            }
            Move(offset) => {
                if offset != 0 {
                    push_asm!(state, "movq %r12, (%rbx)");
                    push_asm!(
                        state,
                        "{add_sub} ${shift}, %rbx",
                        add_sub = if offset > 0 { "addq" } else { "subq" },
                        shift = offset.abs() * 8
                    );
                    push_asm!(state, "movq (%rbx), %r12");
                }
            }
            Loop(ref content) => {
                state.loopnum += 1;
                let curloop = state.loopnum;
                push_asm!(state, "jmp endloop{}", curloop);
                push_asm!(state, "loop{}:", curloop);

                compile_iter(state, &content);

                push_asm!(state, "endloop{}:", curloop);
                push_asm!(state, "test %r12, %r12");
                push_asm!(state, "jnz loop{}", curloop);
            }
            If(offset, ref content) => {
                state.ifnum += 1;
                let curif = state.ifnum;
                if offset == 0 {
                    push_asm!(state, "test %r12, %r12");
                } else {
                    push_asm!(state, "cmpq $0, {}(%rbx)", offset * 8);
                }
                push_asm!(state, "jz endif{}", curif);

                compile_iter(state, &content);

                push_asm!(state, "endif{}:\n", curif);
            }
            Scan(offset) => {
                // Slighly more optimal than normal loop and move
                state.loopnum += 1;
                push_asm!(state, "movq %r12, (%rbx)");
                push_asm!(state, "jmp endloop{}", state.loopnum);
                push_asm!(state, "loop{}:", state.loopnum);
                push_asm!(
                    state,
                    "{add_sub} ${shift}, %rbx",
                    add_sub = if offset > 0 { "addq" } else { "subq" },
                    shift = offset.abs() * 8
                );
                push_asm!(state, "endloop{}:", state.loopnum);
                push_asm!(state, "cmp $0, (%rbx)");
                push_asm!(state, "jnz loop{}", state.loopnum);
                push_asm!(state, "movq (%rbx), %r12");
            }
            Input => {
                push_asm!(state, "");

                #[cfg(target_os = "redox")]
                {
                    push_asm!(state, "movq ${}, %rax", syscall::SYS_READ);
                    push_asm!(state, "movq %rbx, %rcx");
                    push_asm!(state, "xor %rbx, %rbx");
                    push_asm!(state, "movq $1, %rdx");
                    push_asm!(state, "int $0x80");
                    push_asm!(state, "movq %rcx, %rbx");
                }

                #[cfg(not(target_os = "redox"))]
                {
                    push_asm!(state, "xor %rax, %rax");
                    push_asm!(state, "xor %rdi, %rdi");
                    push_asm!(state, "movq %rbx, %rsi");
                    push_asm!(state, "movq $1, %rdx");
                    push_asm!(state, "syscall");
                }

                push_asm!(state, "movq (%rbx), %r12\n");
            }
            LoadOut(offset, add) => {
                let outaddr = format!("(strbuff+{})", outbuffpos);
                if offset == 0 {
                    push_asm!(state, "movq %r12, {}", outaddr);
                } else {
                    push_asm!(state, "movq {}(%rbx), %rax", offset * 8);
                    push_asm!(state, "movq %rax, {}", outaddr);
                }
                if add > 0 {
                    push_asm!(state, "addb ${}, {}", add, outaddr);
                } else if add < 0 {
                    push_asm!(state, "subb ${}, {}", -add, outaddr);
                }
                outbuffpos += 1;
            }
            LoadOutSet(value) => {
                let outaddr = format!("(strbuff+{})", outbuffpos);
                push_asm!(state, "movq ${}, {}", value, outaddr);
                outbuffpos += 1;
            }
            Output => {
                #[cfg(target_os = "redox")]
                {
                    push_asm!(state, "movq ${}, %rax", syscall::SYS_WRITE);
                    push_asm!(state, "movq %rbx, %r11");
                    push_asm!(state, "movq $1, %rbx");
                    push_asm!(state, "movq $strbuff, %rcx");
                    push_asm!(state, "movq ${}, %rdx", outbuffpos);
                    push_asm!(state, "int $0x80");
                    push_asm!(state, "movq %r11, %rbx\n");
                }

                #[cfg(not(target_os = "redox"))]
                {
                    push_asm!(state, "movq $1, %rax");
                    push_asm!(state, "movq $1, %rdi");
                    push_asm!(state, "movq $strbuff, %rsi");
                    push_asm!(state, "movq ${}, %rdx", outbuffpos);
                    push_asm!(state, "syscall\n");
                }

                if state.outbuffsize < outbuffpos + 8 {
                    state.outbuffsize = outbuffpos + 8;
                }
                outbuffpos = 0;
            }
        }
    }

    state.level -= 1;
}

impl IsbfcIR {
    /// Compiles the intermediate representation to x86_64 Linux assembly
    /// returning a String
    pub fn compile(&self, tape_size: i32) -> String {
        let mut state = CompileState::default();

        compile_iter(&mut state, &self.tokens);

        // Exit syscall
        #[cfg(not(target_os = "redox"))]
        let exit = concat!(
            "    movq $60, %rax\n",
            "    movq $0, %rdi\n",
            "    syscall\n"
        );
        #[cfg(target_os = "redox")]
        let exit = format!(
            concat!(
                "    movq ${}, %rax\n",
                "    movq $0, %rdi\n",
                "    int $0x80\n"
            ),
            syscall::SYS_EXIT
        );

        format!(
            concat!(
                ".section .bss\n",
                "    .lcomm strbuff, {outbuffsize}\n",
                "    .lcomm mem, {}\n",
                "    .set startidx, mem + {}\n",
                ".section .text\n",
                ".global _start\n",
                "_start:\n",
                "    xor %r12, %r12\n",
                "    movq $startidx, %rbx\n\n",
                "{}\n",
                "{exit_syscall}\n",
            ),
            tape_size * 8,
            (tape_size / 2) * 8,
            state.output,
            outbuffsize = state.outbuffsize,
            exit_syscall = exit
        )
    }
}
<|endoftext|>


<commit_before><commit_msg>add approx_eq for r2::Point<commit_after><|endoftext|>


<commit_before><commit_msg>Increased length of REDIRECT_FLAGS to 3<commit_after><|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/.

//!Media module
//!
//!Provides functionality to upload images, GIFs and videos using Twitter Media API

use std::collections::HashMap;
use error;
use error::Error::{
    InvalidResponse,
    MissingValue
};

use rustc_serialize::{
    json,
    base64
};
use self::base64::{ToBase64};

use common::*;
use links;
use auth;

pub use hyper::mime;

#[derive(Debug)]
///Media's upload progressing info.
pub enum ProgressInfo {
    ///Video is pending for processing. Contains number of seconds after which to check.
    Pending(u64),
    ///Video is beeing processed. Contains number of seconds after which to check.
    InProgress(u64),
    ///Video's processing failed. Contains reason.
    Failed(String),
    ///Video's processing is finished. Media can be used in other API calls.
    Success
}

impl FromJson for ProgressInfo {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        field_present!(input, state);
        let state: String = try!(field(input, "state"));

        match state.as_ref() {
            "pending" => {
                field_present!(input, check_after_secs);
                Ok(ProgressInfo::Pending(try!(field(input, "check_after_secs"))))
            },
            "in_progress" => {
                field_present!(input, check_after_secs);
                Ok(ProgressInfo::InProgress(try!(field(input, "check_after_secs"))))
            },
            "failed" => {
                let error = try!(input.find("error").ok_or(MissingValue("error")));

                let name = try!(error.find("name").ok_or(MissingValue("name")));
                let message = try!(error.find("message").ok_or(MissingValue("message")));

                let name = try!(name.as_string().ok_or(InvalidResponse("Expected string in error's name", None)));
                let message = try!(message.as_string().ok_or(InvalidResponse("Expected string in error's message", None)));
                Ok(ProgressInfo::Failed(format!("{}: {}", name, message)))
            },
            "succeeded" => Ok(ProgressInfo::Success),
            state => Err(InvalidResponse("Unexpected progress info state", Some(state.to_string())))

        }
    }
}

///Represents media file that is uploaded on twitter.
pub struct Media {
    ///ID that can be used in API calls (e.g. attach to tweet).
    pub id: u64,
    ///Number of second the media can be used in other API calls.
    pub expires_after: u64,
    ///Progress information. If present determines whether Media can be used.
    pub progress: Option<ProgressInfo>
}

impl FromJson for Media {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("Tweet received json that wasn't an object", Some(input.to_string())));
        }

        field_present!(input, media_id);

        Ok(Media {
            id: try!(field(input, "media_id")),
            //We can miss this field on failed upload in which case 0 is pretty reasonable value.
            expires_after: field(input, "expires_after_secs").unwrap_or(0),
            progress: try!(field(input, "processing_info"))
        })
    }
}

///Uploads image using old twitter API.
///
///The image should be raw binary with content of file.
pub fn upload_image(image: &[u8], token: &auth::Token, handle: &Handle) -> FutureResponse<Media> {
    let mut params = HashMap::new();

    let config = base64::Config {
        char_set: base64::CharacterSet::Standard,
        newline: base64::Newline::LF,
        pad: true,
        line_length: None,
    };

    add_param(&mut params, "media_data", image.to_base64(config));

    let req = auth::post(links::medias::UPLOAD, token, Some(&params));
    make_parsed_future(handle, req)
}

///Content upload module using new chunked API.
///
///See [example](https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload)
pub mod upload {
    use super::*;

    ///Sends INIT message to twitter API in order to initiate upload.
    ///
    ///## Parameters:
    ///
    ///* total - The size of media file being uploaded(in bytes).
    ///* mime - File's mime type.
    pub fn init(total: usize, mime: mime::Mime, token: &auth::Token, handle: &Handle) -> FutureResponse<Media> {
        let mut params = HashMap::new();

        add_param(&mut params, "command", "INIT");
        add_param(&mut params, "total_bytes", total.to_string());
        add_param(&mut params, "media_type", mime.to_string());

        let req = auth::post(links::medias::UPLOAD, token, Some(&params));
        make_parsed_future(handle, req)
    }

    ///Sends APPEND message to twitter API in order to send chunk of media.
    ///
    ///## Parameters:
    ///
    ///* id - Media's id returned in response to `INIT` message.
    ///* chunk - Bytes to send.
    ///* index - Ordered index of file chunk.
    pub fn append(id: u64, chunk: &[u8], index: usize, token: &auth::Token, handle: &Handle) -> FutureResponse<()> {
        let mut params = HashMap::new();

        let config = base64::Config {
            char_set: base64::CharacterSet::Standard,
            newline: base64::Newline::LF,
            pad: true,
            line_length: None,
        };

        add_param(&mut params, "command", "APPEND");
        add_param(&mut params, "media_id", id.to_string());
        add_param(&mut params, "media_data", chunk.to_base64(config));
        add_param(&mut params, "segment_index", index.to_string());

        let req = auth::post(links::medias::UPLOAD, token, Some(&params));
        make_parsed_future(handle, req)
    }

    ///Sends FINALIE message to twitter API in order to finish sending of media.
    ///
    ///## Parameters:
    ///
    ///* id - Media's id returned in response to `INIT` message.
    pub fn finalize(id: u64, token: &auth::Token, handle: &Handle) -> FutureResponse<Media> {
        let mut params = HashMap::new();

        add_param(&mut params, "command", "FINALIZE");
        add_param(&mut params, "media_id", id.to_string());

        let req = auth::post(links::medias::UPLOAD, token, Some(&params));
        make_parsed_future(handle, req)
    }

    ///Sends STATUS message to twitter API in order to retrieve media info.
    pub fn status(id: u64, token: &auth::Token, handle: &Handle) -> FutureResponse<Media> {
        let mut params = HashMap::new();

        add_param(&mut params, "command", "STATUS");
        add_param(&mut params, "media_id", id.to_string());

        let req = auth::get(links::medias::UPLOAD, token, Some(&params));
        make_parsed_future(handle, req)
    }
}

#[cfg(test)]
mod tests {
    use common::FromJson;

    use super::Media;

    use std::fs::File;
    use std::io::Read;

    fn load_media(path: &str) -> Media {
        let mut file = File::open(path).unwrap();
        let mut content = String::new();
        file.read_to_string(&mut content).unwrap();
        Media::from_str(&content).unwrap()
    }

    #[test]
    fn parse_media() {
        let media = load_media("src/media/media.json");

        assert_eq!(media.id, 710511363345354753);
        assert_eq!(media.expires_after, 86400);
    }

    #[test]
    fn parse_media_pending() {
        let media = load_media("src/media/media_pending.json");

        assert_eq!(media.id, 13);
        assert_eq!(media.expires_after, 86400);
        assert!(media.progress.is_some());

        match media.progress {
            Some(super::ProgressInfo::Pending(5)) => (),
            other => assert!(false, format!("Unexpected value of progress={:?}", other))
        }
    }

    #[test]
    fn parse_media_in_progress() {
        let media = load_media("src/media/media_in_progress.json");

        assert_eq!(media.id, 13);
        assert_eq!(media.expires_after, 3595);
        assert!(media.progress.is_some());

        match media.progress {
            Some(super::ProgressInfo::InProgress(10)) => (),
            other => assert!(false, format!("Unexpected value of progress={:?}", other))
        }
    }

    #[test]
    fn parse_media_fail() {
        let media = load_media("src/media/media_fail.json");

        assert_eq!(media.id, 710511363345354753);
        assert_eq!(media.expires_after, 0);
        assert!(media.progress.is_some());

        match media.progress {
            Some(super::ProgressInfo::Failed(error)) => assert_eq!(error, "InvalidMedia: Unsupported video format"),
            other => assert!(false, format!("Unexpected value of progress={:?}", other))
        }
    }
}
<commit_msg>UploadFuture<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/.

//!Media module
//!
//!Provides functionality to upload images, GIFs and videos using Twitter Media API

use std::borrow::Cow;
use std::collections::HashMap;

use futures::{Future, Async, Poll};
use rustc_serialize::{json, base64};
use rustc_serialize::base64::{ToBase64};

use common::*;
use error;
use error::Error::{InvalidResponse, MissingValue};
use links;
use auth;

use hyper::mime;

#[derive(Debug)]
///Media's upload progressing info.
pub enum ProgressInfo {
    ///Video is pending for processing. Contains number of seconds after which to check.
    Pending(u64),
    ///Video is beeing processed. Contains number of seconds after which to check.
    InProgress(u64),
    ///Video's processing failed. Contains reason.
    Failed(String),
    ///Video's processing is finished. Media can be used in other API calls.
    Success
}

impl FromJson for ProgressInfo {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        field_present!(input, state);
        let state: String = try!(field(input, "state"));

        match state.as_ref() {
            "pending" => {
                field_present!(input, check_after_secs);
                Ok(ProgressInfo::Pending(try!(field(input, "check_after_secs"))))
            },
            "in_progress" => {
                field_present!(input, check_after_secs);
                Ok(ProgressInfo::InProgress(try!(field(input, "check_after_secs"))))
            },
            "failed" => {
                let error = try!(input.find("error").ok_or(MissingValue("error")));

                let name = try!(error.find("name").ok_or(MissingValue("name")));
                let message = try!(error.find("message").ok_or(MissingValue("message")));

                let name = try!(name.as_string().ok_or(InvalidResponse("Expected string in error's name", None)));
                let message = try!(message.as_string().ok_or(InvalidResponse("Expected string in error's message", None)));
                Ok(ProgressInfo::Failed(format!("{}: {}", name, message)))
            },
            "succeeded" => Ok(ProgressInfo::Success),
            state => Err(InvalidResponse("Unexpected progress info state", Some(state.to_string())))

        }
    }
}

///Represents media file that is uploaded on twitter.
pub struct Media {
    ///ID that can be used in API calls (e.g. attach to tweet).
    pub id: u64,
    ///Number of second the media can be used in other API calls.
    pub expires_after: u64,
    ///Progress information. If present determines whether Media can be used.
    pub progress: Option<ProgressInfo>
}

impl FromJson for Media {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("Tweet received json that wasn't an object", Some(input.to_string())));
        }

        field_present!(input, media_id);

        Ok(Media {
            id: try!(field(input, "media_id")),
            //We can miss this field on failed upload in which case 0 is pretty reasonable value.
            expires_after: field(input, "expires_after_secs").unwrap_or(0),
            progress: try!(field(input, "processing_info"))
        })
    }
}

///Uploads image using old twitter API.
///
///The image should be raw binary with content of file.
pub fn upload_image(image: &[u8], token: &auth::Token, handle: &Handle) -> FutureResponse<Media> {
    let mut params = HashMap::new();

    let config = base64::Config {
        char_set: base64::CharacterSet::Standard,
        newline: base64::Newline::LF,
        pad: true,
        line_length: None,
    };

    add_param(&mut params, "media_data", image.to_base64(config));

    let req = auth::post(links::medias::UPLOAD, token, Some(&params));
    make_parsed_future(handle, req)
}

/// A `Future` that represents an in-progress media upload.
pub struct UploadFuture<'a> {
    data: Cow<'a, [u8]>,
    token: auth::Token,
    handle: Handle,
    chunk_size: usize,
    status: UploadInner,
}

/// The current status of an `UploadFuture`.
enum UploadInner {
    /// The `UploadFuture` is waiting to initialize the media upload session.
    WaitingForInit(FutureResponse<Media>),
    /// The `UploadFuture` is in the progress of uploading data.
    UploadingChunk(u64, usize, FutureResponse<()>),
    /// The `UploadFuture` is currently finalizing the media with Twitter.
    Finalizing(FutureResponse<Media>),
    /// The `UploadFuture` has completed, or has encountered an error.
    Invalid,
}

impl<'a> UploadFuture<'a> {
    /// Creates a new `UploadFuture` with the given data and media type, and with the default chunk
    /// size of 512 KiB.
    pub fn new<V: Into<Cow<'a, [u8]>>>(data: V,
                                       media_type: mime::Mime,
                                       token: &auth::Token,
                                       handle: &Handle)
        -> UploadFuture<'a>
    {
        let data = data.into();
        let loader = upload::init(data.len(), media_type, token, handle);
        UploadFuture {
            data,
            token: token.clone(),
            handle: handle.clone(),
            chunk_size: 1024 * 512, // 512 KiB
            status: UploadInner::WaitingForInit(loader),
        }
    }

    /// Creates a new `UploadFuture` with the given data, media type, and chunk size.
    pub fn with_chunk_size<V: Into<Cow<'a, [u8]>>>(data: V,
                                                   chunk_size: usize,
                                                   media_type: mime::Mime,
                                                   token: &auth::Token,
                                                   handle: &Handle)
        -> UploadFuture<'a>
    {
        let data = data.into();
        let loader = upload::init(data.len(), media_type, token, handle);
        UploadFuture {
            data,
            token: token.clone(),
            handle: handle.clone(),
            chunk_size,
            status: UploadInner::WaitingForInit(loader),
        }
    }

    fn get_chunk(&self, chunk_num: usize) -> Option<&[u8]> {
        let start = chunk_num * self.chunk_size;
        let end = (chunk_num + 1) * self.chunk_size;

        if start >= self.data.len() {
            None
        } else if end >= self.data.len() {
            Some(&self.data[start..])
        } else {
            Some(&self.data[start..end])
        }
    }

    fn append(&self, chunk_num: usize, media_id: u64) -> Option<FutureResponse<()>> {
        let mut chunk = self.get_chunk(chunk_num);
        if chunk.is_none() && chunk_num == 0 {
            chunk = Some(&[][..]);
        }

        if let Some(chunk) = chunk {
            Some(upload::append(media_id, chunk, chunk_num, &self.token, &self.handle))
        } else {
            None
        }
    }
}

impl<'a> Future for UploadFuture<'a> {
    type Item = Response<Media>;
    type Error = error::Error;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        use std::mem::replace;

        match replace(&mut self.status, UploadInner::Invalid) {
            UploadInner::WaitingForInit(mut init) => {
                match init.poll() {
                    Ok(Async::NotReady) => {
                        self.status = UploadInner::WaitingForInit(init);
                        Ok(Async::NotReady)
                    },
                    Ok(Async::Ready(media)) => {
                        let id = media.id;
                        let loader = self.append(0, id).unwrap();
                        self.status = UploadInner::UploadingChunk(id, 0, loader);
                        self.poll()
                    },
                    Err(e) => Err(e),
                }
            },
            UploadInner::UploadingChunk(id, chunk_idx, mut upload) => {
                match upload.poll() {
                    Ok(Async::NotReady) => {
                        self.status = UploadInner::UploadingChunk(id, chunk_idx, upload);
                        Ok(Async::NotReady)
                    },
                    Ok(Async::Ready(_)) => {
                        let chunk_idx = chunk_idx + 1;
                        if let Some(upload) = self.append(chunk_idx, id) {
                            self.status = UploadInner::UploadingChunk(id, chunk_idx, upload);
                        } else {
                            let loader = upload::finalize(id, &self.token, &self.handle);
                            self.status = UploadInner::Finalizing(loader);
                        }

                        self.poll()
                    },
                    Err(e) => Err(e),
                }
            },
            UploadInner::Finalizing(mut finalize) => {
                match finalize.poll() {
                    Ok(Async::NotReady) => {
                        self.status = UploadInner::Finalizing(finalize);
                        Ok(Async::NotReady)
                    },
                    status => status,
                }
            },
            UploadInner::Invalid => Err(error::Error::FutureAlreadyCompleted),
        }
    }
}

///Content upload module using new chunked API.
///
///See [example](https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload)
pub mod upload {
    use super::*;

    ///Sends INIT message to twitter API in order to initiate upload.
    ///
    ///## Parameters:
    ///
    ///* total - The size of media file being uploaded(in bytes).
    ///* mime - File's mime type.
    pub fn init(total: usize, mime: mime::Mime, token: &auth::Token, handle: &Handle) -> FutureResponse<Media> {
        let mut params = HashMap::new();

        add_param(&mut params, "command", "INIT");
        add_param(&mut params, "total_bytes", total.to_string());
        add_param(&mut params, "media_type", mime.to_string());

        let req = auth::post(links::medias::UPLOAD, token, Some(&params));
        make_parsed_future(handle, req)
    }

    ///Sends APPEND message to twitter API in order to send chunk of media.
    ///
    ///## Parameters:
    ///
    ///* id - Media's id returned in response to `INIT` message.
    ///* chunk - Bytes to send.
    ///* index - Ordered index of file chunk.
    pub fn append(id: u64, chunk: &[u8], index: usize, token: &auth::Token, handle: &Handle) -> FutureResponse<()> {
        let mut params = HashMap::new();

        let config = base64::Config {
            char_set: base64::CharacterSet::Standard,
            newline: base64::Newline::LF,
            pad: true,
            line_length: None,
        };

        add_param(&mut params, "command", "APPEND");
        add_param(&mut params, "media_id", id.to_string());
        add_param(&mut params, "media_data", chunk.to_base64(config));
        add_param(&mut params, "segment_index", index.to_string());

        let req = auth::post(links::medias::UPLOAD, token, Some(&params));
        make_parsed_future(handle, req)
    }

    ///Sends FINALIE message to twitter API in order to finish sending of media.
    ///
    ///## Parameters:
    ///
    ///* id - Media's id returned in response to `INIT` message.
    pub fn finalize(id: u64, token: &auth::Token, handle: &Handle) -> FutureResponse<Media> {
        let mut params = HashMap::new();

        add_param(&mut params, "command", "FINALIZE");
        add_param(&mut params, "media_id", id.to_string());

        let req = auth::post(links::medias::UPLOAD, token, Some(&params));
        make_parsed_future(handle, req)
    }

    ///Sends STATUS message to twitter API in order to retrieve media info.
    pub fn status(id: u64, token: &auth::Token, handle: &Handle) -> FutureResponse<Media> {
        let mut params = HashMap::new();

        add_param(&mut params, "command", "STATUS");
        add_param(&mut params, "media_id", id.to_string());

        let req = auth::get(links::medias::UPLOAD, token, Some(&params));
        make_parsed_future(handle, req)
    }
}

#[cfg(test)]
mod tests {
    use common::FromJson;

    use super::Media;

    use std::fs::File;
    use std::io::Read;

    fn load_media(path: &str) -> Media {
        let mut file = File::open(path).unwrap();
        let mut content = String::new();
        file.read_to_string(&mut content).unwrap();
        Media::from_str(&content).unwrap()
    }

    #[test]
    fn parse_media() {
        let media = load_media("src/media/media.json");

        assert_eq!(media.id, 710511363345354753);
        assert_eq!(media.expires_after, 86400);
    }

    #[test]
    fn parse_media_pending() {
        let media = load_media("src/media/media_pending.json");

        assert_eq!(media.id, 13);
        assert_eq!(media.expires_after, 86400);
        assert!(media.progress.is_some());

        match media.progress {
            Some(super::ProgressInfo::Pending(5)) => (),
            other => assert!(false, format!("Unexpected value of progress={:?}", other))
        }
    }

    #[test]
    fn parse_media_in_progress() {
        let media = load_media("src/media/media_in_progress.json");

        assert_eq!(media.id, 13);
        assert_eq!(media.expires_after, 3595);
        assert!(media.progress.is_some());

        match media.progress {
            Some(super::ProgressInfo::InProgress(10)) => (),
            other => assert!(false, format!("Unexpected value of progress={:?}", other))
        }
    }

    #[test]
    fn parse_media_fail() {
        let media = load_media("src/media/media_fail.json");

        assert_eq!(media.id, 710511363345354753);
        assert_eq!(media.expires_after, 0);
        assert!(media.progress.is_some());

        match media.progress {
            Some(super::ProgressInfo::Failed(error)) => assert_eq!(error, "InvalidMedia: Unsupported video format"),
            other => assert!(false, format!("Unexpected value of progress={:?}", other))
        }
    }
}
<|endoftext|>


<commit_before><commit_msg>fix a test failure<commit_after><|endoftext|>


<commit_before>use glutin::event_loop::EventLoopProxy;
use tokio::{time, sync::Mutex};
use interpolation::lerp;

use crate::ipc_protocol::{
    ServerConnection,
    ServerResponse,
    RotationDirection,
};
use crate::renderer_client::ClientId;
use crate::radians::Radians;
use crate::{Distance, Point};

use super::super::{
    RequestRedraw,
    state::TurtleState,
    app::{TurtleId, TurtleDrawings},
    access_control::{AccessControl, RequiredData, RequiredTurtles},
    renderer::display_list::{DisplayList, PrimHandle},
};

/// Frames per second - The number of times the animation will update per second
const FPS: u64 = 60;

// 1,000,000 us in 1 s
const MICROS_PER_SEC: u64 = 1_000_000;

pub(crate) async fn move_forward(
    conn: &ServerConnection,
    client_id: ClientId,
    app_control: &AccessControl,
    display_list: &Mutex<DisplayList>,
    event_loop: &Mutex<EventLoopProxy<RequestRedraw>>,
    id: TurtleId,
    distance: Distance,
) {
    let mut data = app_control.get(RequiredData {
        drawing: false,
        turtles: Some(RequiredTurtles::One(id)),
    }).await;

    // Borrow the data initially to setup the animation but then drop it so the locks on the turtle
    // and display list are released (allows for rendering to occur)
    let mut anim = {
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();

        let TurtleState {position, heading, ..} = turtle.state;

        // The total amount we'll move in the x and y directions
        let movement = Point {
            x: distance * heading.cos(),
            y: distance * heading.sin(),
        };
        let target_pos = position + movement;

        let mut display_list = display_list.lock().await;
        MoveAnimation::new(turtle, &mut display_list, target_pos)
    };

    while anim.running {
        // Signal the main thread that the image has changed
        event_loop.lock().await.send_event(RequestRedraw)
            .expect("bug: event loop closed before animation completed");

        // Sleep until it is time to update the animation again
        anim.timer.tick().await;

        // These locks are dropped at the end of this loop iteration to allow rendering to occur
        // while we wait
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();
        let mut display_list = display_list.lock().await;

        anim.step(turtle, &mut display_list);
    }

    conn.send(client_id, ServerResponse::AnimationComplete(id)).await
        .expect("unable to send response to IPC client");
}

pub(crate) async fn move_to(
    conn: &ServerConnection,
    client_id: ClientId,
    app_control: &AccessControl,
    display_list: &Mutex<DisplayList>,
    event_loop: &Mutex<EventLoopProxy<RequestRedraw>>,
    id: TurtleId,
    target_pos: Point,
) {
    let mut data = app_control.get(RequiredData {
        drawing: false,
        turtles: Some(RequiredTurtles::One(id)),
    }).await;

    // Borrow the data initially to setup the animation but then drop it so the locks on the turtle
    // and display list are released (allows for rendering to occur)
    let mut anim = {
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();

        let mut display_list = display_list.lock().await;
        MoveAnimation::new(turtle, &mut display_list, target_pos)
    };

    while anim.running {
        // Signal the main thread that the image has changed
        event_loop.lock().await.send_event(RequestRedraw)
            .expect("bug: event loop closed before animation completed");

        // Sleep until it is time to update the animation again
        anim.timer.tick().await;

        // These locks are dropped at the end of this loop iteration to allow rendering to occur
        // while we wait
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();
        let mut display_list = display_list.lock().await;

        anim.step(turtle, &mut display_list);
    }

    conn.send(client_id, ServerResponse::AnimationComplete(id)).await
        .expect("unable to send response to IPC client");
}

struct MoveAnimation {
    /// true if the animation should continue, false if it should stop
    running: bool,
    /// A timer used to delay the execution of the animation to a reasonable FPS
    timer: time::Interval,
    /// The instant that the animation started, used to precisely determine how long the animation
    /// has been running
    start: time::Instant,
    /// The position that the turtle started at
    start_pos: Point,
    /// The target position to move to (i.e. the final value of the animation)
    target_pos: Point,
    /// The total duration of the animation in microseconds
    total_micros: f64,
    /// A handle to the line that is manipulated by thsi animation
    prim: PrimHandle,
}

impl MoveAnimation {
    fn new(
        turtle: &mut TurtleDrawings,
        display_list: &mut DisplayList,
        target_pos: Point,
    ) -> Self {
        let frame_duration = time::Duration::from_millis(MICROS_PER_SEC / FPS);
        // Need to start at now() + frame_duration or else timer will initially tick for 0 seconds
        let timer = time::interval_at(time::Instant::now() + frame_duration, frame_duration);

        let TurtleState {position, speed, ref pen, ..} = turtle.state;

        if speed.is_instant() {
            // Set to the final position and draw a line with no animation
            turtle.state.position = target_pos;
            let prim = display_list.push_line(position, target_pos, pen);
            turtle.drawings.push(prim);

            if let Some(poly_handle) = turtle.current_fill_polygon {
                //TODO: Push point into polygon
                todo!()
            }

            Self {
                // stop the animation right away since it has already completed
                running: false,
                timer,
                start: time::Instant::now(),
                start_pos: position,
                target_pos,
                total_micros: 0.0,
                prim,
            }

        } else {
            let px_per_sec = speed.to_px_per_sec();
            let abs_distance = (target_pos - position).len();
            // Use microseconds instead of ms for greater precision
            let total_micros = abs_distance * MICROS_PER_SEC as f64 / px_per_sec;

            // No need to update position since the turtle hasn't move anywhere yet

            // Start with a zero-length line since the animation hasn't started yet
            let prim = display_list.push_line(position, position, pen);
            turtle.drawings.push(prim);

            if let Some(poly_handle) = turtle.current_fill_polygon {
                //TODO: Push point into polygon
                todo!()
            }

            Self {
                running: true,
                timer,
                start: time::Instant::now(),
                start_pos: position,
                target_pos,
                total_micros,
                prim,
            }
        }

    }

    /// Advances the animation based on the amount of time that has elapsed so far
    fn step(&mut self, turtle: &mut TurtleDrawings, display_list: &mut DisplayList) {
        let &mut Self {
            ref mut running,
            timer: _,
            ref mut start,
            start_pos,
            target_pos,
            total_micros,
            prim,
        } = self;

        let elapsed = start.elapsed().as_micros() as f64;
        if elapsed >= total_micros {
            *running = false;
            turtle.state.position = target_pos;
            display_list.replace_line(prim, start_pos, target_pos, &turtle.state.pen);

            if let Some(poly_handle) = turtle.current_fill_polygon {
                //TODO: Replace point in fill polygon
                todo!()
            }

        } else {
            // t is the total progress made in the animation so far
            let t = elapsed / total_micros;
            let current_pos = lerp(&start_pos, &target_pos, &t);

            turtle.state.position = current_pos;
            display_list.replace_line(prim, start_pos, current_pos, &turtle.state.pen);

            if let Some(poly_handle) = turtle.current_fill_polygon {
                //TODO: Replace point in fill polygon
                todo!()
            }
        }
    }
}

pub(crate) async fn rotate_in_place(
    conn: &ServerConnection,
    client_id: ClientId,
    app_control: &AccessControl,
    event_loop: &Mutex<EventLoopProxy<RequestRedraw>>,
    id: TurtleId,
    angle: Radians,
    direction: RotationDirection,
) {
    let mut data = app_control.get(RequiredData {
        drawing: false,
        turtles: Some(RequiredTurtles::One(id)),
    }).await;
    let mut turtles = data.turtles_mut().await;

    let TurtleDrawings {state: turtle, ..} = turtles.one_mut();

    //TODO

    conn.send(client_id, ServerResponse::AnimationComplete(id)).await
        .expect("unable to send response to IPC client");
}
<commit_msg>Implemented rotation animation<commit_after>use glutin::event_loop::EventLoopProxy;
use tokio::{time, sync::Mutex};
use interpolation::lerp;

use crate::ipc_protocol::{
    ServerConnection,
    ServerResponse,
    RotationDirection,
};
use crate::renderer_client::ClientId;
use crate::radians::{self, Radians};
use crate::{Distance, Point};

use super::super::{
    RequestRedraw,
    state::TurtleState,
    app::{TurtleId, TurtleDrawings},
    access_control::{AccessControl, RequiredData, RequiredTurtles},
    renderer::display_list::{DisplayList, PrimHandle},
};

/// Frames per second - The number of times the animation will update per second
const FPS: u64 = 60;

// 1,000,000 us in 1 s
const MICROS_PER_SEC: u64 = 1_000_000;

pub(crate) async fn move_forward(
    conn: &ServerConnection,
    client_id: ClientId,
    app_control: &AccessControl,
    display_list: &Mutex<DisplayList>,
    event_loop: &Mutex<EventLoopProxy<RequestRedraw>>,
    id: TurtleId,
    distance: Distance,
) {
    let mut data = app_control.get(RequiredData {
        drawing: false,
        turtles: Some(RequiredTurtles::One(id)),
    }).await;

    // Borrow the data initially to setup the animation but then drop it so the locks on the turtle
    // and display list are released (allows for rendering to occur)
    let mut anim = {
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();

        let TurtleState {position, heading, ..} = turtle.state;

        // The total amount we'll move in the x and y directions
        let movement = Point {
            x: distance * heading.cos(),
            y: distance * heading.sin(),
        };
        let target_pos = position + movement;

        let mut display_list = display_list.lock().await;
        MoveAnimation::new(turtle, &mut display_list, target_pos)
    };

    while anim.running {
        // Signal the main thread that the image has changed
        event_loop.lock().await.send_event(RequestRedraw)
            .expect("bug: event loop closed before animation completed");

        // Sleep until it is time to update the animation again
        anim.timer.tick().await;

        // These locks are dropped at the end of this loop iteration to allow rendering to occur
        // while we wait
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();
        let mut display_list = display_list.lock().await;

        anim.step(turtle, &mut display_list);
    }

    conn.send(client_id, ServerResponse::AnimationComplete(id)).await
        .expect("unable to send response to IPC client");
}

pub(crate) async fn move_to(
    conn: &ServerConnection,
    client_id: ClientId,
    app_control: &AccessControl,
    display_list: &Mutex<DisplayList>,
    event_loop: &Mutex<EventLoopProxy<RequestRedraw>>,
    id: TurtleId,
    target_pos: Point,
) {
    let mut data = app_control.get(RequiredData {
        drawing: false,
        turtles: Some(RequiredTurtles::One(id)),
    }).await;

    // Borrow the data initially to setup the animation but then drop it so the locks on the turtle
    // and display list are released (allows for rendering to occur)
    let mut anim = {
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();

        let mut display_list = display_list.lock().await;
        MoveAnimation::new(turtle, &mut display_list, target_pos)
    };

    while anim.running {
        // Signal the main thread that the image has changed
        event_loop.lock().await.send_event(RequestRedraw)
            .expect("bug: event loop closed before animation completed");

        // Sleep until it is time to update the animation again
        anim.timer.tick().await;

        // These locks are dropped at the end of this loop iteration to allow rendering to occur
        // while we wait
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();
        let mut display_list = display_list.lock().await;

        anim.step(turtle, &mut display_list);
    }

    conn.send(client_id, ServerResponse::AnimationComplete(id)).await
        .expect("unable to send response to IPC client");
}

struct MoveAnimation {
    /// true if the animation should continue, false if it should stop
    running: bool,
    /// A timer used to delay the execution of the animation to a reasonable FPS
    timer: time::Interval,
    /// The instant that the animation started, used to precisely determine how long the animation
    /// has been running
    start: time::Instant,
    /// The position that the turtle started at
    start_pos: Point,
    /// The target position to move to (i.e. the final value of the animation)
    target_pos: Point,
    /// The total duration of the animation in microseconds
    total_micros: f64,
    /// A handle to the line that is manipulated by thsi animation
    prim: PrimHandle,
}

impl MoveAnimation {
    fn new(
        turtle: &mut TurtleDrawings,
        display_list: &mut DisplayList,
        target_pos: Point,
    ) -> Self {
        let frame_duration = time::Duration::from_millis(MICROS_PER_SEC / FPS);
        // Need to start at now() + frame_duration or else timer will initially tick for 0 seconds
        let timer = time::interval_at(time::Instant::now() + frame_duration, frame_duration);

        let TurtleState {position, speed, ref pen, ..} = turtle.state;

        if speed.is_instant() {
            // Set to the final position and draw a line with no animation
            turtle.state.position = target_pos;
            let prim = display_list.push_line(position, target_pos, pen);
            turtle.drawings.push(prim);

            if let Some(poly_handle) = turtle.current_fill_polygon {
                //TODO: Push point into polygon
                todo!()
            }

            Self {
                // stop the animation right away since it has already completed
                running: false,
                timer,
                start: time::Instant::now(),
                start_pos: position,
                target_pos,
                total_micros: 0.0,
                prim,
            }

        } else {
            let px_per_sec = speed.to_px_per_sec();
            let abs_distance = (target_pos - position).len();
            // Use microseconds instead of ms for greater precision
            let total_micros = abs_distance * MICROS_PER_SEC as f64 / px_per_sec;

            // No need to update position since the turtle hasn't move anywhere yet

            // Start with a zero-length line since the animation hasn't started yet
            let prim = display_list.push_line(position, position, pen);
            turtle.drawings.push(prim);

            if let Some(poly_handle) = turtle.current_fill_polygon {
                //TODO: Push point into polygon
                todo!()
            }

            Self {
                running: true,
                timer,
                start: time::Instant::now(),
                start_pos: position,
                target_pos,
                total_micros,
                prim,
            }
        }

    }

    /// Advances the animation based on the amount of time that has elapsed so far
    fn step(&mut self, turtle: &mut TurtleDrawings, display_list: &mut DisplayList) {
        let &mut Self {
            ref mut running,
            timer: _,
            ref start,
            start_pos,
            target_pos,
            total_micros,
            prim,
        } = self;

        let elapsed = start.elapsed().as_micros() as f64;
        if elapsed >= total_micros {
            *running = false;
            turtle.state.position = target_pos;
            display_list.replace_line(prim, start_pos, target_pos, &turtle.state.pen);

            if let Some(poly_handle) = turtle.current_fill_polygon {
                //TODO: Replace point in fill polygon
                todo!()
            }

        } else {
            // t is the total progress made in the animation so far
            let t = elapsed / total_micros;
            let current_pos = lerp(&start_pos, &target_pos, &t);

            turtle.state.position = current_pos;
            display_list.replace_line(prim, start_pos, current_pos, &turtle.state.pen);

            if let Some(poly_handle) = turtle.current_fill_polygon {
                //TODO: Replace point in fill polygon
                todo!()
            }
        }
    }
}

pub(crate) async fn rotate_in_place(
    conn: &ServerConnection,
    client_id: ClientId,
    app_control: &AccessControl,
    event_loop: &Mutex<EventLoopProxy<RequestRedraw>>,
    id: TurtleId,
    angle: Radians,
    direction: RotationDirection,
) {
    let mut data = app_control.get(RequiredData {
        drawing: false,
        turtles: Some(RequiredTurtles::One(id)),
    }).await;

    // Borrow the data initially to setup the animation but then drop it so the lock on the turtle
    // is released (allows for rendering to occur)
    let mut anim = {
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();

        RotateAnimation::new(turtle, angle, direction)
    };

    while anim.running {
        // Signal the main thread that the image has changed
        event_loop.lock().await.send_event(RequestRedraw)
            .expect("bug: event loop closed before animation completed");

        // Sleep until it is time to update the animation again
        anim.timer.tick().await;

        // This lock is dropped at the end of this loop iteration to allow rendering to occur
        // while we wait
        let mut turtles = data.turtles_mut().await;
        let turtle = turtles.one_mut();

        anim.step(turtle);
    }

    conn.send(client_id, ServerResponse::AnimationComplete(id)).await
        .expect("unable to send response to IPC client");
}

struct RotateAnimation {
    /// true if the animation should continue, false if it should stop
    running: bool,
    /// A timer used to delay the execution of the animation to a reasonable FPS
    timer: time::Interval,
    /// The instant that the animation started, used to precisely determine how long the animation
    /// has been running
    start: time::Instant,
    /// The start angle of the turtle
    start_heading: Radians,
    /// The angle in radians that the turtle is to have rotated by the end of the animation
    delta_angle: Radians,
    /// The direction of rotation
    direction: RotationDirection,
    /// The total duration of the animation in microseconds
    total_micros: f64,
}

impl RotateAnimation {
    fn new(
        turtle: &mut TurtleDrawings,
        delta_angle: Radians,
        direction: RotationDirection,
    ) -> Self {
        let frame_duration = time::Duration::from_millis(MICROS_PER_SEC / FPS);
        // Need to start at now() + frame_duration or else timer will initially tick for 0 seconds
        let timer = time::interval_at(time::Instant::now() + frame_duration, frame_duration);

        let TurtleState {heading, speed, ..} = turtle.state;
        if speed.is_instant() {
            // Set to the final heading with no animation
            turtle.state.heading = rotate(heading, delta_angle, direction);

            Self {
                // stop the animation right away since it has already completed
                running: false,
                timer,
                start: time::Instant::now(),
                start_heading: heading,
                delta_angle,
                direction,
                total_micros: 0.0,
            }

        } else {
            let rad_per_sec = speed.to_rad_per_sec();
            // Use microseconds instead of ms for greater precision
            let total_micros = (delta_angle * MICROS_PER_SEC as f64 / rad_per_sec).to_radians();

            // No need to update heading since the turtle hasn't rotated yet

            Self {
                running: true,
                timer,
                start: time::Instant::now(),
                start_heading: heading,
                delta_angle,
                direction,
                total_micros,
            }
        }

    }

    /// Advances the animation based on the amount of time that has elapsed so far
    fn step(&mut self, turtle: &mut TurtleDrawings) {
        let &mut Self {
            ref mut running,
            timer: _,
            ref start,
            start_heading,
            delta_angle,
            direction,
            total_micros,
        } = self;

        let elapsed = start.elapsed().as_micros() as f64;
        if elapsed >= total_micros {
            *running = false;

            // Set to the final heading
            turtle.state.heading = rotate(start_heading, delta_angle, direction);

        } else {
            // t is the total progress made in the animation so far
            let t = elapsed / total_micros;
            let current_delta = lerp(&radians::ZERO, &delta_angle, &t);

            turtle.state.heading = rotate(start_heading, current_delta, direction);
            debug_assert!(!turtle.state.heading.is_nan(), "bug: heading became NaN");
        }
    }
}

/// Rotates the given `angle` by the given `rotation` in the given `direction`
///
/// Let's say you have a starting angle X. Standard angles go counterclockwise, so
/// if clockwise is true, we need to subtract the `rotation` from X resulting in
/// `X - rotation`. If clockwise is false, we can just add normally.
fn rotate(angle: Radians, rotation: Radians, direction: RotationDirection) -> Radians {
    use RotationDirection::*;
    let angle = match direction {
        Clockwise => angle - rotation,
        Counterclockwise => angle + rotation,
    };

    // Normalize the angle to be between 0 and 2*pi
    // Formula adapted from: https://stackoverflow.com/a/24234924/551904
    // More info: https://stackoverflow.com/a/28316446/551904
    angle - radians::TWO_PI * (angle / radians::TWO_PI).floor()
}
<|endoftext|>


<commit_before>use std::io::{File, BufferedReader};
use std::io::SeekStyle::SeekSet;
use error::{CTResult, CTDatabaseError, CTSyntaxError};
use error::CTError::{DatabaseError, SyntaxError};
use token::Token;
use token::TokenKind::Elem;

#[derive(Show, PartialEq)]
pub struct ElemData {
    short_name: String,
    long_name: String,
    mass: f64,
    atomic_num: u16,
}

pub struct ElemDatabase {
    db: BufferedReader<File>,
}

impl ElemDatabase {
    pub fn open(path: &str) -> CTResult<ElemDatabase> {
        match File::open(&Path::new(path)) {
            Ok(db_file) => Ok(ElemDatabase { db: BufferedReader::new(db_file) }),
            Err(_) => Err(DatabaseError(CTDatabaseError {
                desc: format!("Could not open database file. Expected at: {:?}", path)
            })),
        }
    }

    pub fn get_single_data(&mut self, elem: &Token) -> CTResult<ElemData> {
        // we return to the beginning of the underlying file, since the data
        // might lie on a line we have previously read past
        self.db.get_mut().seek(0, SeekSet).ok().expect("Internal error reading database");
        let mut line_iter = self.db.lines();
        let short_name = match elem.tok {
            Elem(ref name) => name.as_slice(),
            _ => unreachable!(),
        };
        match line_iter.find(|l| l.is_err() || l.as_ref().ok().unwrap().starts_with(short_name)) {
            Some(Ok(ref line)) => decode_line(line),
            Some(Err(_)) => Err(DatabaseError(CTDatabaseError {
                desc: "Error reading the database".to_string()
            })),
            None => Err(SyntaxError(CTSyntaxError {
                desc: format!("Could not find element: {:?}", short_name),
                pos: elem.pos,
                len: elem.len,
            })),
        }
    }
}

fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(DatabaseError(CTDatabaseError {
            desc: "Missing field in database".to_string()
        }))
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(DatabaseError(CTDatabaseError {
                desc: "Field in database corrupted".to_string()
            }))
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::File;
    use std::io::fs;
    use token::Token;
    use token::TokenKind::Elem;

    fn make_dummy_db(name: &str, contents: &str) -> ElemDatabase {
        if let Err(e) = File::create(&Path::new(name)).and_then(|mut f| f.write_str(contents)) {
            // if we can't make the database we can't test, so just abort here
            panic!("Could not create dummy database: {:?}", e.desc);
        }
        ElemDatabase::open(name).unwrap()
    }

    fn remove_dummy_db(name: &str) {
        if let Err(e) = fs::unlink(&Path::new(name)) {
            // if we can't remove the database something is wrong, and we abort the test
            panic!("Could not remove dummy database: {:?}", e.desc);
        }
    }

    #[test]
    fn decode() {
        let db_name = "decode_db";
        let mut db = make_dummy_db(db_name, "A;123.456789;Abba;12");
        let raw_result = db.get_single_data(&Token { tok: Elem("A".to_string()), pos: 0, len: 2 });
        let expected = ElemData {
            short_name: "A".to_string(),
            long_name: "Abba".to_string(),
            mass: 123.456789,
            atomic_num: 12,
        };
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn missing_field() {
        let db_name = "missing_field_db";
        let mut db = make_dummy_db(db_name, "A;");
        let result = db.get_single_data(&Token { tok: Elem("A".to_string()), pos: 0, len: 2 });
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn field_corrupted() {
        let db_name = "field_corrupted_db";
        let mut db = make_dummy_db(db_name, "A;not a number;Abba;12");
        let result = db.get_single_data(&Token { tok: Elem("A".to_string()), pos: 0, len: 2 });
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }
}<commit_msg>Write function to retrieve data for mulitple elems<commit_after>use std::io::{File, BufferedReader};
use std::io::SeekStyle::SeekSet;
use error::{CTResult, CTDatabaseError, CTSyntaxError};
use error::CTError::{DatabaseError, SyntaxError};
use token::Token;
use token::TokenKind::Elem;

#[derive(Show, PartialEq)]
pub struct ElemData {
    short_name: String,
    long_name: String,
    mass: f64,
    atomic_num: u16,
}

pub struct ElemDatabase {
    db: BufferedReader<File>,
}

impl ElemDatabase {
    pub fn open(path: &str) -> CTResult<ElemDatabase> {
        match File::open(&Path::new(path)) {
            Ok(db_file) => Ok(ElemDatabase { db: BufferedReader::new(db_file) }),
            Err(_) => Err(DatabaseError(CTDatabaseError {
                desc: format!("Could not open database file. Expected at: {:?}", path)
            })),
        }
    }

    pub fn get_single_data(&mut self, elem: &Token) -> CTResult<ElemData> {
        // we return to the beginning of the underlying file, since the data
        // might lie on a line we have previously read past
        self.db.get_mut().seek(0, SeekSet).ok().expect("Internal error reading database");
        let mut line_iter = self.db.lines();
        let short_name = match elem.tok {
            Elem(ref name) => name.as_slice(),
            _ => unreachable!(),
        };
        match line_iter.find(|l| l.is_err() || l.as_ref().ok().unwrap().starts_with(short_name)) {
            Some(Ok(ref line)) => decode_line(line),
            Some(Err(_)) => Err(DatabaseError(CTDatabaseError {
                desc: "Error reading the database".to_string()
            })),
            None => Err(SyntaxError(CTSyntaxError {
                desc: format!("Could not find element: {:?}", short_name),
                pos: elem.pos,
                len: elem.len,
            })),
        }
    }

    pub fn get_data(&mut self, elems: &Vec<Token>) -> CTResult<Vec<ElemData>> {
        let mut out = Vec::new();
        for elem in elems.iter() {
           match self.get_single_data(elem) {
                Ok(data) => out.push(data),
                Err(e) => return Err(e),
            }
        }
        Ok(out)
    }
}

fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(DatabaseError(CTDatabaseError {
            desc: "Missing field in database".to_string()
        }))
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(DatabaseError(CTDatabaseError {
                desc: "Field in database corrupted".to_string()
            }))
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::io::File;
    use std::io::fs;
    use token::Token;
    use token::TokenKind::Elem;

    fn make_dummy_db(name: &str, contents: &str) -> ElemDatabase {
        if let Err(e) = File::create(&Path::new(name)).and_then(|mut f| f.write_str(contents)) {
            // if we can't make the database we can't test, so just abort here
            panic!("Could not create dummy database: {:?}", e.desc);
        }
        ElemDatabase::open(name).unwrap()
    }

    fn remove_dummy_db(name: &str) {
        if let Err(e) = fs::unlink(&Path::new(name)) {
            // if we can't remove the database something is wrong, and we abort the test
            panic!("Could not remove dummy database: {:?}", e.desc);
        }
    }

    #[test]
    fn decode() {
        let db_name = "decode_db";
        let mut db = make_dummy_db(db_name, "A;123.456789;Abba;12");
        let raw_result = db.get_single_data(&Token { tok: Elem("A".to_string()), pos: 0, len: 2 });
        let expected = ElemData {
            short_name: "A".to_string(),
            long_name: "Abba".to_string(),
            mass: 123.456789,
            atomic_num: 12,
        };
        remove_dummy_db(db_name);
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn missing_field() {
        let db_name = "missing_field_db";
        let mut db = make_dummy_db(db_name, "A;");
        let result = db.get_single_data(&Token { tok: Elem("A".to_string()), pos: 0, len: 2 });
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }

    #[test]
    fn field_corrupted() {
        let db_name = "field_corrupted_db";
        let mut db = make_dummy_db(db_name, "A;not a number;Abba;12");
        let result = db.get_single_data(&Token { tok: Elem("A".to_string()), pos: 0, len: 2 });
        remove_dummy_db(db_name);
        assert!(result.is_err());
    }
}<|endoftext|>