/*
 * cxxclass.{cc,hh} -- representation of C++ classes
 * Eddie Kohler
 *
 * Copyright (c) 2000 Massachusetts Institute of Technology
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>

#include "cxxclass.hh"
#include <click/straccum.hh>

bool CxxFunction::parsing_header_file;

CxxFunction::CxxFunction(const String &name, bool in_header,
			 const String &ret_type, const String &args,
			 const String &body, const String &clean_body)
  : _name(name), _in_header(in_header), _from_header_file(parsing_header_file),
    _alive(true), _ret_type(ret_type), _args(args),
    _body(body), _clean_body(clean_body)
{
  //fprintf(stderr, "%s::%s\n", _name.c_str(), _body.c_str());
}

String
compile_pattern(const String &pattern0)
{
  static const char *three_tokens[] = { ">>=", "<<=", "->*", "::*", 0 };
  static const char *two_tokens[] = { "++", "--", "+=", "-=", "*=", "/=", "->",
				      "%=", "^=", "&=", "~=", "==", "!=",
				      "&&", "||",
				      ">=", "<=", "::", "<<", ">>", ".*", 0 };
  
  StringAccum sa;
  const char *s = pattern0.data();
  const char *end_s = s + pattern0.length();

  while (s < end_s && isspace(*s)) // skip leading space
    s++;

  // XXX not all constraints on patterns are expressed here
  while (s < end_s) {
    if (isspace(*s)) {
      sa << ' ';
      while (s < end_s && isspace(*s))
	s++;
      
    } else if (isalnum(*s) || *s == '_') {
      while (s < end_s && (isalnum(*s) || *s == '_'))
	sa << *s++;
      sa << ' ';
      
    } else if (*s == '#') {
      assert(s < end_s - 1 && isdigit(s[1]));
      sa << s[0] << s[1];
      s += 2;
      
    } else {
      const char *token = 0;
      if (s < end_s - 2)
	for (int i = 0; !token && three_tokens[i]; i++)
	  if (strncmp(three_tokens[i], s, 3) == 0)
	    token = three_tokens[i];
      if (!token && s < end_s - 1)
	for (int i = 0; !token && two_tokens[i]; i++)
	  if (strncmp(two_tokens[i], s, 2) == 0)
	    token = two_tokens[i];
      if (!token)
	sa << *s++ << ' ';
      else {
	sa << token << ' ';
	s += strlen(token);
      }
    }
  }

  return sa.take_string();
}

bool
CxxFunction::find_expr(const String &pattern, int *pos1, int *pos2,
		       int match_pos[10], int match_len[10]) const
{
  const char *ps = pattern.data();
  int plen = pattern.length();

  const char *ts = _clean_body.data();
  int tpos = 0;
  int tlen = _clean_body.length();

  while (tpos < tlen) {

    // fast loop: look for occurrences of first character in pattern
    while (tpos < tlen && ts[tpos] != ps[0])
      tpos++;

    int tpos1 = tpos;
    tpos++;
    int ppos = 1;

    while (tpos < tlen && ppos < plen) {

      if (isspace(ps[ppos])) {
	if (ppos > 0 && (isalnum(ps[ppos-1]) || ps[ppos-1] == '_')
	    && (isalnum(ts[tpos]) || ts[tpos] == '_'))
	  break;
	while (tpos < tlen && isspace(ts[tpos]))
	  tpos++;
	ppos++;

      } else if (ps[ppos] == '#') {
	// save expr and skip over it
	int paren_level = 0;
	int question_level = 0;
	int which = ps[ppos+1] - '0';
	match_pos[which] = tpos;
	while (tpos < tlen) {
	  if (ts[tpos] == '(')
	    paren_level++;
	  else if (ts[tpos] == ')') {
	    if (paren_level == 0)
	      break;
	    paren_level--;
	  } else if (ts[tpos] == ',') {
	    if (paren_level == 0 && question_level == 0)
	      break;
	  } else if (ts[tpos] == '?')
	    question_level++;
	  else if (ts[tpos] == ':' && question_level)
	    question_level--;
	  tpos++;
	}
	match_len[which] = tpos - match_pos[which];
	ppos += 2;

      } else if (ps[ppos] == ts[tpos])
	ppos++, tpos++;

      else
	break;

    }

    if (ppos >= plen) {
      // check that this pattern match didn't occur after some evil qualifier,
      // namely '.', '::', or '->'
      int p = tpos1 - 1;
      while (p >= 0 && isspace(ts[p]))
	p--;
      if (p < 0
	  || (ts[p] != '.'
	      && (p == 0 || ts[p-1] != ':' || ts[p] != ':')
	      && (p == 0 || ts[p-1] != '-' || ts[p] != '>'))) {
	*pos1 = tpos1;
	*pos2 = tpos;
	return true;
      }
    }

    // look for next match
    tpos = tpos1 + 1;
  }

  return false;
}

bool
CxxFunction::find_expr(const String &pattern) const
{
  int pos1, pos2, match_pos[10], match_len[10];
  return find_expr(pattern, &pos1, &pos2, match_pos, match_len);
}

bool
CxxFunction::replace_expr(const String &pattern, const String &replacement)
{
  int pos1, pos2, match_pos[10], match_len[10];
  if (!find_expr(pattern, &pos1, &pos2, match_pos, match_len))
    return false;

  //fprintf(stderr, ":::::: %s\n", _body.c_str());
  
  StringAccum sa, clean_sa;
  const char *s = replacement.data();
  const char *end_s = s + replacement.length();
  while (s < end_s) {
    if (*s == '#') {
      assert(s < end_s - 1 && isdigit(s[1]));
      int which = s[1] - '0';
      sa << _body.substring(match_pos[which], match_len[which]);
      clean_sa << _clean_body.substring(match_pos[which], match_len[which]);
      s += 2;
    } else {
      sa << *s;
      clean_sa << *s;
      s++;
    }
  }

  String new_body =
    _body.substring(0, pos1) + sa.take_string() + _body.substring(pos2);
  String new_clean_body =
    _clean_body.substring(0, pos1) + clean_sa.take_string()
    + _clean_body.substring(pos2);
  _body = new_body;
  _clean_body = new_clean_body;

  //fprintf(stderr, ">>>>>> %s\n", _body.c_str());
  return true;
}


/*****
 * CxxClass
 **/

