<commit_before>use lexer::Token;

use common::types::Ty;
use plan::expr;
use plan::expr::*;

use self::PartParsingResult::{Done, NotComplete, Error};

/// Associative operator with precedence.
///
/// This is the enum which specifies operator precedence and fixity to the parser.
#[derive(Debug, PartialEq, Eq)]
pub enum AssocOp {
    /// `+`
    Add,
    /// `-`
    Subtract,
    /// `*`
    Multiply,
    /// `/`
    Divide,
    /// `%`
    Modulus,
    /// `&`
    And,
    /// `|`
    Or,
    /// `==`
    Equal,
    /// `<`
    Less,
    /// `<=`
    LessEqual,
    /// `!=`
    NotEqual,
    /// `>`
    Greater,
    /// `>=`
    GreaterEqual,
    /// `=`
    Assign,
}

impl AssocOp {
  fn from_token(t: &Token) -> Option<AssocOp> {
    match *t {
      Token::Multiply => Some(AssocOp::Multiply),
      Token::Divide => Some(AssocOp::Divide),
      Token::Modulus => Some(AssocOp::Modulus),
      Token::Plus => Some(AssocOp::Add),
      Token::Minus => Some(AssocOp::Subtract),
      Token::Lt => Some(AssocOp::Less),
      Token::Le => Some(AssocOp::LessEqual),
      Token::Ge => Some(AssocOp::GreaterEqual),
      Token::Gt => Some(AssocOp::Greater),
      Token::Eq => Some(AssocOp::Equal),
      Token::Ne => Some(AssocOp::NotEqual),
      Token::And => Some(AssocOp::And),
      Token::Or => Some(AssocOp::Or),
      _ => None
    }
  }

