<commit_before>#[doc="Creates CSS boxes from a DOM."]

import css::values::{DisplayType, Block, Inline, DisplayNone};
import dom::base::{ElementData, HTMLDivElement, HTMLImageElement, Element, Text, Node};
import gfx::geometry::zero_size_au;
import layout::base::{Appearance, BTree, BlockBox, Box, BoxKind, InlineBox, IntrinsicBox, NTree};
import layout::base::{TextBoxKind};
import layout::text::TextBox;
import util::tree;
import option::is_none;

export box_builder_methods;

enum ctxt = {
    // The parent node that we're scanning.
    parent_node: Node,
    // The parent box that these boxes will be added to.
    parent_box: @Box,

    //
    // The current anonymous box that we're currently appending inline nodes to.
    //
    // See CSS2 9.2.1.1.
    //

    mut anon_box: Option<@Box>
};

fn create_context(parent_node: Node, parent_box: @Box) -> ctxt {
    return ctxt({
           parent_node: parent_node,
           parent_box: parent_box,
           mut anon_box: None
    });
}

impl ctxt {
    #[doc="
     Constructs boxes for the parent's children, when the parent's 'display' attribute is 'block'.
     "]
    fn construct_boxes_for_block_children() {
        for NTree.each_child(self.parent_node) |kid| {

            // Create boxes for the child. Get its primary box.
            let kid_box = kid.construct_boxes();
            if (kid_box.is_none()) {
                again;
            }

            // Determine the child's display.
            let disp = kid.get_specified_style().display_type;
            if disp != Some(Inline) {
                self.finish_anonymous_box_if_necessary();
            }

            // Add the child's box to the current enclosing box or the current anonymous box.
            match kid.get_specified_style().display_type {
              Some(Block) => BTree.add_child(self.parent_box, kid_box.get()),
              Some(Inline) => {
                let anon_box = match self.anon_box {
                  None => {
                    //
                    // The anonymous box inherits the attributes of its parents for now, so
                    // that properties of intrinsic boxes are not spread to their parenting
                    // anonymous box.
                    //
                    // TODO: check what CSS actually specifies
                    //

                    let b = @Box(self.parent_node, InlineBox);
                    self.anon_box = Some(b);
                    b
                  }
                  Some(b) => b
                };
                BTree.add_child(anon_box, kid_box.get());
              }
              Some(DisplayNone) => {
                // Nothing to do.
              }
              _ => { //hack for now
              }
            }
        }
    }

    #[doc="
      Constructs boxes for the parent's children, when the parent's 'display'
      attribute is 'inline'.
     "]
    fn construct_boxes_for_inline_children() {
        for NTree.each_child(self.parent_node) |kid| {

            // Construct boxes for the child. Get its primary box.
            let kid_box = kid.construct_boxes();

            // Determine the child's display.
            let disp = kid.get_specified_style().display_type;
            if disp != Some(Inline) {
                // TODO
            }

            // Add the child's box to the current enclosing box.
            match kid.get_specified_style().display_type {
              Some(Block) => {
                // TODO
                #warn("TODO: non-inline display found inside inline box");
                BTree.add_child(self.parent_box, kid_box.get());
              }
              Some(Inline) => {
                BTree.add_child(self.parent_box, kid_box.get());
              }
              Some(DisplayNone) => {
                // Nothing to do.
              }
              _  => { //hack for now
              }
            }
        }
    }

    #[doc="Constructs boxes for the parent's children."]
    fn construct_boxes_for_children() {
        #debug("parent node:");
        self.parent_node.dump();

        match self.parent_node.get_specified_style().display_type {
          Some(Block) => self.construct_boxes_for_block_children(),
          Some(Inline) => self.construct_boxes_for_inline_children(),
          Some(DisplayNone) => { /* Nothing to do. */ }
          _ => { //hack for now
          }
        }

        self.finish_anonymous_box_if_necessary();
        assert is_none(self.anon_box);
    }

    #[doc="
      Flushes the anonymous box we're creating if it exists. This appends the
      anonymous box to the block.
    "]
    fn finish_anonymous_box_if_necessary() {
        match copy self.anon_box {
          None => { /* Nothing to do. */ }
          Some(b) => BTree.add_child(self.parent_box, b)
        }
        self.anon_box = None;
    }
}

trait PrivBoxBuilder {
    fn determine_box_kind() -> Option<BoxKind>;
}

impl Node : PrivBoxBuilder {
    #[doc="
      Determines the kind of box that this node needs. Also, for images, computes the intrinsic
      size.
     "]
    fn determine_box_kind() -> Option<BoxKind> {
        match self.read(|n| copy n.kind) {
            ~Text(string) => Some(TextBoxKind(@TextBox(copy string))),
            ~Element(element) => {
                match (copy *element.kind, self.get_specified_style().display_type)  {
                    (HTMLImageElement({size}), _) => Some(IntrinsicBox(@size)),
                    (_, Some(Block)) => Some(BlockBox),
                    (_, Some(Inline)) => Some(InlineBox),
                    (_, Some(DisplayNone)) => None,
                    (_, Some(_)) => Some(InlineBox),
                    (_, None) => {
                        fail ~"The specified display style should be a default instead of none"
                    }
                }
            },
            _ => fail ~"unstyleable node type encountered"
        }
    }
}

trait BoxBuilder {
    fn construct_boxes() -> Option<@Box>;
}

impl Node : BoxBuilder {
    #[doc="Creates boxes for this node. This is the entry point."]
    fn construct_boxes() -> Option<@Box> {
        match self.determine_box_kind() {
            None => None,
            Some(kind) => {
                let my_box = @Box(self, kind);
                match kind {
                    BlockBox | InlineBox => {
                        let cx = create_context(self, my_box);
                        cx.construct_boxes_for_children();
                    }
                    _ => {
                        // Nothing to do.
                    }
                }
                Some(my_box)
            }
        }
    }
}

<commit_msg>Add Comment and Doctype nodes to box_builder<commit_after>#[doc="Creates CSS boxes from a DOM."]

import css::values::{DisplayType, Block, Inline, DisplayNone};
import dom::base::{ElementData, HTMLDivElement, HTMLImageElement, Element, Text, Node, Doctype, Comment};
import gfx::geometry::zero_size_au;
import layout::base::{Appearance, BTree, BlockBox, Box, BoxKind, InlineBox, IntrinsicBox, NTree};
import layout::base::{TextBoxKind};
import layout::text::TextBox;
import util::tree;
import option::is_none;

export box_builder_methods;

enum ctxt = {
    // The parent node that we're scanning.
    parent_node: Node,
    // The parent box that these boxes will be added to.
    parent_box: @Box,

    //
    // The current anonymous box that we're currently appending inline nodes to.
    //
    // See CSS2 9.2.1.1.
    //

    mut anon_box: Option<@Box>
};

fn create_context(parent_node: Node, parent_box: @Box) -> ctxt {
    return ctxt({
           parent_node: parent_node,
           parent_box: parent_box,
           mut anon_box: None
    });
}

impl ctxt {
    #[doc="
     Constructs boxes for the parent's children, when the parent's 'display' attribute is 'block'.
     "]
    fn construct_boxes_for_block_children() {
        for NTree.each_child(self.parent_node) |kid| {

            // Create boxes for the child. Get its primary box.
            let kid_box = kid.construct_boxes();
            if (kid_box.is_none()) {
                again;
            }

            // Determine the child's display.
            let disp = kid.get_specified_style().display_type;
            if disp != Some(Inline) {
                self.finish_anonymous_box_if_necessary();
            }

            // Add the child's box to the current enclosing box or the current anonymous box.
            match kid.get_specified_style().display_type {
              Some(Block) => BTree.add_child(self.parent_box, kid_box.get()),
              Some(Inline) => {
                let anon_box = match self.anon_box {
                  None => {
                    //
                    // The anonymous box inherits the attributes of its parents for now, so
                    // that properties of intrinsic boxes are not spread to their parenting
                    // anonymous box.
                    //
                    // TODO: check what CSS actually specifies
                    //

                    let b = @Box(self.parent_node, InlineBox);
                    self.anon_box = Some(b);
                    b
                  }
                  Some(b) => b
                };
                BTree.add_child(anon_box, kid_box.get());
              }
              Some(DisplayNone) => {
                // Nothing to do.
              }
              _ => { //hack for now
              }
            }
        }
    }

    #[doc="
      Constructs boxes for the parent's children, when the parent's 'display'
      attribute is 'inline'.
     "]
    fn construct_boxes_for_inline_children() {
        for NTree.each_child(self.parent_node) |kid| {

            // Construct boxes for the child. Get its primary box.
            let kid_box = kid.construct_boxes();

            // Determine the child's display.
            let disp = kid.get_specified_style().display_type;
            if disp != Some(Inline) {
                // TODO
            }

            // Add the child's box to the current enclosing box.
            match kid.get_specified_style().display_type {
              Some(Block) => {
                // TODO
                #warn("TODO: non-inline display found inside inline box");
                BTree.add_child(self.parent_box, kid_box.get());
              }
              Some(Inline) => {
                BTree.add_child(self.parent_box, kid_box.get());
              }
              Some(DisplayNone) => {
                // Nothing to do.
              }
              _  => { //hack for now
              }
            }
        }
    }

    #[doc="Constructs boxes for the parent's children."]
    fn construct_boxes_for_children() {
        #debug("parent node:");
        self.parent_node.dump();

        match self.parent_node.get_specified_style().display_type {
          Some(Block) => self.construct_boxes_for_block_children(),
          Some(Inline) => self.construct_boxes_for_inline_children(),
          Some(DisplayNone) => { /* Nothing to do. */ }
          _ => { //hack for now
          }
        }

        self.finish_anonymous_box_if_necessary();
        assert is_none(self.anon_box);
    }

    #[doc="
      Flushes the anonymous box we're creating if it exists. This appends the
      anonymous box to the block.
    "]
    fn finish_anonymous_box_if_necessary() {
        match copy self.anon_box {
          None => { /* Nothing to do. */ }
          Some(b) => BTree.add_child(self.parent_box, b)
        }
        self.anon_box = None;
    }
}

trait PrivBoxBuilder {
    fn determine_box_kind() -> Option<BoxKind>;
}

impl Node : PrivBoxBuilder {
    #[doc="
      Determines the kind of box that this node needs. Also, for images, computes the intrinsic
      size.
     "]
    fn determine_box_kind() -> Option<BoxKind> {
        match self.read(|n| copy n.kind) {
            ~Text(string) => Some(TextBoxKind(@TextBox(copy string))),
            ~Element(element) => {
                match (copy *element.kind, self.get_specified_style().display_type)  {
                    (HTMLImageElement({size}), _) => Some(IntrinsicBox(@size)),
                    (_, Some(Block)) => Some(BlockBox),
                    (_, Some(Inline)) => Some(InlineBox),
                    (_, Some(DisplayNone)) => None,
                    (_, Some(_)) => Some(InlineBox),
                    (_, None) => {
                        fail ~"The specified display style should be a default instead of none"
                    }
                }
            },
          ~Doctype(*)
          | ~Comment(*) => None
        }
    }
}

trait BoxBuilder {
    fn construct_boxes() -> Option<@Box>;
}

impl Node : BoxBuilder {
    #[doc="Creates boxes for this node. This is the entry point."]
    fn construct_boxes() -> Option<@Box> {
        match self.determine_box_kind() {
            None => None,
            Some(kind) => {
                let my_box = @Box(self, kind);
                match kind {
                    BlockBox | InlineBox => {
                        let cx = create_context(self, my_box);
                        cx.construct_boxes_for_children();
                    }
                    _ => {
                        // Nothing to do.
                    }
                }
                Some(my_box)
            }
        }
    }
}

<|endoftext|>


<commit_before>import std::io;
import std::str;
import std::option;
import std::fs;
import std::os;
import std::vec;
import std::test;

import common::mode_run_pass;
import common::mode_run_fail;
import common::mode_compile_fail;
import common::mode_pretty;
import common::cx;
import common::config;
import header::load_props;
import header::test_props;
import util::logv;