CxxClass::CxxClass(const String &name)
  : _name(name), _fn_map(-1)
{
  //fprintf(stderr, "!!!!!%s\n", _name.c_str());
}

void
CxxClass::add_parent(CxxClass *cxx)
{
  _parents.push_back(cxx);
}

CxxFunction &
CxxClass::defun(const CxxFunction &fn)
{
  int which = _functions.size();
  _functions.push_back(fn);
  _fn_map.insert(fn.name(), which);
  _functions.back().unkill();
  return _functions.back();
}

bool
CxxClass::reach(int findex, Vector<int> &reached)
{
  if (findex < 0)
    return false;
  if (reached[findex])
    return _should_rewrite[findex];
  reached[findex] = true;
  
  // return true if reachable and rewritable
  const String &clean_body = _functions[findex].clean_body();
  const char *s = clean_body.data();
  int p = 0;
  int len = clean_body.length();
  bool should_rewrite = _has_push[findex] || _has_pull[findex];

  while (p < len) {
    
    // look for a function call
    while (p < len && s[p] != '(')
      p++;
    if (p >= len)
      break;

    int paren_p = p;
    for (p--; p >= 0 && isspace(s[p]); p--)
      /* nada */;
    if (p < 0 || (!isalnum(s[p]) && s[p] != '_')) {
      p = paren_p + 1;
      continue;
    }
    int end_word_p = p + 1;
    while (p >= 0 && (isalnum(s[p]) || s[p] == '_'))
      p--;
    int start_word_p = p + 1;
    while (p >= 0 && isspace(s[p]))
      p--;

    // have found word; check that it is a direct call
    if (p >= 0 && (s[p] == '.' || (p > 0 && s[p-1] == '-' && s[p] == '>')))
      /* do nothing; a call of some object */;
    else {
      // XXX class-qualified?
      String name = clean_body.substring(start_word_p, end_word_p - start_word_p);
      int findex2 = _fn_map[name];
      if (findex2 >= 0 && reach(findex2, reached))
	should_rewrite = true;
    }

    // skip past word
    p = paren_p + 1;
  }

  if (!should_rewrite && !_functions[findex].from_header_file()) {
    // might still be rewritable if it's inlined from the
    // .cc file, which we can't #include
    const String &ret_type = _functions[findex].ret_type();
    const char *s = ret_type.data();
    int len = ret_type.length();
    for (int p = 0; p < len - 6; p++)
      if (s[p+0] == 'i' && s[p+1] == 'n' && s[p+2] == 'l'
	  && s[p+3] == 'i' && s[p+4] == 'n' && s[p+5] == 'e'
	  && (p == 0 || isspace(s[p-1]))
	  && (p == len-6 || isspace(s[p+6]))) {
	should_rewrite = true;
	break;
      }
  }
  
  _should_rewrite[findex] = should_rewrite;
  return should_rewrite;
}