  fn to_expr(t: &Token, lhs: &Expr, rhs: &Expr) -> Option<Expr> {
    match *t {
      Token::Multiply => Some(expr::Mul(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Divide => Some(expr::Div(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Modulus => Some(expr::Modulus(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Plus => Some(expr::Add(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Minus => Some(expr::Sub(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Lt => Some(expr::Cmp(CmpOp::Lt, lhs.clone(), rhs.clone())),
      Token::Le => Some(expr::Cmp(CmpOp::Le, lhs.clone(), rhs.clone())),
      Token::Ge => Some(expr::Cmp(CmpOp::Ge, lhs.clone(), rhs.clone())),
      Token::Gt => Some(expr::Cmp(CmpOp::Gt, lhs.clone(), rhs.clone())),
      Token::Eq => Some(expr::Cmp(CmpOp::Eq, lhs.clone(), rhs.clone())),
      Token::Ne => Some(expr::Cmp(CmpOp::Ne, lhs.clone(), rhs.clone())),
      Token::And => Some(expr::And(lhs.clone(), rhs.clone())),
      Token::Or => Some(expr::Or(lhs.clone(), rhs.clone())),
      _ => None
    }
  }

  fn precedence(&self) -> usize {
    use self::AssocOp::*;
      match *self {
        Multiply | Divide | Modulus => 13,
        Add | Subtract => 12,
        And => 10,
        Or => 8,
        Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual => 7,
        _ => 0,
    }
  }
}

enum PartParsingResult<T> {
  Done(T, Vec<Token>),
  NotComplete,
  Error(String)
}

pub type ParsingResult = Result<(Vec<Expr>, Vec<Token>), String>;

macro_rules! parse_try(
  ($function: ident, $tokens: ident, $parsed_tokens: ident) => (
    parse_try!($function, $tokens, $parsed_tokens, )
  );

  ($function: ident, $tokens: ident, $parsed_tokens: ident, $($arg: expr),*) => (
    match $function($tokens, $($arg),*) {
      Done(ast, toks) => {
        $parsed_tokens.extend(toks.into_iter());
        ast
      },
      NotComplete => {
        $parsed_tokens.reverse();
        $tokens.extend($parsed_tokens.into_iter());
        return NotComplete
      },
      Error(message) => return Error(message)
    }
  )
);

macro_rules! expect_tokens(
  ([ $($token: pat, $value: expr, $result: stmt);+ ] <= $tokens: ident, $parsed_tokens: ident, $error: expr) => (
    match $tokens.pop() {
      $(
        Some($token) => {
          $parsed_tokens.push($value);
          $result
        },
      )+
      None => {
        $parsed_tokens.reverse();
        $tokens.extend($parsed_tokens.into_iter());
        return NotComplete
      },
      _ => return error($error)
    }
  );

  ([ $($token:pat, $value:expr, $result: stmt);+ ] else $not_matched: block <= $tokens: ident, $parsed_tokens: ident) => (
    match $tokens.last().map(|t| t.clone()) {
      $(
        Some($token) => {
          $tokens.pop();
          $parsed_tokens.push($value);
          $result
        },
      )+
      _ => {$not_matched}
    }
  );
);

fn error<T>(message: &str) -> PartParsingResult<T> {
  Error(message.to_string())
}

pub fn parse(tokens: &[Token], parsed_trees: &[Expr]) -> ParsingResult {
  let mut rest = tokens.to_vec();
  rest.reverse(); // to use like a stack

  let mut asts = parsed_trees.to_vec();

  loop {
    let cur_token = match rest.last() {
      Some(t) => t.clone(),
      None => break
    };

    let result = match cur_token {
      // Fn => parse_function(&mut rest), TODO
      // Extern => parse_extern(&mut rest), TODO
      Token::Delimiter => {
        rest.pop(); continue
      },
      _ => parse_expr(&mut rest),
    };

    match result {
      Done(ast_node, _) => asts.push(ast_node),
      NotComplete => break,
      Error(message) => return Err(message),
    }
  }

  rest.reverse();
  Ok((asts, rest))
}

fn parse_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  let mut parsed_tokens = Vec::new();
  let lhs = parse_try!(parse_primary_expr, tokens, parsed_tokens);
  let expr = parse_try!(parse_binary_expr, tokens, parsed_tokens, 0, &lhs);
  Done(expr, parsed_tokens)
}

fn parse_primary_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  match tokens.last() {
    Some(&Token::Plus) => parse_plus_expr(tokens),
    Some(&Token::Minus) => parse_minus_expr(tokens),
    Some(&Token::Ident(_)) => parse_ident_expr(tokens),
    Some(&Token::Float(_)) => parse_float_expr(tokens, false),
    Some(&Token::Integer(_)) => parse_integer_expr(tokens, false),
    Some(&Token::LiteralStr(_)) => parse_literal_str_expr(tokens),
    Some(&Token::LeftParen) => parse_paren_expr(tokens),
    None => NotComplete,
    _ => error("unknown token when expecting an expression")
  }
}

fn parse_plus_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  tokens.pop();
  parse_primary_expr(tokens)
}

fn parse_minus_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  tokens.pop();
  match tokens.last() {
    Some(&Token::Float(_)) => parse_float_expr(tokens, true),
    Some(&Token::Integer(_)) => parse_integer_expr(tokens, true),
    _ => error("unknown token when expecting an expression")
  }
}

fn parse_ident_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  Error("Not supported yet".to_string())
}

fn parse_float_expr(tokens: &mut Vec<Token>, negative: bool) -> PartParsingResult<Expr> {
  let mut parsed_tokens = Vec::new();

  let mut value = expect_tokens!(
    [Token::Float(val), Token::Float(val), val] <= tokens, parsed_tokens, "float expected"
  );

  if negative {
    value *= -1.;
  }

  Done(Const(Literal::F64(value)), parsed_tokens)
}

fn parse_integer_expr(tokens: &mut Vec<Token>, negative: bool) -> PartParsingResult<Expr> {
  let mut parsed_tokens = Vec::new();

  let mut value = expect_tokens!(
    [Token::Integer(val), Token::Integer(val), val] <= tokens, parsed_tokens, "integer expected"
  );

  if negative {
    value *= -1;
  }

  Done(Const(Literal::I64(value)), parsed_tokens)
}

fn parse_literal_str_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  Error("Not supported yet".to_string())
}

fn parse_paren_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  tokens.pop(); // consume a left paren
  let mut parsed_tokens = vec![Token::LeftParen];

  let expr = parse_try!(parse_expr, tokens, parsed_tokens);

  let paren = expect_tokens!(
    [Token::RightParen, Token::RightParen, ()] <= tokens, parsed_tokens, "')' expected"
  );

  Done(expr, parsed_tokens)
}

fn parse_binary_expr(tokens: &mut Vec<Token>, op_preced: usize, lhs: &Expr) -> PartParsingResult<Expr> {
  let mut result = lhs.clone();
  let mut parsed_tokens = Vec::new();

  loop {
    let (op, preced) = match tokens.last() {
      Some(token) => match AssocOp::from_token(token) {
        Some(assoc_op) => {
          if assoc_op.precedence() >= op_preced {
            (token.clone(), assoc_op.precedence())
          } else {
            break
          }
        },
        None => break, // TODO: check
      },
      _ => break
    };

    tokens.pop();
    parsed_tokens.push(op.clone());

    let mut rhs = parse_try!(parse_primary_expr, tokens, parsed_tokens);

    loop {
      let binary_rhs = match tokens.last().map(|next_op| next_op.clone()) {
        Some(token) => match AssocOp::from_token(&token) {
          Some(assoc_op) => {
            if assoc_op.precedence() > preced {
              parse_try!(parse_binary_expr, tokens, parsed_tokens, assoc_op.precedence(), &rhs)
            } else {
              break
            }
          },
          None => break, // TODO: check
        },
        _ => break
      };

      rhs = binary_rhs;
    }

    result = match AssocOp::to_expr(&op, &result, &rhs) {
      Some(expr) => expr,
      None => panic!(format!("cannot convert to expr: {:?}", op))
    };
  }

  Done(result, parsed_tokens)
}
<commit_msg>Support literal string<commit_after>use lexer::Token;

use common::types::Ty;
use plan::expr;
use plan::expr::*;

use self::PartParsingResult::{Done, NotComplete, Error};

/// Associative operator with precedence.
///
/// This is the enum which specifies operator precedence and fixity to the parser.
#[derive(Debug, PartialEq, Eq)]
pub enum AssocOp {
    /// `+`
    Add,
    /// `-`
    Subtract,
    /// `*`
    Multiply,
    /// `/`
    Divide,
    /// `%`
    Modulus,
    /// `&`
    And,
    /// `|`
    Or,
    /// `==`
    Equal,
    /// `<`
    Less,
    /// `<=`
    LessEqual,
    /// `!=`
    NotEqual,
    /// `>`
    Greater,
    /// `>=`
    GreaterEqual,
    /// `=`
    Assign,
}

impl AssocOp {
  fn from_token(t: &Token) -> Option<AssocOp> {
    match *t {
      Token::Multiply => Some(AssocOp::Multiply),
      Token::Divide => Some(AssocOp::Divide),
      Token::Modulus => Some(AssocOp::Modulus),
      Token::Plus => Some(AssocOp::Add),
      Token::Minus => Some(AssocOp::Subtract),
      Token::Lt => Some(AssocOp::Less),
      Token::Le => Some(AssocOp::LessEqual),
      Token::Ge => Some(AssocOp::GreaterEqual),
      Token::Gt => Some(AssocOp::Greater),
      Token::Eq => Some(AssocOp::Equal),
      Token::Ne => Some(AssocOp::NotEqual),
      Token::And => Some(AssocOp::And),
      Token::Or => Some(AssocOp::Or),
      _ => None
    }
  }

  fn to_expr(t: &Token, lhs: &Expr, rhs: &Expr) -> Option<Expr> {
    match *t {
      Token::Multiply => Some(expr::Mul(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Divide => Some(expr::Div(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Modulus => Some(expr::Modulus(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Plus => Some(expr::Add(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Minus => Some(expr::Sub(ArithmOp::out_type(lhs.ty(), rhs.ty()), lhs.clone(), rhs.clone())),
      Token::Lt => Some(expr::Cmp(CmpOp::Lt, lhs.clone(), rhs.clone())),
      Token::Le => Some(expr::Cmp(CmpOp::Le, lhs.clone(), rhs.clone())),
      Token::Ge => Some(expr::Cmp(CmpOp::Ge, lhs.clone(), rhs.clone())),
      Token::Gt => Some(expr::Cmp(CmpOp::Gt, lhs.clone(), rhs.clone())),
      Token::Eq => Some(expr::Cmp(CmpOp::Eq, lhs.clone(), rhs.clone())),
      Token::Ne => Some(expr::Cmp(CmpOp::Ne, lhs.clone(), rhs.clone())),
      Token::And => Some(expr::And(lhs.clone(), rhs.clone())),
      Token::Or => Some(expr::Or(lhs.clone(), rhs.clone())),
      _ => None
    }
  }

  fn precedence(&self) -> usize {
    use self::AssocOp::*;
      match *self {
        Multiply | Divide | Modulus => 13,
        Add | Subtract => 12,
        And => 10,
        Or => 8,
        Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual => 7,
        _ => 0,
    }
  }
}

enum PartParsingResult<T> {
  Done(T, Vec<Token>),
  NotComplete,
  Error(String)
}

pub type ParsingResult = Result<(Vec<Expr>, Vec<Token>), String>;

macro_rules! parse_try(
  ($function: ident, $tokens: ident, $parsed_tokens: ident) => (
    parse_try!($function, $tokens, $parsed_tokens, )
  );

  ($function: ident, $tokens: ident, $parsed_tokens: ident, $($arg: expr),*) => (
    match $function($tokens, $($arg),*) {
      Done(ast, toks) => {
        $parsed_tokens.extend(toks.into_iter());
        ast
      },
      NotComplete => {
        $parsed_tokens.reverse();
        $tokens.extend($parsed_tokens.into_iter());
        return NotComplete
      },
      Error(message) => return Error(message)
    }
  )
);

macro_rules! expect_tokens(
  ([ $($token: pat, $value: expr, $result: stmt);+ ] <= $tokens: ident, $parsed_tokens: ident, $error: expr) => (
    match $tokens.pop() {
      $(
        Some($token) => {
          $parsed_tokens.push($value);
          $result
        },
      )+
      None => {
        $parsed_tokens.reverse();
        $tokens.extend($parsed_tokens.into_iter());
        return NotComplete
      },
      _ => return error($error)
    }
  );

  ([ $($token:pat, $value:expr, $result: stmt);+ ] else $not_matched: block <= $tokens: ident, $parsed_tokens: ident) => (
    match $tokens.last().map(|t| t.clone()) {
      $(
        Some($token) => {
          $tokens.pop();
          $parsed_tokens.push($value);
          $result
        },
      )+
      _ => {$not_matched}
    }
  );
);

fn error<T>(message: &str) -> PartParsingResult<T> {
  Error(message.to_string())
}

pub fn parse(tokens: &[Token], parsed_trees: &[Expr]) -> ParsingResult {
  let mut rest = tokens.to_vec();
  rest.reverse(); // to use like a stack

  let mut asts = parsed_trees.to_vec();

  loop {
    let cur_token = match rest.last() {
      Some(t) => t.clone(),
      None => break
    };

    let result = match cur_token {
      // Fn => parse_function(&mut rest), TODO
      // Extern => parse_extern(&mut rest), TODO
      Token::Delimiter => {
        rest.pop(); continue
      },
      _ => parse_expr(&mut rest),
    };

    match result {
      Done(ast_node, _) => asts.push(ast_node),
      NotComplete => break,
      Error(message) => return Err(message),
    }
  }

  rest.reverse();
  Ok((asts, rest))
}

fn parse_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  let mut parsed_tokens = Vec::new();
  let lhs = parse_try!(parse_primary_expr, tokens, parsed_tokens);
  let expr = parse_try!(parse_binary_expr, tokens, parsed_tokens, 0, &lhs);
  Done(expr, parsed_tokens)
}

fn parse_primary_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  match tokens.last() {
    Some(&Token::Plus) => parse_plus_expr(tokens),
    Some(&Token::Minus) => parse_minus_expr(tokens),
    Some(&Token::Ident(_)) => parse_ident_expr(tokens),
    Some(&Token::Float(_)) => parse_float_expr(tokens, false),
    Some(&Token::Integer(_)) => parse_integer_expr(tokens, false),
    Some(&Token::LiteralStr(_)) => parse_literal_str_expr(tokens),
    Some(&Token::LeftParen) => parse_paren_expr(tokens),
    None => NotComplete,
    _ => error("unknown token when expecting an expression")
  }
}

fn parse_plus_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  tokens.pop();
  parse_primary_expr(tokens)
}

fn parse_minus_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  tokens.pop();
  match tokens.last() {
    Some(&Token::Float(_)) => parse_float_expr(tokens, true),
    Some(&Token::Integer(_)) => parse_integer_expr(tokens, true),
    _ => error("unknown token when expecting an expression")
  }
}

fn parse_ident_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  Error("Not supported yet".to_string())
}

fn parse_float_expr(tokens: &mut Vec<Token>, negative: bool) -> PartParsingResult<Expr> {
  let mut parsed_tokens = Vec::new();

  let mut value = expect_tokens!(
    [Token::Float(val), Token::Float(val), val] <= tokens, parsed_tokens, "float expected"
  );

  if negative {
    value *= -1.;
  }

  Done(Const(Literal::F64(value)), parsed_tokens)
}

fn parse_integer_expr(tokens: &mut Vec<Token>, negative: bool) -> PartParsingResult<Expr> {
  let mut parsed_tokens = Vec::new();

  let mut value = expect_tokens!(
    [Token::Integer(val), Token::Integer(val), val] <= tokens, parsed_tokens, "integer expected"
  );

  if negative {
    value *= -1;
  }

  Done(Const(Literal::I64(value)), parsed_tokens)
}

fn parse_literal_str_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  let mut parsed_tokens = Vec::new();

  let mut value = expect_tokens!(
    [Token::LiteralStr(val), Token::LiteralStr(val.clone()), val] <= tokens, parsed_tokens, "string expected"
  );

  Done(Const(Literal::String(value.to_string())), parsed_tokens)
}

fn parse_paren_expr(tokens: &mut Vec<Token>) -> PartParsingResult<Expr> {
  tokens.pop(); // consume a left paren
  let mut parsed_tokens = vec![Token::LeftParen];

  let expr = parse_try!(parse_expr, tokens, parsed_tokens);

  let paren = expect_tokens!(
    [Token::RightParen, Token::RightParen, ()] <= tokens, parsed_tokens, "')' expected"
  );

  Done(expr, parsed_tokens)
}

fn parse_binary_expr(tokens: &mut Vec<Token>, op_preced: usize, lhs: &Expr) -> PartParsingResult<Expr> {
  let mut result = lhs.clone();
  let mut parsed_tokens = Vec::new();

  loop {
    let (op, preced) = match tokens.last() {
      Some(token) => match AssocOp::from_token(token) {
        Some(assoc_op) => {
          if assoc_op.precedence() >= op_preced {
            (token.clone(), assoc_op.precedence())
          } else {
            break
          }
        },
        None => break, // TODO: check
      },
      _ => break
    };

    tokens.pop();
    parsed_tokens.push(op.clone());

    let mut rhs = parse_try!(parse_primary_expr, tokens, parsed_tokens);

    loop {
      let binary_rhs = match tokens.last().map(|next_op| next_op.clone()) {
        Some(token) => match AssocOp::from_token(&token) {
          Some(assoc_op) => {
            if assoc_op.precedence() > preced {
              parse_try!(parse_binary_expr, tokens, parsed_tokens, assoc_op.precedence(), &rhs)
            } else {
              break
            }
          },
          None => break, // TODO: check
        },
        _ => break
      };

      rhs = binary_rhs;
    }

    result = match AssocOp::to_expr(&op, &result, &rhs) {
      Some(expr) => expr,
      None => panic!(format!("cannot convert to expr: {:?}", op))
    };
  }

  Done(result, parsed_tokens)
}
<|endoftext|>


<commit_before>use super::{value::Value, Vm};
use crate::{builtin_functions::BuiltinFunction, compiler::lir::Instruction, input::Input};
use itertools::Itertools;
use log::{debug, trace};

impl Vm {
    pub(super) fn run_builtin_function(&mut self, builtin_function: BuiltinFunction) {
        trace!("run_builtin_function: builtin{:?}", builtin_function);

        let return_value = match builtin_function {
            BuiltinFunction::Add => self.add(),
            BuiltinFunction::Equals => Ok(self.equals()),
            BuiltinFunction::GetArgumentCount => self.get_argument_count(),
            BuiltinFunction::IfElse => self.if_else(),
            BuiltinFunction::Panic => self.panic_builtin(),
            BuiltinFunction::Print => self.print(),
            BuiltinFunction::StructGet => self.struct_get(),
            BuiltinFunction::StructGetKeys => self.struct_get_keys(),
            BuiltinFunction::StructHasKey => self.struct_has_key(),
            BuiltinFunction::TypeOf => Ok(self.type_of()),
            BuiltinFunction::Use => self.use_(),
            _ => panic!("Unhandled builtin function: {:?}", builtin_function),
        };
        let return_value = match return_value {
            Ok(value) => value,
            Err(panic_message) => self.panic(panic_message),
        };

        let return_object = self.heap.import(return_value);
        self.data_stack.push(return_object);
    }

    fn add(&mut self) -> Result<Value, String> {
        let b = self
            .pop_value()
            .unwrap()
            .try_into_int()
            .map_err(|it| format!("builtinAdd expects numbers as arguments, got {}.", it))?;
        let a = self
            .pop_value()
            .unwrap()
            .try_into_int()
            .map_err(|it| format!("builtinAdd expects numbers as arguments, got {}.", it))?;

        Ok((a + b).into())
    }

    fn equals(&mut self) -> Value {
        let b = self.pop_value().unwrap();
        let a = self.pop_value().unwrap();

        (a == b).into()
    }

    fn get_argument_count(&mut self) -> Result<Value, String> {
        let (_, chunk) = self
            .pop_value()
            .unwrap()
            .try_into_closure()
            .map_err(|it| format!("builtinAddArgumentCount expects a closure, got {}.", it))?;

        let num_args = self.chunks[chunk].num_args;
        Ok(Value::Int(num_args as u64))
    }

    fn if_else(&mut self) -> Result<Value, String> {
        let else_ = self.pop_value().unwrap();
        let then = self.pop_value().unwrap();
        let condition = self.pop_value().unwrap().try_into_symbol().map_err(|it| {
            format!(
                "builtinIfElse expects a boolean symbol as the condition, got {}.",
                it
            )
        })?;
        match then.clone() {
            Value::Closure { body, .. } => {
                if self.chunks[body].num_args > 0 {
                    return Err(format!("builtinIfElse expects a closure without arguments as the then, got one with {} arguments.", self.chunks[body].num_args));
                }
            }
            _ => {
                return Err(format!(
                    "builtinIfElse expects a closure as the then, got {}.",
                    then
                ))
            }
        };
        match else_.clone() {
            Value::Closure { body, .. } => {
                if self.chunks[body].num_args > 0 {
                    return Err(format!("builtinIfElse expects a closure without arguments as the else, got one with {} arguments.", self.chunks[body].num_args));
                }
            }
            _ => {
                return Err(format!(
                    "builtinIfElse expects a closure as the else, got {}.",
                    else_
                ))
            }
        };
        let condition = match condition.as_str() {
            "True" => true,
            "False" => false,
            _ => {
                return Err(format!(
                    "builtinIfElse expected True or False as a condition, but got {}",
                    condition
                ));
            }
        };

        let closure_object = self.heap.import(if condition { then } else { else_ });
        self.data_stack.push(closure_object);
        self.run_instruction(Instruction::Call);
        Ok(self.pop_value().unwrap())
    }

    fn panic_builtin(&mut self) -> Result<Value, String> {
        let message = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinPanic expects a text as the message, got {}.", it))?;

        Ok(self.panic(message))
    }

    fn print(&mut self) -> Result<Value, String> {
        let message = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinPrint expects a text as the message, got {}.", it))?;

        println!("{:?}", message);
        Ok(Value::nothing())
    }

    fn struct_get(&mut self) -> Result<Value, String> {
        let key = self.pop_value().unwrap();
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructGet expects struct, got {}.", it))?;

        match struct_.get(&key) {
            Some(value) => Ok(value.clone().into()),
            None => Err(format!("Struct does not contain key {:?}.", key)),
        }
    }

    fn struct_get_keys(&mut self) -> Result<Value, String> {
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructGetKeys expects struct, got {}.", it))?;

        Ok(Value::list(struct_.keys().cloned().collect()))
    }

    fn struct_has_key(&mut self) -> Result<Value, String> {
        let key = self.pop_value().unwrap();
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructHasKey expects struct, got {}.", it))?;

        Ok((struct_.contains_key(&key)).into())
    }

    fn type_of(&mut self) -> Value {
        let value = self.pop_value().unwrap();
        match value {
            Value::Int(_) => Value::Symbol("Int".to_owned()).into(),
            Value::Text(_) => Value::Symbol("Text".to_owned()).into(),
            Value::Symbol(_) => Value::Symbol("Symbol".to_owned()).into(),
            Value::Struct(_) => Value::Symbol("Struct".to_owned()).into(),
            Value::Closure { .. } => Value::Symbol("Function".to_owned()).into(),
        }
    }

    fn use_(&mut self) -> Result<Value, String> {
        let target = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinUse expects text as target, got {}.", it))?;
        let current_path_struct = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinUse expects struct as current path, got {}.", it))?;

        let mut current_path = vec![];
        let mut index = 0;
        while let Some(component) = current_path_struct.get(&Value::Int(index)) {
            current_path.push(component.clone().try_into_text().map_err(|it| format!("builtinUse expects a struct as current path with only textual paths as values, but the map contains the value {} for key {}", it, index)));
            index += 1;
        }

        let target = UseTarget::parse(&target)?;

        if target.parent_navigations > current_path.len() {
            return Err("Too many parent navigations.".to_string());
        }

        // let inputs = target.resolve(&current_path[..]);
        // let input = match inputs
        //     .iter()
        //     .filter(|&it| db.get_input(it.to_owned()).is_some())
        //     .next()
        // {
        //     Some(target) => target,
        //     None => {
        //         return self.panic(format!(
        //             "Target doesn't exist. Checked the following path(s): {}",
        //             inputs.iter().map(|it| format!("{}", it)).join(", ")
        //         ));
        //     }
        // };

        Ok(Value::Symbol("Used".to_string()))

        // TODO: Continue implementing use.
        // let (lir, _) = db.lir(input.clone()).unwrap();
        // TODO: Run LIR.
        // let discover_result = db.run_all(input.to_owned(), import_chain.to_owned());

        // TODO: Put public identifiers into map.
        // hir.identifiers
        //     .iter()
        //     .map(|(id, key)| {
        //         let mut key = key.to_owned();
        //         key.get_mut(0..1).unwrap().make_ascii_uppercase();
        //         let key = Value::Symbol(key.to_owned());

        //         let value = match discover_result.get(id) {
        //             Some(value) => value.to_owned()?,
        //             None => return DiscoverResult::ErrorInHir,
        //         };

        //         DiscoverResult::Value((key, value))
        //     })
        //     .collect::<DiscoverResult<HashMap<Value, Value>>>()
        //     .map(|it| Value::Struct(it))
    }
}

struct UseTarget {
    parent_navigations: usize,
    path: Vec<String>,
}
impl UseTarget {
    const PARENT_NAVIGATION_CHAR: char = '.';

    fn parse(target: &str) -> Result<Self, String> {
        let mut parent_navigations = 0;
        let mut target = target;
        while target.chars().next() == Some(UseTarget::PARENT_NAVIGATION_CHAR) {
            parent_navigations += 1;
            target = &target[UseTarget::PARENT_NAVIGATION_CHAR.len_utf8()..];
        }

        let mut path = vec![];
        loop {
            let mut chars = vec![];
            while let Some(c) = target.chars().next() {
                if c == UseTarget::PARENT_NAVIGATION_CHAR {
                    break;
                }
                chars.push(c);
                target = &target[c.len_utf8()..];
            }

            if target.is_empty() {
                path.push(chars.into_iter().join(""));
                break;
            }

            if chars.is_empty() {
                return Err("Target contains consecutive dots (`.`) in the path.".to_owned());
            }

            path.push(chars.into_iter().join(""));
        }
        Ok(UseTarget {
            parent_navigations,
            path,
        })
    }

    fn resolve(&self, current_path: &[String]) -> Vec<Input> {
        let mut path = current_path.to_owned();
        if self.parent_navigations == 0 {
            assert!(!path.is_empty());
            let last = path.last_mut().unwrap();
            if last == ".candy" {
                path.pop();
            } else {
                *last = last
                    .strip_suffix(".candy")
                    .expect("File name must end with `.candy`.")
                    .to_owned();
            }
        } else {
            for _ in 0..self.parent_navigations {
                if path.is_empty() {
                    return vec![];
                }
                path.pop();
            }
        }

        for part in &self.path {
            path.push(part.to_owned());
        }

        let mut result = vec![];

        let mut subdirectory = path.clone();
        subdirectory.push(".candy".to_owned());
        result.push(Input::File(subdirectory));

        if path.len() >= 1 {
            let last = path.last_mut().unwrap();
            *last = format!("{}.candy", last);
            result.push(Input::File(path));
        }
        result
    }
}

impl Vm {
    fn pop_value(&mut self) -> Option<Value> {
        let address = self.data_stack.pop()?;
        Some(self.heap.export(address))
    }
}
<commit_msg>Special-case builtinIfElse for control flow<commit_after>use super::{value::Value, Vm};
use crate::{builtin_functions::BuiltinFunction, compiler::lir::Instruction, input::Input};
use itertools::Itertools;
use log::{debug, trace};

impl Vm {
    pub(super) fn run_builtin_function(&mut self, builtin_function: BuiltinFunction) {
        trace!("run_builtin_function: builtin{:?}", builtin_function);

        let return_value_or_panic_message = match builtin_function {
            BuiltinFunction::Add => self.add(),
            BuiltinFunction::Equals => Ok(self.equals()),
            BuiltinFunction::GetArgumentCount => self.get_argument_count(),
            BuiltinFunction::IfElse => match self.if_else() {
                // If successful, builtinIfElse doesn't return a value, but
                // diverges the control flow.
                Ok(()) => return,
                Err(message) => Err(message),
            },
            BuiltinFunction::Panic => self.panic_builtin(),
            BuiltinFunction::Print => self.print(),
            BuiltinFunction::StructGet => self.struct_get(),
            BuiltinFunction::StructGetKeys => self.struct_get_keys(),
            BuiltinFunction::StructHasKey => self.struct_has_key(),
            BuiltinFunction::TypeOf => Ok(self.type_of()),
            BuiltinFunction::Use => self.use_(),
        };
        let return_value = match return_value_or_panic_message {
            Ok(value) => value,
            Err(panic_message) => self.panic(panic_message),
        };

        let return_object = self.heap.import(return_value);
        self.data_stack.push(return_object);
    }

    fn add(&mut self) -> Result<Value, String> {
        let b = self
            .pop_value()
            .unwrap()
            .try_into_int()
            .map_err(|it| format!("builtinAdd expects numbers as arguments, got {}.", it))?;
        let a = self
            .pop_value()
            .unwrap()
            .try_into_int()
            .map_err(|it| format!("builtinAdd expects numbers as arguments, got {}.", it))?;

        Ok((a + b).into())
    }

    fn equals(&mut self) -> Value {
        let b = self.pop_value().unwrap();
        let a = self.pop_value().unwrap();

        (a == b).into()
    }

    fn get_argument_count(&mut self) -> Result<Value, String> {
        let (_, chunk) = self
            .pop_value()
            .unwrap()
            .try_into_closure()
            .map_err(|it| format!("builtinAddArgumentCount expects a closure, got {}.", it))?;

        let num_args = self.chunks[chunk].num_args;
        Ok(Value::Int(num_args as u64))
    }

    fn if_else(&mut self) -> Result<(), String> {
        let else_ = self.pop_value().unwrap();
        let then = self.pop_value().unwrap();
        let condition = self.pop_value().unwrap().try_into_symbol().map_err(|it| {
            format!(
                "builtinIfElse expects a boolean symbol as the condition, got {}.",
                it
            )
        })?;
        match then.clone() {
            Value::Closure { body, .. } => {
                if self.chunks[body].num_args > 0 {
                    return Err(format!("builtinIfElse expects a closure without arguments as the then, got one with {} arguments.", self.chunks[body].num_args));
                }
            }
            _ => {
                return Err(format!(
                    "builtinIfElse expects a closure as the then, got {}.",
                    then
                ))
            }
        };
        match else_.clone() {
            Value::Closure { body, .. } => {
                if self.chunks[body].num_args > 0 {
                    return Err(format!("builtinIfElse expects a closure without arguments as the else, got one with {} arguments.", self.chunks[body].num_args));
                }
            }
            _ => {
                return Err(format!(
                    "builtinIfElse expects a closure as the else, got {}.",
                    else_
                ))
            }
        };
        let condition = match condition.as_str() {
            "True" => true,
            "False" => false,
            _ => {
                return Err(format!(
                    "builtinIfElse expected True or False as a condition, but got {}",
                    condition
                ));
            }
        };

        let closure_object = self.heap.import(if condition { then } else { else_ });
        debug!(
            "IfElse executing the closure: {:?}",
            self.heap.export_without_dropping(closure_object)
        );
        self.data_stack.push(closure_object);
        self.run_instruction(Instruction::Call);
        Ok(())
    }

    fn panic_builtin(&mut self) -> Result<Value, String> {
        let message = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinPanic expects a text as the message, got {}.", it))?;

        Ok(self.panic(message))
    }

    fn print(&mut self) -> Result<Value, String> {
        let message = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinPrint expects a text as the message, got {}.", it))?;

        println!("{:?}", message);
        Ok(Value::nothing())
    }

    fn struct_get(&mut self) -> Result<Value, String> {
        let key = self.pop_value().unwrap();
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructGet expects struct, got {}.", it))?;

        match struct_.get(&key) {
            Some(value) => Ok(value.clone().into()),
            None => Err(format!("Struct does not contain key {:?}.", key)),
        }
    }

    fn struct_get_keys(&mut self) -> Result<Value, String> {
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructGetKeys expects struct, got {}.", it))?;

        Ok(Value::list(struct_.keys().cloned().collect()))
    }

    fn struct_has_key(&mut self) -> Result<Value, String> {
        let key = self.pop_value().unwrap();
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructHasKey expects struct, got {}.", it))?;

        Ok((struct_.contains_key(&key)).into())
    }

    fn type_of(&mut self) -> Value {
        let value = self.pop_value().unwrap();
        match value {
            Value::Int(_) => Value::Symbol("Int".to_owned()).into(),
            Value::Text(_) => Value::Symbol("Text".to_owned()).into(),
            Value::Symbol(_) => Value::Symbol("Symbol".to_owned()).into(),
            Value::Struct(_) => Value::Symbol("Struct".to_owned()).into(),
            Value::Closure { .. } => Value::Symbol("Function".to_owned()).into(),
        }
    }

    fn use_(&mut self) -> Result<Value, String> {
        let target = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinUse expects text as target, got {}.", it))?;
        let current_path_struct = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinUse expects struct as current path, got {}.", it))?;

        let mut current_path = vec![];
        let mut index = 0;
        while let Some(component) = current_path_struct.get(&Value::Int(index)) {
            current_path.push(component.clone().try_into_text().map_err(|it| format!("builtinUse expects a struct as current path with only textual paths as values, but the map contains the value {} for key {}", it, index)));
            index += 1;
        }

        let target = UseTarget::parse(&target)?;

        if target.parent_navigations > current_path.len() {
            return Err("Too many parent navigations.".to_string());
        }

        // let inputs = target.resolve(&current_path[..]);
        // let input = match inputs
        //     .iter()
        //     .filter(|&it| db.get_input(it.to_owned()).is_some())
        //     .next()
        // {
        //     Some(target) => target,
        //     None => {
        //         return self.panic(format!(
        //             "Target doesn't exist. Checked the following path(s): {}",
        //             inputs.iter().map(|it| format!("{}", it)).join(", ")
        //         ));
        //     }
        // };

        Ok(Value::Symbol("Used".to_string()))

        // TODO: Continue implementing use.
        // let (lir, _) = db.lir(input.clone()).unwrap();
        // TODO: Run LIR.
        // let discover_result = db.run_all(input.to_owned(), import_chain.to_owned());

        // TODO: Put public identifiers into map.
        // hir.identifiers
        //     .iter()
        //     .map(|(id, key)| {
        //         let mut key = key.to_owned();
        //         key.get_mut(0..1).unwrap().make_ascii_uppercase();
        //         let key = Value::Symbol(key.to_owned());

        //         let value = match discover_result.get(id) {
        //             Some(value) => value.to_owned()?,
        //             None => return DiscoverResult::ErrorInHir,
        //         };

        //         DiscoverResult::Value((key, value))
        //     })
        //     .collect::<DiscoverResult<HashMap<Value, Value>>>()
        //     .map(|it| Value::Struct(it))
    }
}

struct UseTarget {
    parent_navigations: usize,
    path: Vec<String>,
}
impl UseTarget {
    const PARENT_NAVIGATION_CHAR: char = '.';

    fn parse(target: &str) -> Result<Self, String> {
        let mut parent_navigations = 0;
        let mut target = target;
        while target.chars().next() == Some(UseTarget::PARENT_NAVIGATION_CHAR) {
            parent_navigations += 1;
            target = &target[UseTarget::PARENT_NAVIGATION_CHAR.len_utf8()..];
        }

        let mut path = vec![];
        loop {
            let mut chars = vec![];
            while let Some(c) = target.chars().next() {
                if c == UseTarget::PARENT_NAVIGATION_CHAR {
                    break;
                }
                chars.push(c);
                target = &target[c.len_utf8()..];
            }

            if target.is_empty() {
                path.push(chars.into_iter().join(""));
                break;
            }

            if chars.is_empty() {
                return Err("Target contains consecutive dots (`.`) in the path.".to_owned());
            }

            path.push(chars.into_iter().join(""));
        }
        Ok(UseTarget {
            parent_navigations,
            path,
        })
    }

    fn resolve(&self, current_path: &[String]) -> Vec<Input> {
        let mut path = current_path.to_owned();
        if self.parent_navigations == 0 {
            assert!(!path.is_empty());
            let last = path.last_mut().unwrap();
            if last == ".candy" {
                path.pop();
            } else {
                *last = last
                    .strip_suffix(".candy")
                    .expect("File name must end with `.candy`.")
                    .to_owned();
            }
        } else {
            for _ in 0..self.parent_navigations {
                if path.is_empty() {
                    return vec![];
                }
                path.pop();
            }
        }

        for part in &self.path {
            path.push(part.to_owned());
        }

        let mut result = vec![];

        let mut subdirectory = path.clone();
        subdirectory.push(".candy".to_owned());
        result.push(Input::File(subdirectory));

        if path.len() >= 1 {
            let last = path.last_mut().unwrap();
            *last = format!("{}.candy", last);
            result.push(Input::File(path));
        }
        result
    }
}

impl Vm {
    fn pop_value(&mut self) -> Option<Value> {
        let address = self.data_stack.pop()?;
        Some(self.heap.export(address))
    }
}
<|endoftext|>


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

use std::env;
use std::ffi::OsString;

use common::ui::UI;
use hcore::crypto::CACHE_KEY_PATH_ENV_VAR;
use hcore::env as henv;
use hcore::fs::CACHE_KEY_PATH;
use hcore::os::users;

use config;
use error::Result;

pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
    try!(inner::rerun_with_sudo_if_needed(ui));

    // If the `$HAB_ORIGIN` environment variable is not present, then see if a default is set in
    // the CLI config. If so, set it as the `$HAB_ORIGIN` environment variable for the `hab-studio`
    // or `docker` execv call.
    if henv::var("HAB_ORIGIN").is_err() {
        let config = try!(config::load_with_sudo_user());
        if let Some(default_origin) = config.origin {
            debug!("Setting default origin {} via CLI config", &default_origin);
            env::set_var("HAB_ORIGIN", default_origin);
        }
    }

    // If the `$HAB_CACHE_KEY_PATH` environment variable is not present, check if we are running
    // under a `sudo` invocation. If so, determine the non-root user that issued the command in
    // order to set their key cache location in the environment variable. This is done so that the
    // `hab-studio` command will find the correct key cache or so that the correct directory will
    // be volume mounted when used with Docker.
    if henv::var(CACHE_KEY_PATH_ENV_VAR).is_err() {
        if let Some(sudo_user) = henv::sudo_user() {
            if let Some(home) = users::get_home_for_user(&sudo_user) {
                let cache_key_path = home.join(format!(".{}", CACHE_KEY_PATH));
                debug!("Setting cache_key_path for SUDO_USER={} to: {}",
                       &sudo_user,
                       cache_key_path.display());
                env::set_var(CACHE_KEY_PATH_ENV_VAR, cache_key_path);
                // Prevent any inner `hab` invocations from triggering similar logic: we will be
                // operating in the context `hab-studio` which is running with rootlike privileges.
                env::remove_var("SUDO_USER");
            }
        }
    }

    inner::start(ui, args)
}

#[cfg(target_os = "linux")]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::path::PathBuf;
    use std::str::FromStr;

    use common::ui::UI;
    use hcore::crypto::{init, default_cache_key_path};
    use hcore::env as henv;
    use hcore::fs::{am_i_root, find_command};
    use hcore::os::process;
    use hcore::package::PackageIdent;

    use error::{Error, Result};
    use exec;

    const STUDIO_CMD: &'static str = "hab-studio";
    const STUDIO_CMD_ENVVAR: &'static str = "HAB_STUDIO_BINARY";
    const STUDIO_PACKAGE_IDENT: &'static str = "core/hab-studio";
    const SUDO_CMD: &'static str = "sudo";

    pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let command = match henv::var(STUDIO_CMD_ENVVAR) {
            Ok(command) => PathBuf::from(command),
            Err(_) => {
                init();
                let ident = try!(PackageIdent::from_str(STUDIO_PACKAGE_IDENT));
                try!(exec::command_from_pkg(ui,
                                            STUDIO_CMD,
                                            &ident,
                                            &default_cache_key_path(None),
                                            0))
            }
        };

        if let Some(cmd) = find_command(command.to_string_lossy().as_ref()) {
            try!(process::become_command(cmd, args));
        } else {
            return Err(Error::ExecCommandNotFound(command.to_string_lossy().into_owned()));
        }
        Ok(())
    }

    pub fn rerun_with_sudo_if_needed(ui: &mut UI) -> Result<()> {
        // If I have root permissions, early return, we are done.
        if am_i_root() {
            return Ok(());
        }

        // Otherwise we will try to re-run this program using `sudo`
        match find_command(SUDO_CMD) {
            Some(sudo_prog) => {
                let mut args: Vec<OsString> =
                    vec!["-p".into(), "[sudo hab-studio] password for %u: ".into(), "-E".into()];
                args.append(&mut env::args_os().collect());
                Ok(try!(process::become_command(sudo_prog, args)))
            }
            None => {
                try!(ui.warn(format!("Could not find the `{}' command, is it in your PATH?",
                                     SUDO_CMD)));
                try!(ui.warn("Running Habitat Studio requires root or administrator privileges. \
                              Please retry this command as a super user or use a \
                              privilege-granting facility such as sudo."));
                try!(ui.br());
                Err(Error::RootRequired)
            }
        }
    }
}

#[cfg(not(target_os = "linux"))]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::process::{Command, Stdio};

    use common::ui::UI;
    use hcore::crypto::default_cache_key_path;
    use hcore::env as henv;
    use hcore::fs::{CACHE_KEY_PATH, find_command};
    use hcore::os::process;

    use error::{Error, Result};
    use VERSION;

    const DOCKER_CMD: &'static str = "docker";
    const DOCKER_CMD_ENVVAR: &'static str = "HAB_DOCKER_BINARY";
    const DOCKER_IMAGE: &'static str = "habitat-docker-registry.bintray.io/studio";
    const DOCKER_IMAGE_ENVVAR: &'static str = "HAB_DOCKER_STUDIO_IMAGE";
    const DOCKER_OPTS: &'static str = "HAB_DOCKER_OPTS";

    pub fn start(_ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let docker = henv::var(DOCKER_CMD_ENVVAR).unwrap_or(DOCKER_CMD.to_string());

        let cmd = match find_command(&docker) {
            Some(cmd) => cmd,
            None => return Err(Error::ExecCommandNotFound(docker.to_string())),
        };

        let child = Command::new(&cmd)
            .arg("images")
            .arg(&image_identifier())
            .arg("-q")
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .expect("docker failed to start");

        let output = child.wait_with_output().expect("failed to wait on child");
        let stdout = String::from_utf8(output.stdout).unwrap();
        if stdout.is_empty() {
            debug!("Failed to studio image locally.");

            let child = Command::new(&cmd)
                .arg("pull")
                .arg(&image_identifier())
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .spawn()
                .expect("docker failed to start");

            let output = child.wait_with_output()
                .expect("failed to wait on child");

            if output.status.success() {
                debug!("Docker image is reachable. Proceeding with launching docker.");
            } else {
                debug!("Docker image is unreachable. Exit code = {:?}",
                       output.status);

                let err_output = String::from_utf8(output.stderr).unwrap();

                if err_output.contains("image") && err_output.contains("not found") {
                    return Err(Error::DockerImageNotFound(image_identifier().to_string()));
                } else if err_output.contains("Cannot connect to the Docker daemon") {
                    return Err(Error::DockerDaemonDown);
                } else {
                    return Err(Error::DockerNetworkDown(image_identifier().to_string()));
                }
            }
        } else {
            debug!("Found studio image locally.");
        }

        let mut cmd_args: Vec<OsString> = vec!["run".into(),
                                               "--rm".into(),
                                               "--tty".into(),
                                               "--interactive".into(),
                                               "--privileged".into()];

        // All the args already placed in `cmd_args` are things that we don't want to insert again.
        // Later args such as `--env` will overwrite any options (potentially) set mistakenly here.
        if let Ok(opts) = henv::var(DOCKER_OPTS) {
            let opts = opts.split(" ")
                .map(|v| v.into())
                // Ensure we're not passing something like `--tty` again here.
                .filter(|v| !cmd_args.contains(v))
                .collect::<Vec<_>>();
            debug!("Docker opts originating from DOCKER_OPTS = {:?}", opts);
            cmd_args.extend_from_slice(opts.as_slice());
        }

        let env_vars = vec!["HAB_DEPOT_URL", "HAB_ORIGIN", "http_proxy", "https_proxy"];
        for var in env_vars {
            if let Ok(val) = henv::var(var) {
                debug!("Propagating environment variable into container: {}={}",
                       var,
                       val);
                cmd_args.push("--env".into());
                cmd_args.push(format!("{}={}", var, val).into());
            }
        }

        cmd_args.push("--volume".into());
        cmd_args.push("/var/run/docker.sock:/var/run/docker.sock".into());
        cmd_args.push("--volume".into());
        cmd_args.push(format!("{}:/{}",
                              default_cache_key_path(None).to_string_lossy(),
                              CACHE_KEY_PATH)
            .into());
        cmd_args.push("--volume".into());
        cmd_args.push(format!("{}:/src", env::current_dir().unwrap().to_string_lossy()).into());

        cmd_args.push(image_identifier().into());
        cmd_args.extend_from_slice(args.as_slice());

        for var in vec!["http_proxy", "https_proxy"] {
            if let Ok(_) = henv::var(var) {
                debug!("Unsetting proxy environment variable '{}' before calling `{}'",
                       var,
                       docker);
                env::remove_var(var);
            }
        }

        debug!("Docker arguments = {:?}", cmd_args);
        Ok(try!(process::become_command(cmd, cmd_args)))
    }

    pub fn rerun_with_sudo_if_needed(_ui: &mut UI) -> Result<()> {
        // No sudo calls necessary here--we are calling `docker` commands instead
        Ok(())
    }

    /// Returns the Docker Studio image with tag for the desired version which corresponds to the
    /// same version (minus release) as this program.
    fn image_identifier() -> String {
        let version: Vec<&str> = VERSION.split("/").collect();
        henv::var(DOCKER_IMAGE_ENVVAR).unwrap_or(format!("{}:{}", DOCKER_IMAGE, version[0]))
    }

    #[cfg(test)]
    mod tests {
        use super::{image_identifier, DOCKER_IMAGE};
        use VERSION;

        #[test]
        fn retrieve_image_identifier() {
            assert_eq!(image_identifier(), format!("{}:{}", DOCKER_IMAGE, VERSION));
        }
    }
}
<commit_msg>output was not being captured before, now it is.<commit_after>// Copyright (c) 2016 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::env;
use std::ffi::OsString;

use common::ui::UI;
use hcore::crypto::CACHE_KEY_PATH_ENV_VAR;
use hcore::env as henv;
use hcore::fs::CACHE_KEY_PATH;
use hcore::os::users;

use config;
use error::Result;

pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
    try!(inner::rerun_with_sudo_if_needed(ui));