export run;

fn run(cx: cx, -_testfile: [u8]) {
    let testfile = str::unsafe_from_bytes(_testfile);
    if cx.config.verbose {
        // We're going to be dumping a lot of info. Start on a new line.
        io::stdout().write_str("\n\n");
    }
    log #fmt["running %s", testfile];
    let props = load_props(testfile);
    alt cx.config.mode {
      mode_compile_fail. { run_cfail_test(cx, props, testfile); }
      mode_run_fail. { run_rfail_test(cx, props, testfile); }
      mode_run_pass. { run_rpass_test(cx, props, testfile); }
      mode_pretty. { run_pretty_test(cx, props, testfile); }
    }
}

fn run_cfail_test(cx: cx, props: test_props, testfile: str) {
    let procres = compile_test(cx, props, testfile);

    if procres.status == 0 {
        fatal_procres("compile-fail test compiled successfully!", procres);
    }

    check_correct_failure_status(procres);
    check_error_patterns(props, testfile, procres);
}

fn run_rfail_test(cx: cx, props: test_props, testfile: str) {
    let procres = compile_test(cx, props, testfile);

    if procres.status != 0 { fatal_procres("compilation failed!", procres); }

    procres = exec_compiled_test(cx, props, testfile);

    // The value our Makefile configures valgrind to return on failure
    const valgrind_err: int = 100;
    if procres.status == valgrind_err {
        fatal_procres("run-fail test isn't valgrind-clean!", procres);
    }

    check_correct_failure_status(procres);
    check_error_patterns(props, testfile, procres);
}

fn check_correct_failure_status(procres: procres) {
    // The value the rust runtime returns on failure
    const rust_err: int = 101;
    if procres.status != rust_err {
        fatal_procres(
            #fmt("failure produced the wrong error code: %d",
                 procres.status),
            procres);
    }
}

fn run_rpass_test(cx: cx, props: test_props, testfile: str) {
    let procres = compile_test(cx, props, testfile);

    if procres.status != 0 { fatal_procres("compilation failed!", procres); }

    procres = exec_compiled_test(cx, props, testfile);

    if procres.status != 0 { fatal_procres("test run failed!", procres); }
}

fn run_pretty_test(cx: cx, props: test_props, testfile: str) {
    if option::is_some(props.pp_exact) {
        logv(cx.config, "testing for exact pretty-printing");
    } else { logv(cx.config, "testing for converging pretty-printing"); }

    let rounds =
        alt props.pp_exact { option::some(_) { 1 } option::none. { 2 } };

    let srcs = [io::read_whole_file_str(testfile)];

    let round = 0;
    while round < rounds {
        logv(cx.config, #fmt["pretty-printing round %d", round]);
        let procres = print_source(cx, testfile, srcs[round]);

        if procres.status != 0 {
            fatal_procres(#fmt["pretty-printing failed in round %d", round],
                          procres);
        }

        srcs += [procres.stdout];
        round += 1;
    }

    let expected =
        alt props.pp_exact {
          option::some(file) {
            let filepath = fs::connect(fs::dirname(testfile), file);
            io::read_whole_file_str(filepath)
          }
          option::none. { srcs[vec::len(srcs) - 2u] }
        };
    let actual = srcs[vec::len(srcs) - 1u];

    if option::is_some(props.pp_exact) {
        // Now we have to care about line endings
        let cr = "\r";
        check (str::is_not_empty(cr));
        actual = str::replace(actual, cr, "");
        expected = str::replace(expected, cr, "");
    }

    compare_source(expected, actual);

    // Finally, let's make sure it actually appears to remain valid code
    let procres = typecheck_source(cx, testfile, actual);

    if procres.status != 0 {
        fatal_procres("pretty-printed source does not typecheck", procres);
    }

    ret;

    fn print_source(cx: cx, testfile: str, src: str) -> procres {
        compose_and_run(cx, testfile, make_pp_args,
                        cx.config.compile_lib_path, option::some(src))
    }

    fn make_pp_args(config: config, _testfile: str) -> procargs {
        let prog = config.rustc_path;
        let args = ["-", "--pretty", "normal"];
        ret {prog: prog, args: args};
    }

    fn compare_source(expected: str, actual: str) {
        if expected != actual {
            error("pretty-printed source does match expected source");
            let msg =
                #fmt["\n\
expected:\n\
------------------------------------------\n\
%s\n\
------------------------------------------\n\
actual:\n\
------------------------------------------\n\
%s\n\
------------------------------------------\n\
\n",
                     expected, actual];
            io::stdout().write_str(msg);
            fail;
        }
    }

    fn typecheck_source(cx: cx, testfile: str, src: str) -> procres {
        compose_and_run(cx, testfile, make_typecheck_args,
                        cx.config.compile_lib_path, option::some(src))
    }

    fn make_typecheck_args(config: config, _testfile: str) -> procargs {
        let prog = config.rustc_path;
        let args = ["-", "--no-trans", "--lib"];
        ret {prog: prog, args: args};
    }
}

fn check_error_patterns(props: test_props, testfile: str, procres: procres) {
    if vec::is_empty(props.error_patterns) {
        fatal("no error pattern specified in " + testfile);
    }

    if procres.status == 0 {
        fatal("process did not return an error status");
    }

    let next_err_idx = 0u;
    let next_err_pat = props.error_patterns[next_err_idx];
    for line: str in str::split(procres.stdout, '\n' as u8) {
        if str::find(line, next_err_pat) > 0 {
            log #fmt["found error pattern %s", next_err_pat];
            next_err_idx += 1u;
            if next_err_idx == vec::len(props.error_patterns) {
                log "found all error patterns";
                ret;
            }
            next_err_pat = props.error_patterns[next_err_idx];
        }
    }

    let missing_patterns =
        vec::slice(props.error_patterns, next_err_idx,
                   vec::len(props.error_patterns));
    if vec::len(missing_patterns) == 1u {
        fatal_procres(#fmt["error pattern '%s' not found!",
                           missing_patterns[0]], procres);
    } else {
        for pattern: str in missing_patterns {
            error(#fmt["error pattern '%s' not found!", pattern]);
        }
        fatal_procres("multiple error patterns not found", procres);
    }
}

type procargs = {prog: str, args: [str]};

type procres = {status: int, stdout: str, stderr: str, cmdline: str};

fn compile_test(cx: cx, props: test_props, testfile: str) -> procres {
    compose_and_run(cx, testfile, bind make_compile_args(_, props, _),
                    cx.config.compile_lib_path, option::none)
}

fn exec_compiled_test(cx: cx, props: test_props, testfile: str) -> procres {
    compose_and_run(cx, testfile, bind make_run_args(_, props, _),
                    cx.config.run_lib_path, option::none)
}

fn compose_and_run(cx: cx, testfile: str,
                   make_args: fn(config, str) -> procargs, lib_path: str,
                   input: option::t<str>) -> procres {
    let procargs = make_args(cx.config, testfile);
    ret program_output(cx, testfile, lib_path, procargs.prog, procargs.args,
                       input);
}

fn make_compile_args(config: config, props: test_props, testfile: str) ->
   procargs {
    let prog = config.rustc_path;
    let args = [testfile, "-o", make_exe_name(config, testfile)];
    let rustcflags =
        alt config.rustcflags {
          option::some(s) { option::some(s) }
          option::none. { option::none }
        };
    args += split_maybe_args(rustcflags);
    args += split_maybe_args(props.compile_flags);
    ret {prog: prog, args: args};
}

fn make_exe_name(config: config, testfile: str) -> str {
    output_base_name(config, testfile) + os::exec_suffix()
}

fn make_run_args(config: config, _props: test_props, testfile: str) ->
   procargs {
    let toolargs = {
            // If we've got another tool to run under (valgrind),
            // then split apart its command
            let runtool =
                alt config.runtool {
                  option::some(s) { option::some(s) }
                  option::none. { option::none }
                };
            split_maybe_args(runtool)
        };

    let args = toolargs + [make_exe_name(config, testfile)];
    ret {prog: args[0], args: vec::slice(args, 1u, vec::len(args))};
}

fn split_maybe_args(argstr: option::t<str>) -> [str] {
    fn rm_whitespace(v: [str]) -> [str] {
        fn flt(s: str) -> option::t<str> {
            if !is_whitespace(s) { option::some(s) } else { option::none }
        }

        // FIXME: This should be in std
        fn is_whitespace(s: str) -> bool {
            for c: u8 in s { if c != ' ' as u8 { ret false; } }
            ret true;
        }
        vec::filter_map(flt, v)
    }

    alt argstr {
      option::some(s) { rm_whitespace(str::split(s, ' ' as u8)) }
      option::none. { [] }
    }
}

fn program_output(cx: cx, testfile: str, lib_path: str, prog: str,
                  args: [str], input: option::t<str>) -> procres {
    let cmdline =
        {
            let cmdline = make_cmdline(lib_path, prog, args);
            logv(cx.config, #fmt["executing %s", cmdline]);
            cmdline
        };
    let res = procsrv::run(cx.procsrv, lib_path, prog, args, input);
    dump_output(cx.config, testfile, res.out, res.err);
    ret {status: res.status,
         stdout: res.out,
         stderr: res.err,
         cmdline: cmdline};
}

fn make_cmdline(libpath: str, prog: str, args: [str]) -> str {
    #fmt["%s %s %s", lib_path_cmd_prefix(libpath), prog,
         str::connect(args, " ")]
}

// Build the LD_LIBRARY_PATH variable as it would be seen on the command line
// for diagnostic purposes
fn lib_path_cmd_prefix(path: str) -> str {
    #fmt["%s=\"%s\"", util::lib_path_env_var(), util::make_new_path(path)]
}

fn dump_output(config: config, testfile: str, out: str, err: str) {
    dump_output_file(config, testfile, out, "out");
    dump_output_file(config, testfile, err, "err");
    maybe_dump_to_stdout(config, out, err);
}

#[cfg(target_os = "win32")]
#[cfg(target_os = "linux")]
fn dump_output_file(config: config, testfile: str, out: str, extension: str) {
    let outfile = make_out_name(config, testfile, extension);
    let writer = io::file_writer(outfile, [io::create, io::truncate]);
    writer.write_str(out);
}

// FIXME (726): Can't use file_writer on mac
#[cfg(target_os = "macos")]
fn dump_output_file(config: config, testfile: str, out: str, extension: str) {
}

fn make_out_name(config: config, testfile: str, extension: str) -> str {
    output_base_name(config, testfile) + "." + extension
}

fn output_base_name(config: config, testfile: str) -> str {
    let base = config.build_base;
    let filename =
        {
            let parts = str::split(fs::basename(testfile), '.' as u8);
            parts = vec::slice(parts, 0u, vec::len(parts) - 1u);
            str::connect(parts, ".")
        };
    #fmt["%s%s.%s", base, filename, config.stage_id]
}

fn maybe_dump_to_stdout(config: config, out: str, err: str) {
    if config.verbose {
        let sep1 = #fmt["------%s------------------------------", "stdout"];
        let sep2 = #fmt["------%s------------------------------", "stderr"];
        let sep3 = "------------------------------------------";
        io::stdout().write_line(sep1);
        io::stdout().write_line(out);
        io::stdout().write_line(sep2);
        io::stdout().write_line(err);
        io::stdout().write_line(sep3);
    }
}