bool
CxxClass::find_should_rewrite()
{
  _has_push.assign(nfunctions(), 0);
  _has_pull.assign(nfunctions(), 0);
  _should_rewrite.assign(nfunctions(), 0);
  
  if (_fn_map["never_devirtualize"] >= 0)
    return false;

  static String::Initializer initializer;
  static String push_pattern = compile_pattern("output(#0).push(#1)");
  static String pull_pattern = compile_pattern("input(#0).pull()");
  static String checked_push_pattern = compile_pattern("checked_output_push(#0,#1)");
  for (int i = 0; i < nfunctions(); i++) {
    if (_functions[i].find_expr(push_pattern)
	|| _functions[i].find_expr(checked_push_pattern))
      _has_push[i] = 1;
    if (_functions[i].find_expr(pull_pattern))
      _has_pull[i] = 1;
  }

  Vector<int> reached(nfunctions(), 0);
  bool any = reach(_fn_map["push"], reached);
  any |= reach(_fn_map["pull"], reached);
  any |= reach(_fn_map["run_task"], reached);
  any |= reach(_fn_map["run_timer"], reached);
  any |= reach(_fn_map["selected"], reached);
  int simple_action = _fn_map["simple_action"];
  if (simple_action >= 0) {
    reach(simple_action, reached);
    _should_rewrite[simple_action] = any = true;
  }
  if (_fn_map["devirtualize_all"] >= 0) {
    for (int i = 0; i < nfunctions(); i++) {
      const String &n = _functions[i].name();
      if (n != _name && n[0] != '~')
	_should_rewrite[i] = any = true;
    }
  }
  
  return any;
}

void
CxxClass::header_text(StringAccum &sa) const
{
  sa << "class " << _name;
  if (_parents.size()) {
    sa << " : ";
    for (int i = 0; i < _parents.size(); i++) {
      if (i) sa << ", ";
      sa << "public " << _parents[i]->name();
    }
  }
  sa << " {\n public:\n";
  for (int i = 0; i < _functions.size(); i++) {
    const CxxFunction &fn = _functions[i];
    if (fn.alive()) {
      sa << "  " << fn.ret_type() << " " << fn.name() << fn.args();
      if (fn.in_header())
	sa << " {" << fn.body() << "}\n";
      else
	sa << ";\n";
    }
  }
  sa << "};\n";
}

void
CxxClass::source_text(StringAccum &sa) const
{
  for (int i = 0; i < _functions.size(); i++) {
    const CxxFunction &fn = _functions[i];
    if (fn.alive() && !fn.in_header()) {
      sa << fn.ret_type() << "\n" << _name << "::" << fn.name() << fn.args();
      sa << "\n{" << fn.body() << "}\n";
    }
  }
}


/*****
 * CxxInfo
 **/

CxxInfo::CxxInfo()
  : _class_map(-1)
{
}

CxxInfo::~CxxInfo()
{
  for (int i = 0; i < _classes.size(); i++)
    delete _classes[i];
}

CxxClass *
CxxInfo::make_class(const String &name)
{
  int which = _class_map[name];
  if (which < 0) {
    CxxClass *nclass = new CxxClass(name);
    which = _classes.size();
    _classes.push_back(nclass);
    _class_map.insert(name, which);
  }
  return _classes[which];
}