    // If the `$HAB_ORIGIN` environment variable is not present, then see if a default is set in
    // the CLI config. If so, set it as the `$HAB_ORIGIN` environment variable for the `hab-studio`
    // or `docker` execv call.
    if henv::var("HAB_ORIGIN").is_err() {
        let config = try!(config::load_with_sudo_user());
        if let Some(default_origin) = config.origin {
            debug!("Setting default origin {} via CLI config", &default_origin);
            env::set_var("HAB_ORIGIN", default_origin);
        }
    }

    // If the `$HAB_CACHE_KEY_PATH` environment variable is not present, check if we are running
    // under a `sudo` invocation. If so, determine the non-root user that issued the command in
    // order to set their key cache location in the environment variable. This is done so that the
    // `hab-studio` command will find the correct key cache or so that the correct directory will
    // be volume mounted when used with Docker.
    if henv::var(CACHE_KEY_PATH_ENV_VAR).is_err() {
        if let Some(sudo_user) = henv::sudo_user() {
            if let Some(home) = users::get_home_for_user(&sudo_user) {
                let cache_key_path = home.join(format!(".{}", CACHE_KEY_PATH));
                debug!("Setting cache_key_path for SUDO_USER={} to: {}",
                       &sudo_user,
                       cache_key_path.display());
                env::set_var(CACHE_KEY_PATH_ENV_VAR, cache_key_path);
                // Prevent any inner `hab` invocations from triggering similar logic: we will be
                // operating in the context `hab-studio` which is running with rootlike privileges.
                env::remove_var("SUDO_USER");
            }
        }
    }