fn error(err: str) { io::stdout().write_line(#fmt["\nerror: %s", err]); }

fn fatal(err: str) -> ! { error(err); fail; }

fn fatal_procres(err: str, procres: procres) -> ! {
    let msg =
        #fmt["\n\
error: %s\n\
command: %s\n\
stdout:\n\
------------------------------------------\n\
%s\n\
------------------------------------------\n\
stderr:\n\
------------------------------------------\n\
%s\n\
------------------------------------------\n\
\n",
             err, procres.cmdline, procres.stdout, procres.stderr];
    io::stdout().write_str(msg);
    fail;
}
<commit_msg>Revert "Check for the correct exit code on cfail tests. Closes #938"<commit_after>import std::io;
import std::str;
import std::option;
import std::fs;
import std::os;
import std::vec;
import std::test;

import common::mode_run_pass;
import common::mode_run_fail;
import common::mode_compile_fail;
import common::mode_pretty;
import common::cx;
import common::config;
import header::load_props;
import header::test_props;
import util::logv;

export run;

fn run(cx: cx, -_testfile: [u8]) {
    let testfile = str::unsafe_from_bytes(_testfile);
    if cx.config.verbose {
        // We're going to be dumping a lot of info. Start on a new line.
        io::stdout().write_str("\n\n");
    }
    log #fmt["running %s", testfile];
    let props = load_props(testfile);
    alt cx.config.mode {
      mode_compile_fail. { run_cfail_test(cx, props, testfile); }
      mode_run_fail. { run_rfail_test(cx, props, testfile); }
      mode_run_pass. { run_rpass_test(cx, props, testfile); }
      mode_pretty. { run_pretty_test(cx, props, testfile); }
    }
}

fn run_cfail_test(cx: cx, props: test_props, testfile: str) {
    let procres = compile_test(cx, props, testfile);

    if procres.status == 0 {
        fatal_procres("compile-fail test compiled successfully!", procres);
    }

    check_error_patterns(props, testfile, procres);
}

fn run_rfail_test(cx: cx, props: test_props, testfile: str) {
    let procres = compile_test(cx, props, testfile);

    if procres.status != 0 { fatal_procres("compilation failed!", procres); }

    procres = exec_compiled_test(cx, props, testfile);

    // The value our Makefile configures valgrind to return on failure
    const valgrind_err: int = 100;
    if procres.status == valgrind_err {
        fatal_procres("run-fail test isn't valgrind-clean!", procres);
    }

    // The value the rust runtime returns on failure
    const rust_err: int = 101;
    if procres.status != rust_err {
        fatal_procres(
            #fmt("run-fail test produced the wrong error code: %d",
                 procres.status),
            procres);
    }

    check_error_patterns(props, testfile, procres);
}

fn run_rpass_test(cx: cx, props: test_props, testfile: str) {
    let procres = compile_test(cx, props, testfile);

    if procres.status != 0 { fatal_procres("compilation failed!", procres); }

    procres = exec_compiled_test(cx, props, testfile);

    if procres.status != 0 { fatal_procres("test run failed!", procres); }
}

fn run_pretty_test(cx: cx, props: test_props, testfile: str) {
    if option::is_some(props.pp_exact) {
        logv(cx.config, "testing for exact pretty-printing");
    } else { logv(cx.config, "testing for converging pretty-printing"); }

    let rounds =
        alt props.pp_exact { option::some(_) { 1 } option::none. { 2 } };

    let srcs = [io::read_whole_file_str(testfile)];

    let round = 0;
    while round < rounds {
        logv(cx.config, #fmt["pretty-printing round %d", round]);
        let procres = print_source(cx, testfile, srcs[round]);

        if procres.status != 0 {
            fatal_procres(#fmt["pretty-printing failed in round %d", round],
                          procres);
        }

        srcs += [procres.stdout];
        round += 1;
    }

    let expected =
        alt props.pp_exact {
          option::some(file) {
            let filepath = fs::connect(fs::dirname(testfile), file);
            io::read_whole_file_str(filepath)
          }
          option::none. { srcs[vec::len(srcs) - 2u] }
        };
    let actual = srcs[vec::len(srcs) - 1u];

    if option::is_some(props.pp_exact) {
        // Now we have to care about line endings
        let cr = "\r";
        check (str::is_not_empty(cr));
        actual = str::replace(actual, cr, "");
        expected = str::replace(expected, cr, "");
    }

    compare_source(expected, actual);

    // Finally, let's make sure it actually appears to remain valid code
    let procres = typecheck_source(cx, testfile, actual);

    if procres.status != 0 {
        fatal_procres("pretty-printed source does not typecheck", procres);
    }

    ret;

    fn print_source(cx: cx, testfile: str, src: str) -> procres {
        compose_and_run(cx, testfile, make_pp_args,
                        cx.config.compile_lib_path, option::some(src))
    }

    fn make_pp_args(config: config, _testfile: str) -> procargs {
        let prog = config.rustc_path;
        let args = ["-", "--pretty", "normal"];
        ret {prog: prog, args: args};
    }

    fn compare_source(expected: str, actual: str) {
        if expected != actual {
            error("pretty-printed source does match expected source");
            let msg =
                #fmt["\n\
expected:\n\
------------------------------------------\n\
%s\n\
------------------------------------------\n\
actual:\n\
------------------------------------------\n\
%s\n\
------------------------------------------\n\
\n",
                     expected, actual];
            io::stdout().write_str(msg);
            fail;
        }
    }

    fn typecheck_source(cx: cx, testfile: str, src: str) -> procres {
        compose_and_run(cx, testfile, make_typecheck_args,
                        cx.config.compile_lib_path, option::some(src))
    }

    fn make_typecheck_args(config: config, _testfile: str) -> procargs {
        let prog = config.rustc_path;
        let args = ["-", "--no-trans", "--lib"];
        ret {prog: prog, args: args};
    }
}

fn check_error_patterns(props: test_props, testfile: str, procres: procres) {
    if vec::is_empty(props.error_patterns) {
        fatal("no error pattern specified in " + testfile);
    }

    if procres.status == 0 {
        fatal("process did not return an error status");
    }

    let next_err_idx = 0u;
    let next_err_pat = props.error_patterns[next_err_idx];
    for line: str in str::split(procres.stdout, '\n' as u8) {
        if str::find(line, next_err_pat) > 0 {
            log #fmt["found error pattern %s", next_err_pat];
            next_err_idx += 1u;
            if next_err_idx == vec::len(props.error_patterns) {
                log "found all error patterns";
                ret;
            }
            next_err_pat = props.error_patterns[next_err_idx];
        }
    }

    let missing_patterns =
        vec::slice(props.error_patterns, next_err_idx,
                   vec::len(props.error_patterns));
    if vec::len(missing_patterns) == 1u {
        fatal_procres(#fmt["error pattern '%s' not found!",
                           missing_patterns[0]], procres);
    } else {
        for pattern: str in missing_patterns {
            error(#fmt["error pattern '%s' not found!", pattern]);
        }
        fatal_procres("multiple error patterns not found", procres);
    }
}

type procargs = {prog: str, args: [str]};

type procres = {status: int, stdout: str, stderr: str, cmdline: str};

fn compile_test(cx: cx, props: test_props, testfile: str) -> procres {
    compose_and_run(cx, testfile, bind make_compile_args(_, props, _),
                    cx.config.compile_lib_path, option::none)
}

fn exec_compiled_test(cx: cx, props: test_props, testfile: str) -> procres {
    compose_and_run(cx, testfile, bind make_run_args(_, props, _),
                    cx.config.run_lib_path, option::none)
}

fn compose_and_run(cx: cx, testfile: str,
                   make_args: fn(config, str) -> procargs, lib_path: str,
                   input: option::t<str>) -> procres {
    let procargs = make_args(cx.config, testfile);
    ret program_output(cx, testfile, lib_path, procargs.prog, procargs.args,
                       input);
}

fn make_compile_args(config: config, props: test_props, testfile: str) ->
   procargs {
    let prog = config.rustc_path;
    let args = [testfile, "-o", make_exe_name(config, testfile)];
    let rustcflags =
        alt config.rustcflags {
          option::some(s) { option::some(s) }
          option::none. { option::none }
        };
    args += split_maybe_args(rustcflags);
    args += split_maybe_args(props.compile_flags);
    ret {prog: prog, args: args};
}

fn make_exe_name(config: config, testfile: str) -> str {
    output_base_name(config, testfile) + os::exec_suffix()
}

fn make_run_args(config: config, _props: test_props, testfile: str) ->
   procargs {
    let toolargs = {
            // If we've got another tool to run under (valgrind),
            // then split apart its command
            let runtool =
                alt config.runtool {
                  option::some(s) { option::some(s) }
                  option::none. { option::none }
                };
            split_maybe_args(runtool)
        };

    let args = toolargs + [make_exe_name(config, testfile)];
    ret {prog: args[0], args: vec::slice(args, 1u, vec::len(args))};
}

fn split_maybe_args(argstr: option::t<str>) -> [str] {
    fn rm_whitespace(v: [str]) -> [str] {
        fn flt(s: str) -> option::t<str> {
            if !is_whitespace(s) { option::some(s) } else { option::none }
        }

        // FIXME: This should be in std
        fn is_whitespace(s: str) -> bool {
            for c: u8 in s { if c != ' ' as u8 { ret false; } }
            ret true;
        }
        vec::filter_map(flt, v)
    }

    alt argstr {
      option::some(s) { rm_whitespace(str::split(s, ' ' as u8)) }
      option::none. { [] }
    }
}

fn program_output(cx: cx, testfile: str, lib_path: str, prog: str,
                  args: [str], input: option::t<str>) -> procres {
    let cmdline =
        {
            let cmdline = make_cmdline(lib_path, prog, args);
            logv(cx.config, #fmt["executing %s", cmdline]);
            cmdline
        };
    let res = procsrv::run(cx.procsrv, lib_path, prog, args, input);
    dump_output(cx.config, testfile, res.out, res.err);
    ret {status: res.status,
         stdout: res.out,
         stderr: res.err,
         cmdline: cmdline};
}

fn make_cmdline(libpath: str, prog: str, args: [str]) -> str {
    #fmt["%s %s %s", lib_path_cmd_prefix(libpath), prog,
         str::connect(args, " ")]
}

// Build the LD_LIBRARY_PATH variable as it would be seen on the command line
// for diagnostic purposes
fn lib_path_cmd_prefix(path: str) -> str {
    #fmt["%s=\"%s\"", util::lib_path_env_var(), util::make_new_path(path)]
}

fn dump_output(config: config, testfile: str, out: str, err: str) {
    dump_output_file(config, testfile, out, "out");
    dump_output_file(config, testfile, err, "err");
    maybe_dump_to_stdout(config, out, err);
}

#[cfg(target_os = "win32")]
#[cfg(target_os = "linux")]
fn dump_output_file(config: config, testfile: str, out: str, extension: str) {
    let outfile = make_out_name(config, testfile, extension);
    let writer = io::file_writer(outfile, [io::create, io::truncate]);
    writer.write_str(out);
}

// FIXME (726): Can't use file_writer on mac
#[cfg(target_os = "macos")]
fn dump_output_file(config: config, testfile: str, out: str, extension: str) {
}

fn make_out_name(config: config, testfile: str, extension: str) -> str {
    output_base_name(config, testfile) + "." + extension
}

fn output_base_name(config: config, testfile: str) -> str {
    let base = config.build_base;
    let filename =
        {
            let parts = str::split(fs::basename(testfile), '.' as u8);
            parts = vec::slice(parts, 0u, vec::len(parts) - 1u);
            str::connect(parts, ".")
        };
    #fmt["%s%s.%s", base, filename, config.stage_id]
}

fn maybe_dump_to_stdout(config: config, out: str, err: str) {
    if config.verbose {
        let sep1 = #fmt["------%s------------------------------", "stdout"];
        let sep2 = #fmt["------%s------------------------------", "stderr"];
        let sep3 = "------------------------------------------";
        io::stdout().write_line(sep1);
        io::stdout().write_line(out);
        io::stdout().write_line(sep2);
        io::stdout().write_line(err);
        io::stdout().write_line(sep3);
    }
}