static String
remove_crap(const String &original_text)
{
  // Get rid of preprocessor directives, comments, string literals, and
  // character literals by replacing them with the right number of spaces.

  const char *s = original_text.data();
  const char *end_s = s + original_text.length();

  StringAccum new_text;
  char *o = new_text.extend(original_text.length());

  char *if0_o_ptr = 0;
  
  while (s < end_s) {
    // read one line

    // skip spaces at beginning of line
    while (s < end_s && isspace(*s))
      *o++ = *s++;

    if (s >= end_s)		// end of data
      break;

    if (*s == '#') {		// preprocessor directive
      const char *first_s = s;
      while (1) {
	while (s < end_s && *s != '\n' && *s != '\r')
	  *o++ = ' ', s++;
	bool backslash = (s[-1] == '\\');
	while (s < end_s && (*s == '\n' || *s == '\r'))
	  *o++ = *s++;
	if (!backslash)
	  break;
      }
      // check for '#if 0 .. #endif'
      const char *ss = first_s + 1;
      while (ss < s && isspace(*ss))
	ss++;
      if (ss < s - 5 && ss[0] == 'e' && ss[1] == 'n' && ss[2] == 'd'
	  && ss[3] == 'i' && ss[4] == 'f') {
	if (if0_o_ptr)
	  while (if0_o_ptr < o)
	    *if0_o_ptr++ = ' ';
	if0_o_ptr = 0;
      } else if (ss < s - 3 && ss[0] == 'i' && ss[1] == 'f') {
	for (ss += 2; ss < s && isspace(*ss); ss++) ;
	if (ss < s && ss[0] == '0')
	  if0_o_ptr = o;
      }
      continue;
    }

    // scan; stop at EOL, comment start, or literal start
    while (s < end_s && *s != '\n' && *s != '\r') {

      // copy chars
      while (s < end_s && *s != '/' && *s != '\"' && *s != '\''
	     && *s != '\n' && *s != '\r')
	*o++ = *s++;

      if (s < end_s - 1 && *s == '/' && s[1] == '*') {
	// slash-star comment
	*o++ = ' ';
	*o++ = ' ';
	s += 2;
	while (s < end_s && (*s != '*' || s >= end_s - 1 || s[1] != '/')) {
	  *o++ = (*s == '\n' || *s == '\r' ? *s : ' ');
	  s++;
	}
	if (s < end_s) {
	  *o++ = ' ';
	  *o++ = ' ';
	  s += 2;
	}
	
      } else if (s < end_s - 1 && *s == '/' && s[1] == '/') {
	// slash-slash comment
	*o++ = ' ';
	*o++ = ' ';
	s += 2;
	while (s < end_s && *s != '\n' && *s != '\r')
	  *o++ = ' ', s++;
	
      } else if (*s == '\"' || *s == '\'') {
	// literal
	// XXX I am not sure why the closing quote,
	// and any characters preceded by backslash, are turned into $.
	char stopper = *s;
	*o++ = ' ', s++;
	while (s < end_s && *s != stopper) {
	  *o++ = ' ', s++;
	  if (s[-1] == '\\')
	    *o++ = '$', s++;
	}
	if (s < end_s)
	  *o++ = '$', s++;
	
      } else if (*s != '\n' && *s != '\r')
	// random other character, fine
	*o++ = *s++;
    }

    // copy EOL characters
    while (s < end_s && (*s == '\n' || *s == '\r'))
      *o++ = *s++;
  }

  return new_text.take_string();
}

static int
skip_balanced_braces(const String &text, int p)
{
  const char *s = text.data();
  int len = text.length();
  int brace_level = 0;
  while (p < len) {
    if (s[p] == '{')
      brace_level++;
    else if (s[p] == '}') {
      if (!--brace_level)
	return p + 1;
    }
    p++;
  }
  return p;
}

static int
skip_balanced_parens(const String &text, int p)
{
  const char *s = text.data();
  int len = text.length();
  int brace_level = 0;
  while (p < len) {
    if (s[p] == '(')
      brace_level++;
    else if (s[p] == ')') {
      if (!--brace_level)
	return p + 1;
    }
    p++;
  }
  return p;
}