    inner::start(ui, args)
}

#[cfg(target_os = "linux")]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::path::PathBuf;
    use std::str::FromStr;

    use common::ui::UI;
    use hcore::crypto::{init, default_cache_key_path};
    use hcore::env as henv;
    use hcore::fs::{am_i_root, find_command};
    use hcore::os::process;
    use hcore::package::PackageIdent;

    use error::{Error, Result};
    use exec;

    const STUDIO_CMD: &'static str = "hab-studio";
    const STUDIO_CMD_ENVVAR: &'static str = "HAB_STUDIO_BINARY";
    const STUDIO_PACKAGE_IDENT: &'static str = "core/hab-studio";
    const SUDO_CMD: &'static str = "sudo";

    pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let command = match henv::var(STUDIO_CMD_ENVVAR) {
            Ok(command) => PathBuf::from(command),
            Err(_) => {
                init();
                let ident = try!(PackageIdent::from_str(STUDIO_PACKAGE_IDENT));
                try!(exec::command_from_pkg(ui,
                                            STUDIO_CMD,
                                            &ident,
                                            &default_cache_key_path(None),
                                            0))
            }
        };

        if let Some(cmd) = find_command(command.to_string_lossy().as_ref()) {
            try!(process::become_command(cmd, args));
        } else {
            return Err(Error::ExecCommandNotFound(command.to_string_lossy().into_owned()));
        }
        Ok(())
    }

    pub fn rerun_with_sudo_if_needed(ui: &mut UI) -> Result<()> {
        // If I have root permissions, early return, we are done.
        if am_i_root() {
            return Ok(());
        }

        // Otherwise we will try to re-run this program using `sudo`
        match find_command(SUDO_CMD) {
            Some(sudo_prog) => {
                let mut args: Vec<OsString> =
                    vec!["-p".into(), "[sudo hab-studio] password for %u: ".into(), "-E".into()];
                args.append(&mut env::args_os().collect());
                Ok(try!(process::become_command(sudo_prog, args)))
            }
            None => {
                try!(ui.warn(format!("Could not find the `{}' command, is it in your PATH?",
                                     SUDO_CMD)));
                try!(ui.warn("Running Habitat Studio requires root or administrator privileges. \
                              Please retry this command as a super user or use a \
                              privilege-granting facility such as sudo."));
                try!(ui.br());
                Err(Error::RootRequired)
            }
        }
    }
}