fn error(err: str) { io::stdout().write_line(#fmt["\nerror: %s", err]); }

fn fatal(err: str) -> ! { error(err); fail; }

fn fatal_procres(err: str, procres: procres) -> ! {
    let msg =
        #fmt["\n\
error: %s\n\
command: %s\n\
stdout:\n\
------------------------------------------\n\
%s\n\
------------------------------------------\n\
stderr:\n\
------------------------------------------\n\
%s\n\
------------------------------------------\n\
\n",
             err, procres.cmdline, procres.stdout, procres.stderr];
    io::stdout().write_str(msg);
    fail;
}
<|endoftext|>


<commit_before>#![feature(test)]

extern crate test;
use test::Bencher;

use resize::Pixel::{Gray16, Gray8};
use resize::Type::Triangle;
use std::fs::File;
use std::path::PathBuf;

fn get_image() -> (png::OutputInfo, Vec<u8>) {
    let root: PathBuf = env!("CARGO_MANIFEST_DIR").into();
    let decoder = png::Decoder::new(File::open(root.join("examples/tiger.png")).unwrap());
    let (info, mut reader) = decoder.read_info().unwrap();
    let mut src = vec![110; info.buffer_size()];
    reader.next_frame(&mut src).unwrap();
    (info, src)
}

#[bench]
fn precomputed_large(b: &mut Bencher) {
    let (info, src) = get_image();
    let (w1, h1) = (info.width as usize, info.height as usize);
    let (w2, h2) = (1600, 1200);
    let mut dst = vec![120; w2 * h2];

    let mut r = resize::new(w1, h1, w2, h2, Gray8, Triangle);

    b.iter(|| r.resize(&src, &mut dst));
}

#[bench]
fn precomputed_small(b: &mut Bencher) {
    let (info, src) = get_image();
    let (w1, h1) = (info.width as usize, info.height as usize);
    let (w2, h2) = (100, 100);
    let mut dst = vec![240; w2 * h2];

    let mut r = resize::new(w1, h1, w2, h2, Gray8, Triangle);

    b.iter(|| r.resize(&src, &mut dst));
}

#[bench]
fn precomputed_small_16bit(b: &mut Bencher) {
    let (info, src) = get_image();
    let (w1, h1) = (info.width as usize, info.height as usize);
    let (w2, h2) = (100,100);
    let mut dst = vec![33; w2*h2];
    let src: Vec<_> = src.into_iter().map(|px|{
        let px = px as u16;
        (px << 8) | px
    }).collect();

    let mut r = resize::new(w1, h1, w2, h2, Gray16, Triangle);

    b.iter(|| r.resize(&src, &mut dst));
}

#[bench]
#[allow(deprecated)]
fn recomputed_small(b: &mut Bencher) {
    let (info, src) = get_image();
    let (w1, h1) = (info.width as usize, info.height as usize);
    let (w2, h2) = (100, 100);
    let mut dst = vec![99; w2 * h2];

    b.iter(|| resize::resize(w1, h1, w2, h2, Gray8, Triangle, &src, &mut dst));
}
<commit_msg>More benches<commit_after>#![feature(test)]

extern crate test;
use test::Bencher;

use resize::Pixel::{Gray16, Gray8};
use resize::Type::Triangle;
use resize::Type::Lanczos3;
use std::fs::File;
use std::path::PathBuf;

fn get_image() -> (png::OutputInfo, Vec<u8>) {
    let root: PathBuf = env!("CARGO_MANIFEST_DIR").into();
    let decoder = png::Decoder::new(File::open(root.join("examples/tiger.png")).unwrap());
    let (info, mut reader) = decoder.read_info().unwrap();
    let mut src = vec![110; info.buffer_size()];
    reader.next_frame(&mut src).unwrap();
    (info, src)
}

#[bench]
fn precomputed_large(b: &mut Bencher) {
    let (info, src) = get_image();
    let (w1, h1) = (info.width as usize, info.height as usize);
    let (w2, h2) = (1600, 1200);
    let mut dst = vec![120; w2 * h2];

    let mut r = resize::new(w1, h1, w2, h2, Gray8, Triangle);

    b.iter(|| r.resize(&src, &mut dst));
}

#[bench]
fn tiny(b: &mut Bencher) {
    let (info, src0) = get_image();
    let (w0, h0) = (info.width as usize, info.height as usize);
    let (w1, h1) = (3200, 2400);
    let mut src1 = vec![0; w1 * h1];

    resize::new(w0, h0, w1, h1, Gray8, Triangle).resize(&src0, &mut src1);
    let (w2, h2) = (16, 12);
    let mut dst = vec![0; w2 * h2];

    let mut r = resize::new(w1, h1, w2, h2, Gray8, Lanczos3);
    b.iter(|| r.resize(&src1, &mut dst));
}

#[bench]
fn huge_stretch(b: &mut Bencher) {
    let (info, src0) = get_image();
    let (w0, h0) = (info.width as usize, info.height as usize);
    let (w1, h1) = (12, 12);
    let mut src1 = vec![0; w1 * h1];

    resize::new(w0, h0, w1, h1, Gray8, Lanczos3).resize(&src0, &mut src1);
    let (w2, h2) = (1200, 1200);
    let mut dst = vec![0; w2 * h2];

    let mut r = resize::new(w1, h1, w2, h2, Gray8, Triangle);
    b.iter(|| r.resize(&src1, &mut dst));
}

#[bench]
fn precomputed_small(b: &mut Bencher) {
    let (info, src) = get_image();
    let (w1, h1) = (info.width as usize, info.height as usize);
    let (w2, h2) = (100, 100);
    let mut dst = vec![240; w2 * h2];

    let mut r = resize::new(w1, h1, w2, h2, Gray8, Triangle);

    b.iter(|| r.resize(&src, &mut dst));
}

#[bench]
fn precomputed_small_16bit(b: &mut Bencher) {
    let (info, src) = get_image();
    let (w1, h1) = (info.width as usize, info.height as usize);
    let (w2, h2) = (100,100);
    let mut dst = vec![33; w2*h2];
    let src: Vec<_> = src.into_iter().map(|px|{
        let px = px as u16;
        (px << 8) | px
    }).collect();

    let mut r = resize::new(w1, h1, w2, h2, Gray16, Triangle);

    b.iter(|| r.resize(&src, &mut dst));
}

#[bench]
#[allow(deprecated)]
fn recomputed_small(b: &mut Bencher) {
    let (info, src) = get_image();
    let (w1, h1) = (info.width as usize, info.height as usize);
    let (w2, h2) = (100, 100);
    let mut dst = vec![99; w2 * h2];

    b.iter(|| resize::resize(w1, h1, w2, h2, Gray8, Triangle, &src, &mut dst));
}
<|endoftext|>


<commit_before>#![feature(test)]

extern crate pico_sys as pico;
extern crate httparse;

extern crate test;

const REQ: &'static [u8] = b"\
GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\
Host: www.kittyhell.com\r\n\
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
Accept-Encoding: gzip,deflate\r\n\
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
Keep-Alive: 115\r\n\
Connection: keep-alive\r\n\
Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral\r\n\r\n";

#[bench]
fn bench_pico(b: &mut test::Bencher) {
    use std::mem;

    #[repr(C)]
    #[derive(Clone, Copy)]
    struct Header<'a>(&'a [u8], &'a [u8]);

    #[repr(C)]
    struct Headers<'a>(&'a mut [Header<'a>]);
    let method = [0i8; 16];
    let path = [0i8; 16];
    let mut minor_version = 0;
    let mut h = [Header(&[], &[]); 16];
    let mut h_len = h.len();
    let headers = Headers(&mut h);
    let prev_buf_len = 0;

    b.iter(|| {
        let ret = unsafe {
            pico::ffi::phr_parse_request(
                REQ.as_ptr() as *const _,
                REQ.len() as u64,
                &mut method.as_ptr(),
                &mut 16,
                &mut path.as_ptr(),
                &mut 16,
                &mut minor_version,
                mem::transmute::<*mut Header, *mut pico::ffi::phr_header>(headers.0.as_mut_ptr()),
                &mut h_len as *mut usize as *mut _,
                prev_buf_len
            )
        };
        assert_eq!(ret, REQ.len() as i32);
    })
}

#[bench]
fn bench_httparse(b: &mut test::Bencher) {
    let mut headers = [httparse::Header{ name: "", value: &[] }; 16];
    let mut req = httparse::Request::new(&mut headers);
    b.iter(|| {
        assert_eq!(req.parse(REQ).unwrap(), httparse::Status::Complete(REQ.len()));
    })
}
<commit_msg>Closes #27: Compilation error in benchmark test<commit_after>#![feature(test)]

extern crate pico_sys as pico;
extern crate httparse;

extern crate test;

const REQ: &'static [u8] = b"\
GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\
Host: www.kittyhell.com\r\n\
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
Accept-Encoding: gzip,deflate\r\n\
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
Keep-Alive: 115\r\n\
Connection: keep-alive\r\n\
Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral\r\n\r\n";

#[bench]
fn bench_pico(b: &mut test::Bencher) {
    use std::mem;

    #[repr(C)]
    #[derive(Clone, Copy)]
    struct Header<'a>(&'a [u8], &'a [u8]);

    #[repr(C)]
    struct Headers<'a>(&'a mut [Header<'a>]);
    let method = [0i8; 16];
    let path = [0i8; 16];
    let mut minor_version = 0;
    let mut h = [Header(&[], &[]); 16];
    let mut h_len = h.len();
    let headers = Headers(&mut h);
    let prev_buf_len = 0;

    b.iter(|| {
        let ret = unsafe {
            pico::ffi::phr_parse_request(
                REQ.as_ptr() as *const _,
                REQ.len(),
                &mut method.as_ptr(),
                &mut 16,
                &mut path.as_ptr(),
                &mut 16,
                &mut minor_version,
                mem::transmute::<*mut Header, *mut pico::ffi::phr_header>(headers.0.as_mut_ptr()),
                &mut h_len as *mut usize as *mut _,
                prev_buf_len
            )
        };
        assert_eq!(ret, REQ.len() as i32);
    })
}

#[bench]
fn bench_httparse(b: &mut test::Bencher) {
    let mut headers = [httparse::Header{ name: "", value: &[] }; 16];
    let mut req = httparse::Request::new(&mut headers);
    b.iter(|| {
        assert_eq!(req.parse(REQ).unwrap(), httparse::Status::Complete(REQ.len()));
    })
}
<|endoftext|>


<commit_before>use hyper::Client;
use super::helper::KinesisHelper;
use utils::screen::Screen;
use utils::screen::Status;
use rusoto::{
    DefaultCredentialsProvider,
    Region
};

enum State {
    Root,
    End,
    StreamList(Vec<String>),
    ShardList(String, Vec<String>),
    RecordList(String),
    Record(String, String),
}

struct KinesisController {
    kinesis_helper: KinesisHelper<DefaultCredentialsProvider, Client>,
    screen: Screen,
    state: State,
}

impl KinesisController {

    pub fn new(credential_provider: DefaultCredentialsProvider,
               client: Client,
               region: Region) -> KinesisController {

        KinesisController {
            kinesis_helper: KinesisHelper::new(client, credential_provider, region),
            screen: Screen::new(),
            state: State::Root
        }

    }

    /// Event Loop
    pub fn run(&mut self) {

        loop {
            self.state = match &self.state {
                State::Root                           => self.root(),
                State::StreamList(streams)            => self.stream_list(streams),
                State::ShardList(stream_name, shards) => self.shared_list(stream_name, shards),
                State::RecordList(shard_iterator)     => self.record_list(shard_iterator),
                State::Record(shard_iterator, record) => self.record(shard_iterator, record),
                State::End => {
                    break;
                }
            };
        }
    }

    /// initial state
    fn root(&mut self) -> State {
        let commands = vec!["l".to_string(), "q".to_string()];
        self.screen.update_screen("🍓  Kinesis", &commands);

        match self.screen.select_line() {
            Status::Error | Status::Quit => State::End,
            Status::Selected(ref c) if c == "l" => {
                match self.kinesis_helper.list_streams() {
                    Ok(streams) => State::StreamList(streams),
                    Err(e) =>  State::Root
                }
            }
            _ => State::Root
        }
    }

    fn stream_list(&mut self, streams: Vec<String>) -> State {

        self.screen.update_screen("🍓  Kinesis > Streams", &streams);

        match self.screen.select_line() {
            Status::Error | Status::Quit => State::End,
            Status::Selected(stream_name) => {
                match self.kinesis_helper.describe_shards(&stream_name) {
                    Ok(shards) => State::ShardList(stream_name.to_string(), shards),
                    Err(e) => State::Root,
                }
            }
            _ => State::Root
        }
    }

    fn shared_list(&mut self, stream_name: String, shards: Vec<String>) -> State {

        self.screen.update_screen("🍓  Kinesis > Streams > Shards", &shards);

        match self.screen.select_line() {
            Status::Error | Status::Quit => State::End,
            Status::Selected(shard_id) => {
                match self.kinesis_helper.get_shard_iterator(&stream_name, &shard_id) {
                    Ok(shard_iterator) => State::RecordList(shard_iterator),
                    Err(e) => State::Root,
                }
            }
            _ => State::Root
        }

    }

    fn record_list(&mut self, shard_iterator: String) -> State {

        match self.kinesis_helper.get_records(&shard_iterator) {

            Ok((results, iterator)) => {

                self.screen.update_screen("🍓  Kinesis > Streams > Shards > Records",
                                     &self.kinesis_helper.decode_records(&results));

                match self.screen.select_line() {
                    Status::Error | Status::Quit => State::End,
                    Status::Selected(ref c) if c == "n" => {
                        match iterator {
                            Some(iterator) => State::RecordList(iterator),
                            None => State::Root
                        }
                    },
                    Status::Selected(ref record) => State::Record(shard_iterator, record.to_string()),
                    _ => State::Root
                }
            },
            Err(e) => State::Root,
        }
    }

    fn record(&mut self, shard_iterator: String, record: String) -> State {
        self.screen.update_screen("🍓  Kinesis > Streams > Shards > Records > Record", &vec!(String::new()));

        match self.screen.render(&self.kinesis_helper.format_record(&record)) {
            Status::Error | Status::Quit => State::End,
            Status::Escaped  => State::RecordList(shard_iterator),
            _ => State::Root
        }
    }

}
<commit_msg>Remove state from Controller<commit_after>use hyper::Client;
use super::helper::KinesisHelper;
use utils::screen::Screen;
use utils::screen::Status;
use rusoto::{
    DefaultCredentialsProvider,
    Region
};

enum State {
    Root,
    End,
    StreamList(Vec<String>),
    ShardList(String, Vec<String>),
    RecordList(String),
    Record(String, String),
}

struct KinesisController {
    kinesis_helper: KinesisHelper<DefaultCredentialsProvider, Client>,
    screen: Screen,
}

impl KinesisController {

    pub fn new(credential_provider: DefaultCredentialsProvider,
               client: Client,
               region: Region) -> KinesisController {

        KinesisController {
            kinesis_helper: KinesisHelper::new(client, credential_provider, region),
            screen: Screen::new(),
        }

    }

    /// Event Loop
    pub fn run(&mut self) {

        let mut state = State::Root;

        loop {
            state = match state {
                State::Root                           => self.root(),
                State::StreamList(streams)            => self.stream_list(streams),
                State::ShardList(stream_name, shards) => self.shared_list(stream_name, shards),
                State::RecordList(shard_iterator)     => self.record_list(shard_iterator),
                State::Record(shard_iterator, record) => self.record(shard_iterator, record),
                State::End => {
                    break;
                }
            };
        }
    }

    /// initial state
    fn root(&mut self) -> State {
        let commands = vec!["l".to_string(), "q".to_string()];
        self.screen.update_screen("🍓  Kinesis", &commands);

        match self.screen.select_line() {
            Status::Error | Status::Quit => State::End,
            Status::Selected(ref c) if c == "l" => {
                match self.kinesis_helper.list_streams() {
                    Ok(streams) => State::StreamList(streams),
                    Err(e) =>  State::Root
                }
            }
            _ => State::Root
        }
    }

    fn stream_list(&mut self, streams: Vec<String>) -> State {

        self.screen.update_screen("🍓  Kinesis > Streams", &streams);

        match self.screen.select_line() {
            Status::Error | Status::Quit => State::End,
            Status::Selected(stream_name) => {
                match self.kinesis_helper.describe_shards(&stream_name) {
                    Ok(shards) => State::ShardList(stream_name.to_string(), shards),
                    Err(e) => State::Root,
                }
            }
            _ => State::Root
        }
    }

    fn shared_list(&mut self, stream_name: String, shards: Vec<String>) -> State {

        self.screen.update_screen("🍓  Kinesis > Streams > Shards", &shards);

        match self.screen.select_line() {
            Status::Error | Status::Quit => State::End,
            Status::Selected(shard_id) => {
                match self.kinesis_helper.get_shard_iterator(&stream_name, &shard_id) {
                    Ok(shard_iterator) => State::RecordList(shard_iterator),
                    Err(e) => State::Root,
                }
            }
            _ => State::Root
        }

    }

    fn record_list(&mut self, shard_iterator: String) -> State {

        match self.kinesis_helper.get_records(&shard_iterator) {

            Ok((results, iterator)) => {

                self.screen.update_screen("🍓  Kinesis > Streams > Shards > Records",
                                     &self.kinesis_helper.decode_records(&results));

                match self.screen.select_line() {
                    Status::Error | Status::Quit => State::End,
                    Status::Selected(ref c) if c == "n" => {
                        match iterator {
                            Some(iterator) => State::RecordList(iterator),
                            None => State::Root
                        }
                    },
                    Status::Selected(ref record) => State::Record(shard_iterator, record.to_string()),
                    _ => State::Root
                }
            },
            Err(e) => State::Root,
        }
    }

    fn record(&mut self, shard_iterator: String, record: String) -> State {
        self.screen.update_screen("🍓  Kinesis > Streams > Shards > Records > Record", &vec!(String::new()));

        match self.screen.render(&self.kinesis_helper.format_record(&record)) {
            Status::Error | Status::Quit => State::End,
            Status::Escaped  => State::RecordList(shard_iterator),
            _ => State::Root
        }
    }

}
<|endoftext|>


<commit_before>//! Parsing Expression Grammar for Lea
//!
//! rust-peg generates a parser from this definition at compile time

use std::mem;

use expr_parser::build_tree;
use span::{Span, Spanned, mkspanned};
use op::*;
use parsetree::*;

use lea_core::Const;

index -> VarIndex<'input>
    = __* "." __* id:ident      { VarIndex::DotIndex(id) }
    / __* "[" e:expression "]"  { VarIndex::ExprIndex(Box::new(e)) }

variable -> Variable<'input>
    = i:ident idxs:index* {
        let mut v = Spanned::new(i.span, VNamed(i.value));
        for idx in idxs {
            let start = v.span.start;
            let end = match idx {
                VarIndex::ExprIndex(ref e) => e.span.start + e.span.len,
                VarIndex::DotIndex(ref id) => id.span.start + id.span.len,
            };

            v = mkspanned(VIndex(Box::new(v), idx), start, end);
        }

        v
    }

/// Returns the expressions to pass as arguments to an invoked function
callargs -> CallArgs<'input>
    = __* "(" args:expression ** listsep __* ")"    { CallArgs::Normal(args) }
    / __* strn:string                               { CallArgs::String(strn) }
    / __* tbl:tablecons                             { CallArgs::Table(tbl) }

methodname -> Spanned<&'input str>
    = ":" __* name:ident __* { name }

callee -> Expr<'input>
    = v:variable            { mkspanned(EVar(v), start_pos, pos) }
    / "(" e:expression ")"  { mkspanned(EBraced(Box::new(e)), start_pos, pos) }

call -> Call<'input>
    = callee:callee name:methodname? args:callargs+ {
        let mut it = args.into_iter();
        let first_args = it.next().unwrap();
        let callee_span = callee.span.clone();
        let callee = Box::new(callee);
        let mut what = Spanned::new(callee_span, match name {
            None => SimpleCall(callee, first_args),
            Some(name) => MethodCall(callee, name, first_args),
        });

        for list in it {
            what = Spanned::new(callee_span,
                SimpleCall(Box::new(Spanned::new(what.span, ECall(what.value))), list));
        }

        what.value
    }

atom_inner -> Expr<'input>
    = lit:literal           { Spanned::new(lit.span, ELit(lit.value)) }
    / op:unop a:atom        { mkspanned(EUnOp(op, Box::new(a)), start_pos, pos) }
    / t:tablecons           { mkspanned(ETable(t), start_pos, pos) }
    / a:arraycons           { mkspanned(EArray(a), start_pos, pos) }
    / "function" f:funcbody { mkspanned(EFunc(f), start_pos, pos) }
    / "..."                 { mkspanned(EVarArgs, start_pos, pos) }
    / c:call                { mkspanned(ECall(c), start_pos, pos) }
    / callee

// Atomic expression. Either a literal, a unary operator applied to another atom or a full expr
// inside parentheses.
atom -> Expr<'input>
    = __* a:atom_inner __* { a }

tableentry -> TableEntry<'input>
    = __* k:ident __* "=" v:expression              { TableEntry::IdentPair(k, v) }
    / __* "[" k:expression "]" __* "=" v:expression { TableEntry::Pair(k, v) }
    / e:expression                                  { TableEntry::Elem(e) }

tablecons -> TableCons<'input>
    = "{" pairs:tableentry ++ listsep listsep? "}"  { pairs }
    / "{" __* "}"                                   { Vec::new() }

arraycons -> Vec<Expr<'input>>
    = "[" vals:expression_list listsep? "]" { vals }
    / "[" __* "]"                           { Vec::new() }

#[pub]
expression -> Expr<'input>
    = __* left:atom rest:(op:binop r:atom { (op, r) })* __* {
        build_tree(left, rest)
    }

// Comma-separated list of expressions (at least one expression is required)
expression_list -> Vec<Expr<'input>>
    = e:expression ++ listsep { e }

elseif -> Spanned<(Expr<'input>, Block<'input>)>
    = "elseif" e:expression "then" __+ body:block { mkspanned((e, body), start_pos, pos) }

stmt_if -> Stmt<'input>
    = "if" cond:expression "then" __+ body:block elifs:elseif* el:("else" __+ block)? "end" {
        mkspanned(SIf {
            cond: cond,
            body: body,
            elifs: elifs,
            el: el,
        }, start_pos, pos)
    }

// List of comma-separated identifiers with at least one element
identlist -> Vec<Spanned<&'input str>>
    = ids:ident ++ listsep { ids }

// Possibly empty identifier list
eidentlist -> Vec<Spanned<&'input str>>
    = ids:ident ** listsep { ids }

varlist -> Vec<Variable<'input>>
    = vars:variable ++ listsep { vars }

// Returns the parameter list and a boolean indicating whether the function takes variable args
funcparams -> (Vec<Spanned<&'input str>>, bool)
    = "(" __* params:identlist va:(__* ',' __* "...")? __* ")" {
        (params, va.is_some())
    }
    / "(" __* "..." __* ")" { (Vec::new(), true) }
    / "(" __* ")" { (Vec::new(), false) }

funcbody -> Function<'input>
    = __* params:funcparams __* body:block "end" {
        let (pars, varargs) = params;
        Function::<'input>::new(pars, varargs, body)
    }

stmt_inner -> Stmt<'input>
    = stmt_if
    / "while" cond:expression "do" __+ body:block "end" {
        mkspanned(SWhile { cond: cond, body: body }, start_pos, pos)
    }
    / "repeat" __+ body:block "until" cond:expression {
        mkspanned(SRepeat { abort_on: cond, body: body }, start_pos, pos)
    }
    / "for" __+ vars:identlist __+ "in" it:expression_list "do" __+ body:block "end" {
        mkspanned(SForIn {
            vars: vars,
            iter: it,
            body: body,
        }, start_pos, pos)
    }
    / "for" __+ var:ident __* "=" start:expression "," end:expression step:("," expression)? "do" __+ body:block "end" {
        mkspanned(SFor {
            var: var,
            start: start,
            step: step,
            end: end,
            body: body,
        }, start_pos, pos)
    }
    / "function" __+ var:variable method:methodname? f:funcbody {
        mkspanned(match method {
            None => SFunc(var, f),
            Some(method) => SMethod(var, method, f),
        }, start_pos, pos)
    }
    / "local" __+ "function" __+ name:ident f:funcbody {
        mkspanned(SLFunc(name, f), start_pos, pos)
    }
    / "do" __+ b:block "end" { mkspanned(SDo(b), start_pos, pos) }
    / "break" { mkspanned(SBreak, start_pos, pos) }
    / ";" { mkspanned(SSemi, start_pos, pos) }
    / "return" vals:expression_list { mkspanned(SReturn(vals), start_pos, pos) }
    / "return" { mkspanned(SReturn(vec![]), start_pos, pos) }
    / "local" __+ locals:identlist __* "=" exprs:expression_list {
        mkspanned(SDecl(locals, exprs), start_pos, pos)
    }
    / "local" __+ locals:identlist { mkspanned(SDecl(locals, vec![]), start_pos, pos) }
    / c:call { mkspanned(SCall(c), start_pos, pos) }
    / vars:varlist __* "=" vals:expression_list {
        mkspanned(SAssign(vars, vals), start_pos, pos)
    }

#[pub]
statement -> Stmt<'input>
    = __* s:stmt_inner __* { s }

#[pub]
block -> Block<'input>
    = __* s:statement* {
        Block {
            stmts: s,
            span: Span::new(start_pos, pos),
        }
    }

/// Lexical elements

/// Parses Lua's "long bracket" and returns the content. A long bracket is opened with '[',
/// followed by any number of '=' (including 0), followed by '[' and is closed with ']', followed
/// by the same number of '=' used in the opening bracket, followed by ']'.
longbracket -> &'input str
    = "[" open:"="* "[" ( !("]" close:"="* "]" {?
        if open.len() == close.len() { Ok(()) } else { Err("") }
    }) . )* "]" "="* "]" { &match_str[open.len()+2 .. match_str.len() - open.len() - 2] }

comment -> ()
    = "--" longbracket { () }
    / "--" [^\n]*

real_whitespace = " " / "\t" / "\n" / "\r"

__ = real_whitespace / comment

#[pub]
ident -> Spanned<&'input str>
    = [_a-zA-Z][_a-zA-Z0-9]* { mkspanned(match_str, start_pos, pos) }

decstr -> &'input str
    = [0-9]+ { match_str }

hexdigit -> char
    = [0-9a-fA-F] { match_str.chars().next().unwrap() }

hexstr -> &'input str
    = hexdigit+ { match_str }

octstr -> &'input str
    = [0-7]+ { match_str }

// integer without preceding sign
uinteger -> i64
    = "0x" hex:hexstr   { i64::from_str_radix(hex, 16).unwrap() }
    / "0o" oct:octstr   { i64::from_str_radix(oct, 8).unwrap() }
    / [0-9]+            { match_str.parse().unwrap() }

integer -> i64
    = minus:"-"? i:uinteger {
        match minus {
            None => i,
            Some(_) => -i,
        }
    }

/// C-like float (only one part before/after the decimal point is required)
cfloat -> &'input str
    = [0-9]* "." decstr { match_str }
    / decstr "." ! "."  { match_str }   // no second "." allowed, since that's the concat operator

/// Float in exponential notation
efloat -> &'input str
    = ( cfloat / decstr ) "e" [+-]? decstr { match_str }

float -> f64
    = "-"? ( efloat / cfloat ) { match_str.parse().unwrap() }

/// Matches the decimal notation of a single byte: 0-255
/// (leading '0's are ignored, but bytes are limited to at most 3 digits, so "0001" for example
/// will return 0 here and parse the "000", but stop at the "1")
dec_byte -> u8
    = [0-9]{,3} {?
        match match_str.parse() {
            Ok(num) => Ok(num),
            Err(_) => Err("decimal byte"),
        }
    }

esc_seq -> char
    = "\\a"     { '\x07' }  // bell
    / "\\b"     { '\x08' }  // backspace
    / "\\f"     { '\x0c' }  // form feed
    / "\\n"     { '\n' }
    / "\\r"     { '\r' }
    / "\\t"     { '\t' }
    / "\\v"     { '\x0b' }  // vertical tab
    / "\\\\"    { '\\' }
    / "\\\""    { '\"' }
    / "\\'"     { '\'' }
    / "\\\n"    { '\n' }    // '\' at the end of a line continues the string and embeds a "\n"
    / "\\\r\n"  { '\n' }    // There's a bug in Windows where a newline is preceded by a "\r"
    / "\\x" digits:hexdigit{2} {
        let string: String = digits.into_iter().collect();
        let i = u8::from_str_radix(string.as_ref(), 16).unwrap();

        i as char
    }
    / "\\0"     { 0 as char }
    / "\\" val:dec_byte { val as char }

/// \z will skip all whitespace that follows it. Useful for breaking string literals into multiple
/// lines: "bla\z
///         bla" == "blabla"
blank_esc -> ()
    = "\\z" real_whitespace*

_double_quote_content -> char
    = esc_seq
    / blank_esc _double_quote_content
    / [^\\"\n]  { match_str.chars().next().unwrap() }   //" to fix syntax highlighting

_single_quote_content -> char
    = esc_seq
    / blank_esc _single_quote_content
    / [^\\'\n]  { match_str.chars().next().unwrap() }

#[pub]
string -> String
    = '"' chars:_double_quote_content* blank_esc? '"'   { chars.into_iter().collect() }
    / '\'' chars:_single_quote_content* blank_esc? '\'' { chars.into_iter().collect() }
    / content:longbracket                               { content.to_owned() }

boolean -> bool
    = "true" { true } / "false" { false }

_literal -> Const
    = f:float   { Const::Number(f.into()) }
    / i:integer { Const::Number(i.into()) }
    / s:string  { Const::Str(s) }
    / b:boolean { Const::Bool(b) }
    / "nil"     { Const::Nil }

#[pub]
literal -> Spanned<Const>
    = l:_literal { mkspanned(l, start_pos, pos) }

binop -> BinOp
    = "==" {BinOp::Eq} / "!=" {BinOp::NEq} / "~=" {BinOp::NEqLua} / ">=" {BinOp::GEq}
    / "<=" {BinOp::LEq} / ">>" {BinOp::ShiftR} / "<<" {BinOp::ShiftL} / "<" {BinOp::Less}
    / ">" {BinOp::Greater} / "&&" {BinOp::LAnd} / "and" {BinOp::LAndLua} / "||" {BinOp::LOr}
    / "or" {BinOp::LOrLua} / "&" {BinOp::BAnd} / "~" {BinOp::BXor} / "|" {BinOp::BOr}
    / "+" {BinOp::Add} / "-" {BinOp::Sub} / "*" {BinOp::Mul} / "/" {BinOp::Div} / "%" {BinOp::Mod}
    / "^" {BinOp::Pow} / ".." {BinOp::Concat}

unop -> UnOp
    = "-" {UnOp::Negate} / "!" {UnOp::LNot} / "not" {UnOp::LNotLua} / "~" {UnOp::BNot}
    / "#" {UnOp::Len}

listsep = __* ',' __*
<commit_msg>Parse binary literals<commit_after>//! Parsing Expression Grammar for Lea
//!
//! rust-peg generates a parser from this definition at compile time

use std::mem;

use expr_parser::build_tree;
use span::{Span, Spanned, mkspanned};
use op::*;
use parsetree::*;

use lea_core::Const;

index -> VarIndex<'input>
    = __* "." __* id:ident      { VarIndex::DotIndex(id) }
    / __* "[" e:expression "]"  { VarIndex::ExprIndex(Box::new(e)) }

variable -> Variable<'input>
    = i:ident idxs:index* {
        let mut v = Spanned::new(i.span, VNamed(i.value));
        for idx in idxs {
            let start = v.span.start;
            let end = match idx {
                VarIndex::ExprIndex(ref e) => e.span.start + e.span.len,
                VarIndex::DotIndex(ref id) => id.span.start + id.span.len,
            };

            v = mkspanned(VIndex(Box::new(v), idx), start, end);
        }

        v
    }

/// Returns the expressions to pass as arguments to an invoked function
callargs -> CallArgs<'input>
    = __* "(" args:expression ** listsep __* ")"    { CallArgs::Normal(args) }
    / __* strn:string                               { CallArgs::String(strn) }
    / __* tbl:tablecons                             { CallArgs::Table(tbl) }

methodname -> Spanned<&'input str>
    = ":" __* name:ident __* { name }

callee -> Expr<'input>
    = v:variable            { mkspanned(EVar(v), start_pos, pos) }
    / "(" e:expression ")"  { mkspanned(EBraced(Box::new(e)), start_pos, pos) }

call -> Call<'input>
    = callee:callee name:methodname? args:callargs+ {
        let mut it = args.into_iter();
        let first_args = it.next().unwrap();
        let callee_span = callee.span.clone();
        let callee = Box::new(callee);
        let mut what = Spanned::new(callee_span, match name {
            None => SimpleCall(callee, first_args),
            Some(name) => MethodCall(callee, name, first_args),
        });

        for list in it {
            what = Spanned::new(callee_span,
                SimpleCall(Box::new(Spanned::new(what.span, ECall(what.value))), list));
        }

        what.value
    }

atom_inner -> Expr<'input>
    = lit:literal           { Spanned::new(lit.span, ELit(lit.value)) }
    / op:unop a:atom        { mkspanned(EUnOp(op, Box::new(a)), start_pos, pos) }
    / t:tablecons           { mkspanned(ETable(t), start_pos, pos) }
    / a:arraycons           { mkspanned(EArray(a), start_pos, pos) }
    / "function" f:funcbody { mkspanned(EFunc(f), start_pos, pos) }
    / "..."                 { mkspanned(EVarArgs, start_pos, pos) }
    / c:call                { mkspanned(ECall(c), start_pos, pos) }
    / callee

// Atomic expression. Either a literal, a unary operator applied to another atom or a full expr
// inside parentheses.
atom -> Expr<'input>
    = __* a:atom_inner __* { a }

tableentry -> TableEntry<'input>
    = __* k:ident __* "=" v:expression              { TableEntry::IdentPair(k, v) }
    / __* "[" k:expression "]" __* "=" v:expression { TableEntry::Pair(k, v) }
    / e:expression                                  { TableEntry::Elem(e) }

tablecons -> TableCons<'input>
    = "{" pairs:tableentry ++ listsep listsep? "}"  { pairs }
    / "{" __* "}"                                   { Vec::new() }

arraycons -> Vec<Expr<'input>>
    = "[" vals:expression_list listsep? "]" { vals }
    / "[" __* "]"                           { Vec::new() }

#[pub]
expression -> Expr<'input>
    = __* left:atom rest:(op:binop r:atom { (op, r) })* __* {
        build_tree(left, rest)
    }

// Comma-separated list of expressions (at least one expression is required)
expression_list -> Vec<Expr<'input>>
    = e:expression ++ listsep { e }

elseif -> Spanned<(Expr<'input>, Block<'input>)>
    = "elseif" e:expression "then" __+ body:block { mkspanned((e, body), start_pos, pos) }

stmt_if -> Stmt<'input>
    = "if" cond:expression "then" __+ body:block elifs:elseif* el:("else" __+ block)? "end" {
        mkspanned(SIf {
            cond: cond,
            body: body,
            elifs: elifs,
            el: el,
        }, start_pos, pos)
    }

// List of comma-separated identifiers with at least one element
identlist -> Vec<Spanned<&'input str>>
    = ids:ident ++ listsep { ids }

// Possibly empty identifier list
eidentlist -> Vec<Spanned<&'input str>>
    = ids:ident ** listsep { ids }

varlist -> Vec<Variable<'input>>
    = vars:variable ++ listsep { vars }

// Returns the parameter list and a boolean indicating whether the function takes variable args
funcparams -> (Vec<Spanned<&'input str>>, bool)
    = "(" __* params:identlist va:(__* ',' __* "...")? __* ")" {
        (params, va.is_some())
    }
    / "(" __* "..." __* ")" { (Vec::new(), true) }
    / "(" __* ")" { (Vec::new(), false) }

funcbody -> Function<'input>
    = __* params:funcparams __* body:block "end" {
        let (pars, varargs) = params;
        Function::<'input>::new(pars, varargs, body)
    }

stmt_inner -> Stmt<'input>
    = stmt_if
    / "while" cond:expression "do" __+ body:block "end" {
        mkspanned(SWhile { cond: cond, body: body }, start_pos, pos)
    }
    / "repeat" __+ body:block "until" cond:expression {
        mkspanned(SRepeat { abort_on: cond, body: body }, start_pos, pos)
    }
    / "for" __+ vars:identlist __+ "in" it:expression_list "do" __+ body:block "end" {
        mkspanned(SForIn {
            vars: vars,
            iter: it,
            body: body,
        }, start_pos, pos)
    }
    / "for" __+ var:ident __* "=" start:expression "," end:expression step:("," expression)? "do" __+ body:block "end" {
        mkspanned(SFor {
            var: var,
            start: start,
            step: step,
            end: end,
            body: body,
        }, start_pos, pos)
    }
    / "function" __+ var:variable method:methodname? f:funcbody {
        mkspanned(match method {
            None => SFunc(var, f),
            Some(method) => SMethod(var, method, f),
        }, start_pos, pos)
    }
    / "local" __+ "function" __+ name:ident f:funcbody {
        mkspanned(SLFunc(name, f), start_pos, pos)
    }
    / "do" __+ b:block "end" { mkspanned(SDo(b), start_pos, pos) }
    / "break" { mkspanned(SBreak, start_pos, pos) }
    / ";" { mkspanned(SSemi, start_pos, pos) }
    / "return" vals:expression_list { mkspanned(SReturn(vals), start_pos, pos) }
    / "return" { mkspanned(SReturn(vec![]), start_pos, pos) }
    / "local" __+ locals:identlist __* "=" exprs:expression_list {
        mkspanned(SDecl(locals, exprs), start_pos, pos)
    }
    / "local" __+ locals:identlist { mkspanned(SDecl(locals, vec![]), start_pos, pos) }
    / c:call { mkspanned(SCall(c), start_pos, pos) }
    / vars:varlist __* "=" vals:expression_list {
        mkspanned(SAssign(vars, vals), start_pos, pos)
    }

#[pub]
statement -> Stmt<'input>
    = __* s:stmt_inner __* { s }

#[pub]
block -> Block<'input>
    = __* s:statement* {
        Block {
            stmts: s,
            span: Span::new(start_pos, pos),
        }
    }

/// Lexical elements

/// Parses Lua's "long bracket" and returns the content. A long bracket is opened with '[',
/// followed by any number of '=' (including 0), followed by '[' and is closed with ']', followed
/// by the same number of '=' used in the opening bracket, followed by ']'.
longbracket -> &'input str
    = "[" open:"="* "[" ( !("]" close:"="* "]" {?
        if open.len() == close.len() { Ok(()) } else { Err("") }
    }) . )* "]" "="* "]" { &match_str[open.len()+2 .. match_str.len() - open.len() - 2] }

comment -> ()
    = "--" longbracket { () }
    / "--" [^\n]*

real_whitespace = " " / "\t" / "\n" / "\r"

__ = real_whitespace / comment

#[pub]
ident -> Spanned<&'input str>
    = [_a-zA-Z][_a-zA-Z0-9]* { mkspanned(match_str, start_pos, pos) }

decstr -> &'input str
    = [0-9]+ { match_str }

hexdigit -> char
    = [0-9a-fA-F] { match_str.chars().next().unwrap() }

hexstr -> &'input str
    = [0-0a-fA-F]+ { match_str }

octstr -> &'input str
    = [0-7]+ { match_str }

binstr -> &'input str
    = [01]+ { match_str }

// integer without preceding sign
uinteger -> i64
    = "0x" hex:hexstr   {? i64::from_str_radix(hex, 16).map_err(|_| "hex string") }
    / "0o" oct:octstr   {? i64::from_str_radix(oct, 8).map_err(|_| "oct string") }
    / "0b" bin:binstr   {? i64::from_str_radix(bin, 2).map_err(|_| "bin string") }
    / [0-9]+            {? match_str.parse().map_err(|_| "integer literal") }

integer -> i64
    = minus:"-"? i:uinteger {
        match minus {
            None => i,
            Some(_) => -i,
        }
    }

/// C-like float (only one part before/after the decimal point is required)
cfloat -> &'input str
    = [0-9]* "." decstr { match_str }
    / decstr "." ! "."  { match_str }   // no second "." allowed, since that's the concat operator

/// Float in exponential notation
efloat -> &'input str
    = ( cfloat / decstr ) "e" [+-]? decstr { match_str }

float -> f64
    = "-"? ( efloat / cfloat ) { match_str.parse().unwrap() }

/// Matches the decimal notation of a single byte: 0-255
/// (leading '0's are ignored, but bytes are limited to at most 3 digits, so "0001" for example
/// will return 0 here and parse the "000", but stop at the "1")
dec_byte -> u8
    = [0-9]{,3} {?
        match match_str.parse() {
            Ok(num) => Ok(num),
            Err(_) => Err("decimal byte"),
        }
    }

esc_seq -> char
    = "\\a"     { '\x07' }  // bell
    / "\\b"     { '\x08' }  // backspace
    / "\\f"     { '\x0c' }  // form feed
    / "\\n"     { '\n' }
    / "\\r"     { '\r' }
    / "\\t"     { '\t' }
    / "\\v"     { '\x0b' }  // vertical tab
    / "\\\\"    { '\\' }
    / "\\\""    { '\"' }
    / "\\'"     { '\'' }
    / "\\\n"    { '\n' }    // '\' at the end of a line continues the string and embeds a "\n"
    / "\\\r\n"  { '\n' }    // There's a bug in Windows where a newline is preceded by a "\r"
    / "\\x" digits:hexdigit{2} {
        let string: String = digits.into_iter().collect();
        let i = u8::from_str_radix(string.as_ref(), 16).unwrap();

        i as char
    }
    / "\\0"     { 0 as char }
    / "\\" val:dec_byte { val as char }

/// \z will skip all whitespace that follows it. Useful for breaking string literals into multiple
/// lines: "bla\z
///         bla" == "blabla"
blank_esc -> ()
    = "\\z" real_whitespace*

_double_quote_content -> char
    = esc_seq
    / blank_esc _double_quote_content
    / [^\\"\n]  { match_str.chars().next().unwrap() }   //" to fix syntax highlighting

_single_quote_content -> char
    = esc_seq
    / blank_esc _single_quote_content
    / [^\\'\n]  { match_str.chars().next().unwrap() }

#[pub]
string -> String
    = '"' chars:_double_quote_content* blank_esc? '"'   { chars.into_iter().collect() }
    / '\'' chars:_single_quote_content* blank_esc? '\'' { chars.into_iter().collect() }
    / content:longbracket                               { content.to_owned() }

boolean -> bool
    = "true" { true } / "false" { false }

_literal -> Const
    = f:float   { Const::Number(f.into()) }
    / i:integer { Const::Number(i.into()) }
    / s:string  { Const::Str(s) }
    / b:boolean { Const::Bool(b) }
    / "nil"     { Const::Nil }

#[pub]
literal -> Spanned<Const>
    = l:_literal { mkspanned(l, start_pos, pos) }

binop -> BinOp
    = "==" {BinOp::Eq} / "!=" {BinOp::NEq} / "~=" {BinOp::NEqLua} / ">=" {BinOp::GEq}
    / "<=" {BinOp::LEq} / ">>" {BinOp::ShiftR} / "<<" {BinOp::ShiftL} / "<" {BinOp::Less}
    / ">" {BinOp::Greater} / "&&" {BinOp::LAnd} / "and" {BinOp::LAndLua} / "||" {BinOp::LOr}
    / "or" {BinOp::LOrLua} / "&" {BinOp::BAnd} / "~" {BinOp::BXor} / "|" {BinOp::BOr}
    / "+" {BinOp::Add} / "-" {BinOp::Sub} / "*" {BinOp::Mul} / "/" {BinOp::Div} / "%" {BinOp::Mod}
    / "^" {BinOp::Pow} / ".." {BinOp::Concat}

unop -> UnOp
    = "-" {UnOp::Negate} / "!" {UnOp::LNot} / "not" {UnOp::LNotLua} / "~" {UnOp::BNot}
    / "#" {UnOp::Len}

listsep = __* ',' __*
<|endoftext|>


<commit_before>use crate::types::{
    EnvFile, EnvValue, EnvValueConditioned, EnvValueDecode, EnvValuePathGlob, EnvValueScript,
};
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use petgraph::algo::{kosaraju_scc, toposort};
use petgraph::graphmap::{DiGraphMap, GraphMap};
use regex::Regex;
use std::collections::HashSet;

#[cfg(test)]
#[path = "env_test.rs"]
mod env_test;

static RE_VARIABLE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\$\{.*?}").unwrap());

fn env_unique<'a>(vals: &'a [&'a IndexMap<String, EnvValue>]) -> Vec<(&'a str, &'a EnvValue)> {
    let mut visited = HashSet::new();
    let mut unique = vec![];

    // iterate through the list in reverse, only taking the first value, then
    // reversing again to make sure that we still adhere to the order.
    // This way we will only ever take the latest value.
    for (key, val) in vals.iter().map(|map| map.iter()).flatten().rev() {
        if visited.contains(&key.as_str()) {
            continue;
        }

        visited.insert(key.as_str());
        unique.push((key.as_str(), val))
    }

    unique.reverse();
    unique
}

static RE_SH_PARAM: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"\$(?:([\w-]+)|\{#?([\w-]+)})").unwrap());

/// The depends_on for a script is a bit more complicated to find,
/// this is because it invokes a shell script (`sh`).
/// This means, that we need to go through the shell script and find all environmental variables
/// that are used, but haven't been declared yet.
///
/// One can also explicitly use a list `depends_on = [...]` to overwrite the existing behavior
/// for finding all dependencies.
///
/// This implementation is very conservative, and regardless of the context, will just capture
/// all environment variables that have been used.
///
/// A future implementation might further extend this, by looking for `'` usage,
/// `declare` statements of `[declare] name=value`, and then exclude those in the
/// subsequent analysis.
///
/// To be completely compliant this implementation is based on:
/// https://pubs.opengroup.org/onlinepubs/9699919799/, the official POSIX standard.
///
/// Chapter 8 Environment Variables:
///
/// > Environment variable names used by the utilities in the Shell and Utilities volume of
/// > POSIX.1-2017 consist solely of uppercase letters, digits, and the <underscore> ( '_' )
/// > from the characters defined in Portable Character Set and do not begin with a digit.
/// > Other characters may be permitted by an implementation;
/// > applications shall tolerate the presence of such names.
/// > Uppercase and lowercase letters shall retain their unique identities and shall not be folded
/// > together.
/// > The name space of environment variable names containing lowercase letters is
/// > reserved for applications.
/// > Applications can define any environment variables with names
/// > from this name space without modifying the behavior of the standard utilities.
///
/// A declaration in a shell script is: `declare var=...` where `declare` is optional.
fn env_depends_on_find_script(val: &EnvValueScript) -> Vec<&str> {
    if let Some(depends_on) = &val.depends_on {
        return depends_on.iter().map(String::as_str).collect();
    }

    let mut depends_on = vec![];
    for line in &val.script {
        for captures in RE_SH_PARAM.captures_iter(line) {
            if let Some(capture) = captures.get(1).or_else(|| captures.get(2)) {
                depends_on.push(capture.as_str());
            }
        }
    }

    depends_on
}

fn env_depends_on_find(val: &str) -> Vec<&str> {
    let mut depends_on = vec![];

    for matched in RE_VARIABLE.find_iter(val) {
        let matched = matched.as_str();
        // remove the first two characters (`${`)
        let (_, matched) = matched.split_at(2);
        // remove the last character (`}`)
        let (matched, _) = matched.split_at(matched.len() - 1);

        depends_on.push(matched.trim());
    }

    depends_on
}

fn env_depends_on(val: &EnvValue) -> Vec<&str> {
    match val {
        EnvValue::Value(value) => env_depends_on_find(value),
        EnvValue::Decode(EnvValueDecode { source, .. }) => env_depends_on_find(source),
        EnvValue::List(values) => values
            .iter()
            .map(|value| env_depends_on_find(value))
            .reduce(|mut acc, mut other| {
                acc.append(&mut other);
                acc
            })
            .unwrap_or_default(),
        EnvValue::Conditional(EnvValueConditioned { value, .. }) => env_depends_on_find(value),
        EnvValue::PathGlob(EnvValuePathGlob { glob, .. }) => env_depends_on_find(glob),
        EnvValue::Script(script) => env_depends_on_find_script(script),
        _ => vec![],
    }
}

pub(crate) fn merge_env(
    base: &IndexMap<String, EnvValue>,
    ext: &IndexMap<String, EnvValue>,
) -> Result<IndexMap<String, EnvValue>, String> {
    let combined = [base, ext];
    let combined: Vec<_> = env_unique(&combined);

    let mut graph: GraphMap<&str, (), _> = DiGraphMap::new();

    let keys: HashSet<_> = combined.iter().map(|(key, _)| *key).collect();
    for key in keys {
        graph.add_node(key);
    }

    for (key, val) in &combined {
        // combined is unique (only latest value),
        // which is why we do not need to delete any previously declared outbound edges.

        // if the env variable is in the current scope add an edge,
        // otherwise it is referencing an external variable.
        for used in env_depends_on(val).into_iter() {
            if graph.contains_node(used) {
                graph.add_edge(*key, used, ());
            }
        }
    }

    debug!("env dependencies: {:?}", graph);

    let variables = match toposort(&graph, None) {
        Ok(iter) => iter,
        Err(_) => {
            // cycle has been detected, for better performance we now only
            // execute scc.
            // In strongly-connected-components every vertex
            // (node) is reachable from every other node.
            // This means that there **must** be a cycle.
            // This isn't strictly necessary, but aids when debugging.
            let mut err =
                "A cycle between different env variables has been detected (E001, see: https://github.com/sagiegurari/cargo-make#e001 for more information)."
                    .to_owned();
            for scc in kosaraju_scc(&graph) {
                let render = scc
                    .iter()
                    .chain(scc.first())
                    .map(ToString::to_string)
                    .reduce(|acc, name| format!("{acc} -> {name}"));

                if let Some(render) = render {
                    err.push_str(&format!(" Cycle: {}.", render));
                }
            }

            return Err(err);
        }
    };

    let mut merge = IndexMap::new();
    for name in variables.into_iter().rev() {
        if name.starts_with("CARGO_MAKE_CURRENT_TASK_") {
            // CARGO_MAKE_CURRENT_TASK are handled differently and **always**
            // retain their old value
            if let Some(value) = base.get(name) {
                merge.insert(name.to_owned(), value.clone());
            }

            continue;
        }

        if let Some((key, val)) = combined
            .iter()
            .filter(|(key, _)| *key == name)
            .last()
            .cloned()
        {
            // we need to check if the base and ext both are a profile,
            // in that case we need to do some special handling,
            // by merging them as well.
            match (base.get(key), ext.get(key)) {
                (Some(EnvValue::Profile(base)), Some(EnvValue::Profile(ext))) => {
                    merge.insert(key.to_owned(), EnvValue::Profile(merge_env(base, ext)?));
                }
                _ => {
                    merge.insert(key.to_owned(), val.clone());
                }
            }
        }
    }

    Ok(merge)
}

pub(crate) fn merge_env_files(
    base: &mut Vec<EnvFile>,
    extended: &mut Vec<EnvFile>,
) -> Vec<EnvFile> {
    [&extended[..], &base[..]].concat()
}

pub(crate) fn merge_env_scripts(base: &mut Vec<String>, extended: &mut Vec<String>) -> Vec<String> {
    [&extended[..], &base[..]].concat()
}
<commit_msg>fix: format msrv<commit_after>use crate::types::{
    EnvFile, EnvValue, EnvValueConditioned, EnvValueDecode, EnvValuePathGlob, EnvValueScript,
};
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use petgraph::algo::{kosaraju_scc, toposort};
use petgraph::graphmap::{DiGraphMap, GraphMap};
use regex::Regex;
use std::collections::HashSet;

#[cfg(test)]
#[path = "env_test.rs"]
mod env_test;

static RE_VARIABLE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\$\{.*?}").unwrap());

fn env_unique<'a>(vals: &'a [&'a IndexMap<String, EnvValue>]) -> Vec<(&'a str, &'a EnvValue)> {
    let mut visited = HashSet::new();
    let mut unique = vec![];

    // iterate through the list in reverse, only taking the first value, then
    // reversing again to make sure that we still adhere to the order.
    // This way we will only ever take the latest value.
    for (key, val) in vals.iter().map(|map| map.iter()).flatten().rev() {
        if visited.contains(&key.as_str()) {
            continue;
        }

        visited.insert(key.as_str());
        unique.push((key.as_str(), val))
    }

    unique.reverse();
    unique
}

static RE_SH_PARAM: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"\$(?:([\w-]+)|\{#?([\w-]+)})").unwrap());

/// The depends_on for a script is a bit more complicated to find,
/// this is because it invokes a shell script (`sh`).
/// This means, that we need to go through the shell script and find all environmental variables
/// that are used, but haven't been declared yet.
///
/// One can also explicitly use a list `depends_on = [...]` to overwrite the existing behavior
/// for finding all dependencies.
///
/// This implementation is very conservative, and regardless of the context, will just capture
/// all environment variables that have been used.
///
/// A future implementation might further extend this, by looking for `'` usage,
/// `declare` statements of `[declare] name=value`, and then exclude those in the
/// subsequent analysis.
///
/// To be completely compliant this implementation is based on:
/// https://pubs.opengroup.org/onlinepubs/9699919799/, the official POSIX standard.
///
/// Chapter 8 Environment Variables:
///
/// > Environment variable names used by the utilities in the Shell and Utilities volume of
/// > POSIX.1-2017 consist solely of uppercase letters, digits, and the <underscore> ( '_' )
/// > from the characters defined in Portable Character Set and do not begin with a digit.
/// > Other characters may be permitted by an implementation;
/// > applications shall tolerate the presence of such names.
/// > Uppercase and lowercase letters shall retain their unique identities and shall not be folded
/// > together.
/// > The name space of environment variable names containing lowercase letters is
/// > reserved for applications.
/// > Applications can define any environment variables with names
/// > from this name space without modifying the behavior of the standard utilities.
///
/// A declaration in a shell script is: `declare var=...` where `declare` is optional.
fn env_depends_on_find_script(val: &EnvValueScript) -> Vec<&str> {
    if let Some(depends_on) = &val.depends_on {
        return depends_on.iter().map(String::as_str).collect();
    }

    let mut depends_on = vec![];
    for line in &val.script {
        for captures in RE_SH_PARAM.captures_iter(line) {
            if let Some(capture) = captures.get(1).or_else(|| captures.get(2)) {
                depends_on.push(capture.as_str());
            }
        }
    }

    depends_on
}

fn env_depends_on_find(val: &str) -> Vec<&str> {
    let mut depends_on = vec![];

    for matched in RE_VARIABLE.find_iter(val) {
        let matched = matched.as_str();
        // remove the first two characters (`${`)
        let (_, matched) = matched.split_at(2);
        // remove the last character (`}`)
        let (matched, _) = matched.split_at(matched.len() - 1);

        depends_on.push(matched.trim());
    }

    depends_on
}

fn env_depends_on(val: &EnvValue) -> Vec<&str> {
    match val {
        EnvValue::Value(value) => env_depends_on_find(value),
        EnvValue::Decode(EnvValueDecode { source, .. }) => env_depends_on_find(source),
        EnvValue::List(values) => values
            .iter()
            .map(|value| env_depends_on_find(value))
            .reduce(|mut acc, mut other| {
                acc.append(&mut other);
                acc
            })
            .unwrap_or_default(),
        EnvValue::Conditional(EnvValueConditioned { value, .. }) => env_depends_on_find(value),
        EnvValue::PathGlob(EnvValuePathGlob { glob, .. }) => env_depends_on_find(glob),
        EnvValue::Script(script) => env_depends_on_find_script(script),
        _ => vec![],
    }
}

pub(crate) fn merge_env(
    base: &IndexMap<String, EnvValue>,
    ext: &IndexMap<String, EnvValue>,
) -> Result<IndexMap<String, EnvValue>, String> {
    let combined = [base, ext];
    let combined: Vec<_> = env_unique(&combined);

    let mut graph: GraphMap<&str, (), _> = DiGraphMap::new();

    let keys: HashSet<_> = combined.iter().map(|(key, _)| *key).collect();
    for key in keys {
        graph.add_node(key);
    }

    for (key, val) in &combined {
        // combined is unique (only latest value),
        // which is why we do not need to delete any previously declared outbound edges.

        // if the env variable is in the current scope add an edge,
        // otherwise it is referencing an external variable.
        for used in env_depends_on(val).into_iter() {
            if graph.contains_node(used) {
                graph.add_edge(*key, used, ());
            }
        }
    }

    debug!("env dependencies: {:?}", graph);

    let variables = match toposort(&graph, None) {
        Ok(iter) => iter,
        Err(_) => {
            // cycle has been detected, for better performance we now only
            // execute scc.
            // In strongly-connected-components every vertex
            // (node) is reachable from every other node.
            // This means that there **must** be a cycle.
            // This isn't strictly necessary, but aids when debugging.
            let mut err =
                "A cycle between different env variables has been detected (E001, see: https://github.com/sagiegurari/cargo-make#e001 for more information)."
                    .to_owned();
            for scc in kosaraju_scc(&graph) {
                let render = scc
                    .iter()
                    .chain(scc.first())
                    .map(ToString::to_string)
                    .reduce(|acc, name| format!("{} -> {}", acc, name));

                if let Some(render) = render {
                    err.push_str(&format!(" Cycle: {}.", render));
                }
            }

            return Err(err);
        }
    };

    let mut merge = IndexMap::new();
    for name in variables.into_iter().rev() {
        if name.starts_with("CARGO_MAKE_CURRENT_TASK_") {
            // CARGO_MAKE_CURRENT_TASK are handled differently and **always**
            // retain their old value
            if let Some(value) = base.get(name) {
                merge.insert(name.to_owned(), value.clone());
            }

            continue;
        }

        if let Some((key, val)) = combined
            .iter()
            .filter(|(key, _)| *key == name)
            .last()
            .cloned()
        {
            // we need to check if the base and ext both are a profile,
            // in that case we need to do some special handling,
            // by merging them as well.
            match (base.get(key), ext.get(key)) {
                (Some(EnvValue::Profile(base)), Some(EnvValue::Profile(ext))) => {
                    merge.insert(key.to_owned(), EnvValue::Profile(merge_env(base, ext)?));
                }
                _ => {
                    merge.insert(key.to_owned(), val.clone());
                }
            }
        }
    }

    Ok(merge)
}

pub(crate) fn merge_env_files(
    base: &mut Vec<EnvFile>,
    extended: &mut Vec<EnvFile>,
) -> Vec<EnvFile> {
    [&extended[..], &base[..]].concat()
}

pub(crate) fn merge_env_scripts(base: &mut Vec<String>, extended: &mut Vec<String>) -> Vec<String> {
    [&extended[..], &base[..]].concat()
}
<|endoftext|>