int
CxxInfo::parse_function_definition(const String &text, int fn_start_p,
				   int paren_p, const String &original,
				   CxxClass *cxx_class)
{
  // find where we think open brace should be
  int p = skip_balanced_parens(text, paren_p);
  const char *s = text.data();
  int len = text.length();
  while (p < len && isspace(s[p]))
    p++;
  if (p < len - 5 && strncmp(s+p, "const", 5) == 0) {
    for (p += 5; p < len && isspace(s[p]); p++)
      /* nada */;
  }
  // if open brace is not there, a function declaration or something similar;
  // return
  if (p >= len || s[p] != '{')
    return p;

  // save boundaries of function body
  int open_brace_p = p;
  int close_brace_p = skip_balanced_braces(text, open_brace_p);

  // find arguments; cut space from end
  for (p = open_brace_p - 1; p >= paren_p && isspace(s[p]); p--)
    /* nada */;
  String args = original.substring(paren_p, p + 1 - paren_p);

  // find function name and class name
  for (p = paren_p - 1; p > fn_start_p && isspace(s[p]); p--)
    /* nada */;
  int end_fn_name_p = p + 1;
  while (p >= fn_start_p && (isalnum(s[p]) || s[p] == '_' || s[p] == '~'))
    p--;
  String fn_name = original.substring(p + 1, end_fn_name_p - (p + 1));
  String class_name;
  if (p >= fn_start_p + 2 && s[p] == ':' && s[p-1] == ':') {
    int end_class_name_p = p - 1;
    for (p -= 2; p >= fn_start_p && (isalnum(s[p]) || s[p] == '_' || s[p] == '~'); p--)
      /* nada */;
    if (p > fn_start_p && s[p] == ':') // nested class fns uninteresting
      return close_brace_p;
    class_name = original.substring(p + 1, end_class_name_p - (p + 1));
  }

  // find return type; skip access control declarations, cut space from end
  while (1) {
    int access_p;
    if (p >= fn_start_p + 6 && strncmp(s+fn_start_p, "public", 6) == 0)
      access_p = fn_start_p + 6;
    else if (p >= fn_start_p + 7 && strncmp(s+fn_start_p, "private", 7) == 0)
      access_p = fn_start_p + 7;
    else if (p >= fn_start_p + 9 && strncmp(s+fn_start_p, "protected", 9) == 0)
      access_p = fn_start_p + 9;
    else
      break;
    while (access_p < p && isspace(s[access_p]))
      access_p++;
    if (access_p == p || s[access_p] != ':')
      break;
    for (access_p++; access_p < p && isspace(s[access_p]); access_p++)
      /* nada */;
    fn_start_p = access_p;
  }
  while (p >= fn_start_p && isspace(s[p]))
    p--;
  String ret_type = original.substring(fn_start_p, p + 1 - fn_start_p);

  // decide if this function/class pair is OK
  CxxClass *relevant_class;
  if (class_name)
    relevant_class = make_class(class_name);
  else
    relevant_class = cxx_class;

  // define function
  if (relevant_class) {
    int body_pos = open_brace_p + 1;
    int body_len = close_brace_p - 1 - body_pos;
    relevant_class->defun
      (CxxFunction(fn_name, !class_name, ret_type, args,
		   original.substring(body_pos, body_len),
		   text.substring(body_pos, body_len)));
  }

  // done
  return close_brace_p;
}

int
CxxInfo::parse_class_definition(const String &text, int p,
				const String &original)
{
  // find class name
  const char *s = text.data();
  int len = text.length();
  while (p < len && isspace(s[p]))
    p++;
  int name_start_p = p;
  while (p < len && (isalnum(s[p]) || s[p] == '_'))
    p++;
  String class_name = original.substring(name_start_p, p - name_start_p);
  CxxClass *cxxc = make_class(class_name);

  // parse superclasses
  while (p < len && s[p] != '{') {
    while (p < len && s[p] != '{' && !isalnum(s[p]) && s[p] != '_')
      p++;
    int p1 = p;
    while (p < len && (isalnum(s[p]) || s[p] == '_'))
      p++;
    if (p > p1 && (p != p1 + 6 || strncmp(s+p1, "public", 6) != 0)) {
      // XXX private or protected inheritance?
      CxxClass *parent = make_class(original.substring(p1, p - p1));
      cxxc->add_parent(parent);
    }
  }

  // parse class body
  return parse_class(text, p + 1, original, cxxc);
}