#[cfg(not(target_os = "linux"))]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::process::{Command, Stdio};

    use common::ui::UI;
    use hcore::crypto::default_cache_key_path;
    use hcore::env as henv;
    use hcore::fs::{CACHE_KEY_PATH, find_command};
    use hcore::os::process;

    use error::{Error, Result};
    use VERSION;

    const DOCKER_CMD: &'static str = "docker";
    const DOCKER_CMD_ENVVAR: &'static str = "HAB_DOCKER_BINARY";
    const DOCKER_IMAGE: &'static str = "habitat-docker-registry.bintray.io/studio";
    const DOCKER_IMAGE_ENVVAR: &'static str = "HAB_DOCKER_STUDIO_IMAGE";
    const DOCKER_OPTS: &'static str = "HAB_DOCKER_OPTS";

    pub fn start(_ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let docker = henv::var(DOCKER_CMD_ENVVAR).unwrap_or(DOCKER_CMD.to_string());

        let cmd = match find_command(&docker) {
            Some(cmd) => cmd,
            None => return Err(Error::ExecCommandNotFound(docker.to_string())),
        };

        let output = Command::new(&cmd)
            .arg("images")
            .arg(&image_identifier())
            .arg("-q")
            .output()
            .expect("docker failed to start");

        let stdout = String::from_utf8(output.stdout).unwrap();
        if stdout.is_empty() {
            debug!("Failed to find studio image locally.");

            let child = Command::new(&cmd)
                .arg("pull")
                .arg(&image_identifier())
                .stdout(Stdio::inherit())
                .stderr(Stdio::inherit())
                .spawn()
                .expect("docker failed to start");

            let output = child.wait_with_output()
                .expect("failed to wait on child");

            if output.status.success() {
                debug!("Docker image is reachable. Proceeding with launching docker.");
            } else {
                debug!("Docker image is unreachable. Exit code = {:?}",
                       output.status);

                let err_output = String::from_utf8(output.stderr).unwrap();

                if err_output.contains("image") && err_output.contains("not found") {
                    return Err(Error::DockerImageNotFound(image_identifier().to_string()));
                } else if err_output.contains("Cannot connect to the Docker daemon") {
                    return Err(Error::DockerDaemonDown);
                } else {
                    return Err(Error::DockerNetworkDown(image_identifier().to_string()));
                }
            }
        } else {
            debug!("Found studio image locally.");
        }

        let mut cmd_args: Vec<OsString> = vec!["run".into(),
                                               "--rm".into(),
                                               "--tty".into(),
                                               "--interactive".into(),
                                               "--privileged".into()];

        // All the args already placed in `cmd_args` are things that we don't want to insert again.
        // Later args such as `--env` will overwrite any options (potentially) set mistakenly here.
        if let Ok(opts) = henv::var(DOCKER_OPTS) {
            let opts = opts.split(" ")
                .map(|v| v.into())
                // Ensure we're not passing something like `--tty` again here.
                .filter(|v| !cmd_args.contains(v))
                .collect::<Vec<_>>();
            debug!("Docker opts originating from DOCKER_OPTS = {:?}", opts);
            cmd_args.extend_from_slice(opts.as_slice());
        }

        let env_vars = vec!["HAB_DEPOT_URL", "HAB_ORIGIN", "http_proxy", "https_proxy"];
        for var in env_vars {
            if let Ok(val) = henv::var(var) {
                debug!("Propagating environment variable into container: {}={}",
                       var,
                       val);
                cmd_args.push("--env".into());
                cmd_args.push(format!("{}={}", var, val).into());
            }
        }

        cmd_args.push("--volume".into());
        cmd_args.push("/var/run/docker.sock:/var/run/docker.sock".into());
        cmd_args.push("--volume".into());
        cmd_args.push(format!("{}:/{}",
                              default_cache_key_path(None).to_string_lossy(),
                              CACHE_KEY_PATH)
            .into());
        cmd_args.push("--volume".into());
        cmd_args.push(format!("{}:/src", env::current_dir().unwrap().to_string_lossy()).into());

        cmd_args.push(image_identifier().into());
        cmd_args.extend_from_slice(args.as_slice());

        for var in vec!["http_proxy", "https_proxy"] {
            if let Ok(_) = henv::var(var) {
                debug!("Unsetting proxy environment variable '{}' before calling `{}'",
                       var,
                       docker);
                env::remove_var(var);
            }
        }

        debug!("Docker arguments = {:?}", cmd_args);
        Ok(try!(process::become_command(cmd, cmd_args)))
    }

    pub fn rerun_with_sudo_if_needed(_ui: &mut UI) -> Result<()> {
        // No sudo calls necessary here--we are calling `docker` commands instead
        Ok(())
    }

    /// Returns the Docker Studio image with tag for the desired version which corresponds to the
    /// same version (minus release) as this program.
    fn image_identifier() -> String {
        let version: Vec<&str> = VERSION.split("/").collect();
        henv::var(DOCKER_IMAGE_ENVVAR).unwrap_or(format!("{}:{}", DOCKER_IMAGE, version[0]))
    }

    #[cfg(test)]
    mod tests {
        use super::{image_identifier, DOCKER_IMAGE};
        use VERSION;

        #[test]
        fn retrieve_image_identifier() {
            assert_eq!(image_identifier(), format!("{}:{}", DOCKER_IMAGE, VERSION));
        }
    }
}
<|endoftext|>


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

use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use mime_guess::guess_mime_type_opt;
use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerResult};
use net_traits::filemanager_thread::{SelectedFile, FileManagerThreadError};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use util::thread::spawn_named;
use uuid::Uuid;

pub struct FileManager {
    receiver: IpcReceiver<FileManagerThreadMsg>,
    idmap: RefCell<HashMap<Uuid, PathBuf>>,
}

pub trait FileManagerThreadFactory {
    fn new() -> Self;
}

impl FileManagerThreadFactory for IpcSender<FileManagerThreadMsg> {
    /// Create a FileManagerThread
    fn new() -> IpcSender<FileManagerThreadMsg> {
        let (chan, recv) = ipc::channel().unwrap();

        spawn_named("FileManager".to_owned(), move || {
            FileManager::new(recv).start();
        });

        chan
    }
}

impl FileManager {
    fn new(recv: IpcReceiver<FileManagerThreadMsg>) -> FileManager {
        FileManager {
            receiver: recv,
            idmap: RefCell::new(HashMap::new()),
        }
    }

    /// Start the file manager event loop
    fn start(&mut self) {
        loop {
            match self.receiver.recv().unwrap() {
                FileManagerThreadMsg::SelectFile(sender) => self.select_file(sender),
                FileManagerThreadMsg::SelectFiles(sender) => self.select_files(sender),
                FileManagerThreadMsg::ReadFile(sender, id) => self.read_file(sender, id),
                FileManagerThreadMsg::DeleteFileID(id) => self.delete_fileid(id),
                FileManagerThreadMsg::Exit => break,
            }
        }
    }
}