int
CxxInfo::parse_class(const String &text, int p, const String &original,
		     CxxClass *cxx_class)
{
  // parse clean_text
  const char *s = text.data();
  int len = text.length();
  
  while (1) {

    // find first batch
    while (p < len && isspace(s[p]))
      p++;
    int p1 = p;
    while (p < len && s[p] != ';' && s[p] != '(' && s[p] != '{' &&
	   s[p] != '}')
      p++;

    //fprintf(stderr, "   %d %c\n", p, s[p]);
    if (p >= len)
      return len;
    else if (s[p] == ';') {
      // uninteresting
      p++;
      continue;
    } else if (s[p] == '}') {
      //fprintf(stderr, "!!!!!!/\n");
      return p + 1;
    }
    else if (s[p] == '{') {
      if (p > p1 + 6 && !cxx_class
	  && (strncmp(s+p1, "class", 5) == 0
	      || strncmp(s+p1, "struct", 6) == 0)) {
	// parse class definition
	p = parse_class_definition(text, p1 + 6, original);
      } else
	p = skip_balanced_braces(text, p);
    } else if (s[p] == '(')
      p = parse_function_definition(text, p1, p, original, cxx_class);
    
  }
}

void
CxxInfo::parse_file(const String &original_text, bool header,
		    String *store_includes)
{
  String clean_text = remove_crap(original_text);
  CxxFunction::parsing_header_file = header;
  parse_class(clean_text, 0, original_text, 0);

  // save initial comments and #defines and #includes for replication.
  // Also skip over 'CLICK_CXX_whatever', enum definitions, typedefs,
  // and 'extern "C" { }' blocks enclosing headers only.
  // XXX Should save up to an arbitrary comment or something
  if (store_includes) {
    const char *s = clean_text.data();
    int p = 0;
    int len = clean_text.length();
    while (1) {
      while (p < len && isspace(s[p]))
	p++;

      if (p < len && s[p] == ';') {
	// mop up stray semicolons
	p++;
	
      } else if (p + 7 < len && memcmp(s + p, "extern", 6) == 0
		 && isspace(s[p+6])) {
	// include 'extern ["C"] { -HEADERS- }'
	int p1 = p + 6;
	while (p1 < len && (isspace(s[p1]) || s[p1] == '$'))
	  p1++;
	if (p1 >= len || s[p1] != '{')
	  break;
	for (p1++; p1 < len && isspace(s[p1]); p1++)
	  /* nada */;
	if (p1 >= len || s[p1] != '}')
	  break;
	p = p1 + 1;
	
      } else if (p + 5 < len && memcmp(s + p, "enum", 4) == 0
		 && isspace(s[p+4])) {
	// include 'enum [IDENTIFIER] { ... }'
	int p1 = p + 5;
	while (p1 < len && isspace(s[p1]))
	  p1++;
	if (p1 < len && (isalnum(s[p1]) || s[p1] == '_')) {
	  while (p1 < len && (isalnum(s[p1]) || s[p1] == '_'))
	    p1++;
	  while (p1 < len && isspace(s[p1]))
	    p1++;
	}
	if (p1 >= len || s[p1] != '{')
	  break;
	for (p1++; p1 < len && s[p1] != '}'; p1++)
	  /* nada */;
	if (p1 >= len)
	  break;
	p = p1 + 1;
	
      } else if (p + 8 < len && memcmp(s + p, "typedef", 7) == 0
		 && isspace(s[p+7])) {
	// include typedefs
	for (p += 8; p < len && s[p] != ';'; p++)
	  /* nada */;
	
      } else if (p + 9 < len && memcmp(s + p, "CLICK_CXX", 9) == 0) {
	// include 'CLICK_CXX' (used in <click/cxxprotect.h>)
	for (p += 9; p < len && (isalnum(s[p]) || s[p] == '_'); p++)
	  /* nada */;
	
      } else
	break;
    }
    *store_includes = original_text.substring(0, p);
  }
}

// Vector template instantiation
#include <click/vector.cc>
template class Vector<CxxFunction>;


syntax highlighted by Code2HTML, v. 0.9.1