impl FileManager {
    fn select_file(&mut self, sender: IpcSender<FileManagerResult<SelectedFile>>) {
        // TODO: Pull the dialog UI in and get selected
        let selected_path = Path::new("");

        match self.create_entry(selected_path) {
            Some(triple) => {
                let _ = sender.send(Ok(triple));
            },
            None => {
                let _ = sender.send(Err(FileManagerThreadError::InvalidSelection));
            }
        }
    }

    fn select_files(&mut self, sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>) {
        let selected_paths = vec![Path::new("")];

        let mut replies = vec![];

        for path in selected_paths {
            match self.create_entry(path) {
                Some(triple) => replies.push(triple),
                None => {
                    let _ = sender.send(Err(FileManagerThreadError::InvalidSelection));
                    return;
                }
            }
        }

        let _ = sender.send(Ok(replies));
    }

    fn create_entry(&mut self, file_path: &Path) -> Option<SelectedFile> {
        match File::open(file_path) {
            Ok(handler) => {
                let id = Uuid::new_v4();
                self.idmap.borrow_mut().insert(id, file_path.to_path_buf());

                // Unix Epoch: https://doc.servo.org/std/time/constant.UNIX_EPOCH.html
                let epoch = handler.metadata().and_then(|metadata| metadata.modified()).map_err(|_| ())
                                              .and_then(|systime| systime.elapsed().map_err(|_| ()))
                                              .and_then(|elapsed| {
                                                    let secs = elapsed.as_secs();
                                                    let nsecs = elapsed.subsec_nanos();
                                                    let msecs = secs * 1000 + nsecs as u64 / 1000000;
                                                    Ok(msecs)
                                                });

                let filename = file_path.file_name();

                match (epoch, filename) {
                    (Ok(epoch), Some(filename)) => {
                        let filename_path = Path::new(filename);
                        let mime = guess_mime_type_opt(filename_path);
                        Some(SelectedFile {
                            id: id,
                            filename: filename_path.to_path_buf(),
                            modified: epoch,
                            type_string: match mime {
                                Some(x) => format!("{}", x),
                                None    => "".to_string(),
                            },
                        })
                    }
                    _ => None
                }
            },
            Err(_) => None
        }
    }

    fn read_file(&mut self, sender: IpcSender<FileManagerResult<Vec<u8>>>, id: Uuid) {
        match self.idmap.borrow().get(&id).and_then(|filepath| {
            let mut buffer = vec![];
            match File::open(&filepath) {
                Ok(mut handler) => {
                    match handler.read_to_end(&mut buffer) {
                        Ok(_) => Some(buffer),
                        Err(_) => None,
                    }
                },
                Err(_) => None,
            }
        }) {
            Some(buffer) => {
                let _ = sender.send(Ok(buffer));
            },
            None => {
                let _ = sender.send(Err(FileManagerThreadError::ReadFileError));
            }
        };
    }

    fn delete_fileid(&mut self, id: Uuid) {
        self.idmap.borrow_mut().remove(&id);
    }
}
<commit_msg>Auto merge of #11576 - rwakulszowa:net-filemanager-idmap-drop-refcell, r=nox<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use mime_guess::guess_mime_type_opt;
use net_traits::filemanager_thread::{FileManagerThreadMsg, FileManagerResult};
use net_traits::filemanager_thread::{SelectedFile, FileManagerThreadError};
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use util::thread::spawn_named;
use uuid::Uuid;

pub struct FileManager {
    receiver: IpcReceiver<FileManagerThreadMsg>,
    idmap: HashMap<Uuid, PathBuf>,
}

pub trait FileManagerThreadFactory {
    fn new() -> Self;
}

impl FileManagerThreadFactory for IpcSender<FileManagerThreadMsg> {
    /// Create a FileManagerThread
    fn new() -> IpcSender<FileManagerThreadMsg> {
        let (chan, recv) = ipc::channel().unwrap();

        spawn_named("FileManager".to_owned(), move || {
            FileManager::new(recv).start();
        });

        chan
    }
}

impl FileManager {
    fn new(recv: IpcReceiver<FileManagerThreadMsg>) -> FileManager {
        FileManager {
            receiver: recv,
            idmap: HashMap::new(),
        }
    }

    /// Start the file manager event loop
    fn start(&mut self) {
        loop {
            match self.receiver.recv().unwrap() {
                FileManagerThreadMsg::SelectFile(sender) => self.select_file(sender),
                FileManagerThreadMsg::SelectFiles(sender) => self.select_files(sender),
                FileManagerThreadMsg::ReadFile(sender, id) => self.read_file(sender, id),
                FileManagerThreadMsg::DeleteFileID(id) => self.delete_fileid(id),
                FileManagerThreadMsg::Exit => break,
            }
        }
    }
}

impl FileManager {
    fn select_file(&mut self, sender: IpcSender<FileManagerResult<SelectedFile>>) {
        // TODO: Pull the dialog UI in and get selected
        let selected_path = Path::new("");

        match self.create_entry(selected_path) {
            Some(triple) => {
                let _ = sender.send(Ok(triple));
            },
            None => {
                let _ = sender.send(Err(FileManagerThreadError::InvalidSelection));
            }
        }
    }

    fn select_files(&mut self, sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>) {
        let selected_paths = vec![Path::new("")];

        let mut replies = vec![];

        for path in selected_paths {
            match self.create_entry(path) {
                Some(triple) => replies.push(triple),
                None => {
                    let _ = sender.send(Err(FileManagerThreadError::InvalidSelection));
                    return;
                }
            }
        }

        let _ = sender.send(Ok(replies));
    }

    fn create_entry(&mut self, file_path: &Path) -> Option<SelectedFile> {
        match File::open(file_path) {
            Ok(handler) => {
                let id = Uuid::new_v4();
                self.idmap.insert(id, file_path.to_path_buf());

                // Unix Epoch: https://doc.servo.org/std/time/constant.UNIX_EPOCH.html
                let epoch = handler.metadata().and_then(|metadata| metadata.modified()).map_err(|_| ())
                                              .and_then(|systime| systime.elapsed().map_err(|_| ()))
                                              .and_then(|elapsed| {
                                                    let secs = elapsed.as_secs();
                                                    let nsecs = elapsed.subsec_nanos();
                                                    let msecs = secs * 1000 + nsecs as u64 / 1000000;
                                                    Ok(msecs)
                                                });

                let filename = file_path.file_name();

                match (epoch, filename) {
                    (Ok(epoch), Some(filename)) => {
                        let filename_path = Path::new(filename);
                        let mime = guess_mime_type_opt(filename_path);
                        Some(SelectedFile {
                            id: id,
                            filename: filename_path.to_path_buf(),
                            modified: epoch,
                            type_string: match mime {
                                Some(x) => format!("{}", x),
                                None    => "".to_string(),
                            },
                        })
                    }
                    _ => None
                }
            },
            Err(_) => None
        }
    }

    fn read_file(&mut self, sender: IpcSender<FileManagerResult<Vec<u8>>>, id: Uuid) {
        match self.idmap.get(&id).and_then(|filepath| {
            let mut buffer = vec![];
            match File::open(&filepath) {
                Ok(mut handler) => {
                    match handler.read_to_end(&mut buffer) {
                        Ok(_) => Some(buffer),
                        Err(_) => None,
                    }
                },
                Err(_) => None,
            }
        }) {
            Some(buffer) => {
                let _ = sender.send(Ok(buffer));
            },
            None => {
                let _ = sender.send(Err(FileManagerThreadError::ReadFileError));
            }
        };
    }

    fn delete_fileid(&mut self, id: Uuid) {
        self.idmap.remove(&id);
    }
}
<|endoftext|>


<commit_before>use rules::Rule;

pub struct MultiLinesComment {
}

impl MultiLinesComment {
	pub fn new() -> MultiLinesComment {
		MultiLinesComment { }
	}
}

impl Rule for MultiLinesComment {
	fn verify(&self, filename: &str, content: &str) -> Vec<String> {
		let mut errors = Vec::new();
		let mut line_number: usize = 1;

		let mut in_comment = false;

		for line in content.lines() {
			if line.contains("/*") && !line.contains("*/") {
				//Multi lines comment
				if in_comment {
					errors.push(format!("[{}:{}]Comments can't be nested.", filename, line_number));
				}

				let nb_non_white_space = line.chars().filter(|c| !c.is_whitespace()).count();
				if !(nb_non_white_space == 2 || (nb_non_white_space == 3 && line.contains("/**"))) {
					errors.push(format!("[{}:{}]Comment start delimiter must appear on its own line.", filename, line_number));
				}
				in_comment = true;

			}

			if line.contains("*/") && !line.contains("/*") {
				//Multi lines comment
				if !in_comment {
					errors.push(format!("[{}:{}]Unexpected comment end delimiter.", filename, line_number));
				}

				if line.chars().filter(|c| !c.is_whitespace()).count() != 2 {
					errors.push(format!("[{}:{}]Comment end delimiter must appear on its own line.", filename, line_number));
				}
				in_comment = false;
			}

			if in_comment {
				if line.trim().len() < 2 || !line.trim_left().starts_with("**") {
					errors.push(format!("[{}:{}]Comment intermediary line must start with '**'.", filename, line_number));
				}
			}
			line_number += 1;
		}

		if in_comment {
			errors.push(format!("[{}:{}]Expected comment end delimiter.", filename, line_number));
		}

		return errors;
	}
}

#[cfg(test)]
mod test {
	use super::*;
	#[test]
	fn multi_lines_comment() {
		let multi_lines_comment = MultiLinesComment::new(5);

	}
}
<commit_msg>added test for MultiLinesComment<commit_after>use rules::Rule;

pub struct MultiLinesComment {
}

impl MultiLinesComment {
	pub fn new() -> MultiLinesComment {
		MultiLinesComment { }
	}
}

impl Rule for MultiLinesComment {
	fn verify(&self, filename: &str, content: &str) -> Vec<String> {
		let mut errors = Vec::new();
		let mut line_number: usize = 1;

		let mut in_comment = false;

		for line in content.lines() {
			if line.contains("*/") && !line.contains("/*") {
				//Multi lines comment
				if !in_comment {
					errors.push(format!("[{}:{}]Unexpected comment end delimiter.", filename, line_number));
				}

				if line.chars().filter(|c| !c.is_whitespace()).count() != 2 {
					errors.push(format!("[{}:{}]Comment end delimiter must appear on its own line.", filename, line_number));
				}
				in_comment = false;
			}

			if in_comment {
				if !line.trim_left().starts_with("**") {
					errors.push(format!("[{}:{}]Comment intermediary line must start with '**'.", filename, line_number));
				}
			}

			if line.contains("/*") && !line.contains("*/") {
				//Multi lines comment
				if in_comment {
					errors.push(format!("[{}:{}]Comments can't be nested.", filename, line_number));
				}

				let nb_non_white_space = line.chars().filter(|c| !c.is_whitespace()).count();
				if !(nb_non_white_space == 2 || (nb_non_white_space == 3 && line.contains("/**"))) {
					errors.push(format!("[{}:{}]Comment start delimiter must appear on its own line.", filename, line_number));
				}
				in_comment = true;

			}

			line_number += 1;
		}

		if in_comment {
			errors.push(format!("[{}:{}]Expected comment end delimiter.", filename, line_number));
		}

		return errors;
	}
}

#[cfg(test)]
mod test {
	use super::*;
	#[test]
	fn multi_lines_comment() {
		let multi_lines_comment = MultiLinesComment::new();

		assert_eq!(multi_lines_comment.verify("", "zdnkcndccc").len(), 0);
		assert_eq!(multi_lines_comment.verify("", "//zdnkcndccc").len(), 0);
		assert_eq!(multi_lines_comment.verify("", "/*zdnkcndccc*/").len(), 0);
		assert_eq!(multi_lines_comment.verify("", "/*\n**zdnkcn\n*/").len(), 0);

		assert_ne!(multi_lines_comment.verify("", "/*zdnkcn\ndccc*/").len(), 0);
		assert_ne!(multi_lines_comment.verify("", "/*\nzdnkcn\n*/").len(), 0);
		assert_ne!(multi_lines_comment.verify("", "/*\nav**zdnkcn\n*/").len(), 0);
		assert_ne!(multi_lines_comment.verify("", "/** *\n**zdnkcn\n*/").len(), 0);
		assert_ne!(multi_lines_comment.verify("", "/**\n**zdnkcn\n*/*").len(), 0);
	}
}
<|endoftext|>


<commit_before>use crate::core::resolver::ResolveOpts;
use crate::core::{Shell, Workspace};
use crate::ops;
use crate::util::CargoResult;
use failure::Fail;
use opener;
use std::collections::HashMap;
use std::error::Error;
use std::path::Path;
use std::process::Command;

/// Strongly typed options for the `cargo doc` command.
#[derive(Debug)]
pub struct DocOptions<'a> {
    /// Whether to attempt to open the browser after compiling the docs
    pub open_result: bool,
    /// Options to pass through to the compiler
    pub compile_opts: ops::CompileOptions<'a>,
}

/// Main method for `cargo doc`.
pub fn doc(ws: &Workspace<'_>, options: &DocOptions<'_>) -> CargoResult<()> {
    let specs = options.compile_opts.spec.to_package_id_specs(ws)?;
    let opts = ResolveOpts::new(
        /*dev_deps*/ true,
        &options.compile_opts.features,
        options.compile_opts.all_features,
        !options.compile_opts.no_default_features,
    );
    let ws_resolve = ops::resolve_ws_with_opts(ws, opts, &specs)?;

    let ids = specs
        .iter()
        .map(|s| s.query(ws_resolve.targeted_resolve.iter()))
        .collect::<CargoResult<Vec<_>>>()?;
    let pkgs = ws_resolve.pkg_set.get_many(ids)?;

    let mut lib_names = HashMap::new();
    let mut bin_names = HashMap::new();
    let mut names = Vec::new();
    for package in &pkgs {
        for target in package.targets().iter().filter(|t| t.documented()) {
            if target.is_lib() {
                if let Some(prev) = lib_names.insert(target.crate_name(), package) {
                    failure::bail!(
                        "The library `{}` is specified by packages `{}` and \
                         `{}` but can only be documented once. Consider renaming \
                         or marking one of the targets as `doc = false`.",
                        target.crate_name(),
                        prev,
                        package
                    );
                }
            } else if let Some(prev) = bin_names.insert(target.crate_name(), package) {
                failure::bail!(
                    "The binary `{}` is specified by packages `{}` and \
                     `{}` but can be documented only once. Consider renaming \
                     or marking one of the targets as `doc = false`.",
                    target.crate_name(),
                    prev,
                    package
                );
            }
            names.push(target.crate_name());
        }
    }

    let compilation = ops::compile(ws, &options.compile_opts)?;

    if options.open_result {
        let name = match names.first() {
            Some(s) => s.to_string(),
            None => return Ok(()),
        };
        let path = compilation
            .root_output
            .with_file_name("doc")
            .join(&name)
            .join("index.html");
        if path.exists() {
            let mut shell = options.compile_opts.config.shell();
            shell.status("Opening", path.display())?;
            open_docs(&path, &mut shell)?;
        }
    }

    Ok(())
}

fn open_docs(path: &Path, shell: &mut Shell) -> CargoResult<()> {
    match std::env::var_os("BROWSER") {
        Some(browser) => {
            if let Err(e) = Command::new(&browser).arg(path).status() {
                shell.warn(format!(
                    "Couldn't open docs with {}: {}",
                    browser.to_string_lossy(),
                    e.description()
                ))?;
            }
        }
        None => {
            if let Err(e) = opener::open(&path) {
                shell.warn(format!("Couldn't open docs: {}", e))?;
                for cause in (&e as &dyn Fail).iter_chain() {
                    shell.warn(format!("Caused by:\n {}", cause))?;
                }
            }
        }
    };

    Ok(())
}
<commit_msg>Auto merge of #7698 - dtolnay:description, r=alexcrichton<commit_after>use crate::core::resolver::ResolveOpts;
use crate::core::{Shell, Workspace};
use crate::ops;
use crate::util::CargoResult;
use failure::Fail;
use opener;
use std::collections::HashMap;
use std::path::Path;
use std::process::Command;

/// Strongly typed options for the `cargo doc` command.
#[derive(Debug)]
pub struct DocOptions<'a> {
    /// Whether to attempt to open the browser after compiling the docs
    pub open_result: bool,
    /// Options to pass through to the compiler
    pub compile_opts: ops::CompileOptions<'a>,
}

/// Main method for `cargo doc`.
pub fn doc(ws: &Workspace<'_>, options: &DocOptions<'_>) -> CargoResult<()> {
    let specs = options.compile_opts.spec.to_package_id_specs(ws)?;
    let opts = ResolveOpts::new(
        /*dev_deps*/ true,
        &options.compile_opts.features,
        options.compile_opts.all_features,
        !options.compile_opts.no_default_features,
    );
    let ws_resolve = ops::resolve_ws_with_opts(ws, opts, &specs)?;

    let ids = specs
        .iter()
        .map(|s| s.query(ws_resolve.targeted_resolve.iter()))
        .collect::<CargoResult<Vec<_>>>()?;
    let pkgs = ws_resolve.pkg_set.get_many(ids)?;

    let mut lib_names = HashMap::new();
    let mut bin_names = HashMap::new();
    let mut names = Vec::new();
    for package in &pkgs {
        for target in package.targets().iter().filter(|t| t.documented()) {
            if target.is_lib() {
                if let Some(prev) = lib_names.insert(target.crate_name(), package) {
                    failure::bail!(
                        "The library `{}` is specified by packages `{}` and \
                         `{}` but can only be documented once. Consider renaming \
                         or marking one of the targets as `doc = false`.",
                        target.crate_name(),
                        prev,
                        package
                    );
                }
            } else if let Some(prev) = bin_names.insert(target.crate_name(), package) {
                failure::bail!(
                    "The binary `{}` is specified by packages `{}` and \
                     `{}` but can be documented only once. Consider renaming \
                     or marking one of the targets as `doc = false`.",
                    target.crate_name(),
                    prev,
                    package
                );
            }
            names.push(target.crate_name());
        }
    }

    let compilation = ops::compile(ws, &options.compile_opts)?;

    if options.open_result {
        let name = match names.first() {
            Some(s) => s.to_string(),
            None => return Ok(()),
        };
        let path = compilation
            .root_output
            .with_file_name("doc")
            .join(&name)
            .join("index.html");
        if path.exists() {
            let mut shell = options.compile_opts.config.shell();
            shell.status("Opening", path.display())?;
            open_docs(&path, &mut shell)?;
        }
    }

    Ok(())
}

fn open_docs(path: &Path, shell: &mut Shell) -> CargoResult<()> {
    match std::env::var_os("BROWSER") {
        Some(browser) => {
            if let Err(e) = Command::new(&browser).arg(path).status() {
                shell.warn(format!(
                    "Couldn't open docs with {}: {}",
                    browser.to_string_lossy(),
                    e
                ))?;
            }
        }
        None => {
            if let Err(e) = opener::open(&path) {
                shell.warn(format!("Couldn't open docs: {}", e))?;
                for cause in (&e as &dyn Fail).iter_chain() {
                    shell.warn(format!("Caused by:\n {}", cause))?;
                }
            }
        }
    };

    Ok(())
}
<|endoftext|>