Index: generic/tclCompExpr.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclCompExpr.c,v
retrieving revision 1.22
diff -u -r1.22 tclCompExpr.c
--- generic/tclCompExpr.c	6 Apr 2004 22:25:50 -0000	1.22
+++ generic/tclCompExpr.c	21 Sep 2004 07:42:59 -0000
@@ -91,6 +91,8 @@
 #define OP_STREQ	21
 #define OP_STRNEQ	22
 #define OP_EXPON	23
+#define OP_IN		24
+#define OP_NOT_IN	25
 
 /*
  * Table describing the expression operators. Entries in this table must
@@ -134,6 +136,8 @@
     {"eq",  2,  INST_STR_EQ},
     {"ne",  2,  INST_STR_NEQ},
     {"**",  2,	INST_EXPON},
+    {"in",  2,  INST_LIST_IN},
+    {"ni", 2,  INST_LIST_NOT_IN},
     {NULL}
 };
 
Index: generic/tclCompile.h
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclCompile.h,v
retrieving revision 1.47
diff -u -r1.47 tclCompile.h
--- generic/tclCompile.h	3 Jul 2004 02:03:36 -0000	1.47
+++ generic/tclCompile.h	21 Sep 2004 07:42:59 -0000
@@ -544,8 +544,12 @@
 
 #define INST_START_CMD                  105
 
+/* TIP #201 - expr 'in', and 'ni' operators */
+#define INST_LIST_IN                    106
+#define INST_LIST_NOT_IN                107
+
 /* The last opcode */
-#define LAST_INST_OPCODE		105
+#define LAST_INST_OPCODE		107
 
 /*
  * Table describing the Tcl bytecode instructions: their name (for
Index: generic/tclExecute.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclExecute.c,v
retrieving revision 1.149
diff -u -r1.149 tclExecute.c
--- generic/tclExecute.c	18 Sep 2004 19:24:53 -0000	1.149
+++ generic/tclExecute.c	21 Sep 2004 07:43:00 -0000
@@ -2970,6 +2970,76 @@
      * ---------------------------------------------------------
      */
 
+    case INST_LIST_IN:
+    case INST_LIST_NOT_IN:
+    {
+	/*
+	 *
+	 */
+
+	int iResult;
+	Tcl_Obj *valuePtr, *value2Ptr;
+	char *s1, *s2;
+	int s1len, s2len, llen, i;
+	Tcl_Obj *o;
+	
+	value2Ptr = *tosPtr;
+	valuePtr = *(tosPtr - 1);
+
+	s1 = Tcl_GetStringFromObj(valuePtr, &s1len);
+	result = Tcl_ListObjLength(interp, value2Ptr, &llen);
+	if (result != TCL_OK) {
+	    goto checkForCatch;
+	}
+	iResult = 0;
+	if (llen > 0) {
+	    /* An empty list doesn't match anything */
+	    i = 0;
+	    do {
+		result = Tcl_ListObjIndex(interp, value2Ptr, i, &o);
+		if (result != TCL_OK) {
+		    goto checkForCatch;
+		}
+		if (o != NULL) {
+		    s2 = Tcl_GetStringFromObj(o, &s2len);
+		} else {
+		    s2 = "";
+		}
+		if (s1len == s2len) {
+		    iResult = (strcmp(s1, s2) == 0);
+		}
+		i++;
+	    } while (i < llen && iResult == 0);
+	}
+
+	if (*pc == INST_LIST_NOT_IN) {
+	    iResult = !iResult;
+	}
+
+	TRACE(("%.20s %.20s => %d\n", O2S(valuePtr), O2S(value2Ptr), iResult));
+
+	/*
+	 * Peep-hole optimisation: if you're about to jump, do jump
+	 * from here.
+	 */
+
+	pc++;
+#ifndef TCL_COMPILE_DEBUG
+	switch (*pc) {
+	    case INST_JUMP_FALSE1:
+		NEXT_INST_F((iResult? 2 : TclGetInt1AtPtr(pc+1)), 2, 0);
+	    case INST_JUMP_TRUE1:
+		NEXT_INST_F((iResult? TclGetInt1AtPtr(pc+1) : 2), 2, 0);
+	    case INST_JUMP_FALSE4:
+		NEXT_INST_F((iResult? 5 : TclGetInt4AtPtr(pc+1)), 2, 0);
+	    case INST_JUMP_TRUE4:
+		NEXT_INST_F((iResult? TclGetInt4AtPtr(pc+1) : 5), 2, 0);
+	}
+#endif
+	objResultPtr = Tcl_NewIntObj(iResult);
+	NEXT_INST_F(0, 2, 1);
+    }
+
     case INST_STR_EQ:
     case INST_STR_NEQ:
     {
@@ -2988,6 +3058,7 @@
 	     * we don't really have to think hard about equality.
 	     */
 	    iResult = (*pc == INST_STR_EQ);
+
 	} else {
 	    char *s1, *s2;
 	    int s1len, s2len;
Index: generic/tclParseExpr.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclParseExpr.c,v
retrieving revision 1.21
diff -u -r1.21 tclParseExpr.c
--- generic/tclParseExpr.c	6 Apr 2004 22:25:54 -0000	1.21
+++ generic/tclParseExpr.c	21 Sep 2004 07:43:00 -0000
@@ -134,6 +134,12 @@
 #define EXPON		36
 
 /*
+ * 'in' operator
+ */
+#define LISTIN		37
+#define LISTNI		38
+
+/*
  * Mapping from lexemes to strings; used for debugging messages. These
  * entries must match the order and number of the lexeme definitions above.
  */
@@ -144,7 +150,7 @@
     "*", "/", "%", "+", "-",
     "<<", ">>", "<", ">", "<=", ">=", "==", "!=",
     "&", "^", "|", "&&", "||", "?", ":",
-    "!", "~", "eq", "ne", "**"
+    "!", "~", "eq", "ne", "**", "in", "ni"
 };
 
 /*
@@ -169,6 +175,7 @@
 static int		ParseRelationalExpr _ANSI_ARGS_((ParseInfo *infoPtr));
 static int		ParseShiftExpr _ANSI_ARGS_((ParseInfo *infoPtr));
 static int		ParseExponentialExpr _ANSI_ARGS_((ParseInfo *infoPtr));
+static int		ParseInExpr _ANSI_ARGS_((ParseInfo *infoPtr));
 static int		ParseUnaryExpr _ANSI_ARGS_((ParseInfo *infoPtr));
 static void		PrependSubExprTokens _ANSI_ARGS_((CONST char *op,
 				int opBytes, CONST char *src, int srcBytes,
@@ -1031,7 +1038,7 @@
  * ParseExponentialExpr --
  *
  *	This procedure parses a Tcl exponential expression:
- *	exponentialExpr ::= unaryExpr {'**' unaryExpr}
+ *	exponentialExpr ::= inExpr {'**' inExpr}
  *
  * Results:
  *	The return value is TCL_OK on a successful parse and TCL_ERROR
@@ -1059,7 +1066,7 @@
     srcStart = infoPtr->start;
     firstIndex = parsePtr->numTokens;
 
-    code = ParseUnaryExpr(infoPtr);
+    code = ParseInExpr(infoPtr);
     if (code != TCL_OK) {
 	return code;
     }
@@ -1091,6 +1098,68 @@
 /*
  *----------------------------------------------------------------------
  *
+ * ParseInExpr --
+ *
+ *	This procedure parses a Tcl in expression:
+ *	inExpr ::= unaryExpr 'in' unaryExpr | unaryExpr
+ *
+ * Results:
+ *	The return value is TCL_OK on a successful parse and TCL_ERROR
+ *	on failure. If TCL_ERROR is returned, then the interpreter's result
+ *	contains an error message.
+ *
+ * Side effects:
+ *	If there is insufficient space in parsePtr to hold all the
+ *	information about the subexpression, then additional space is
+ *	malloc-ed.
+ *
+ *----------------------------------------------------------------------
+ */
+static int
+ParseInExpr(infoPtr)
+    ParseInfo *infoPtr;		/* Holds the parse state for the
+				 * expression being parsed. */
+{
+    Tcl_Parse *parsePtr = infoPtr->parsePtr;
+    int firstIndex, lexeme, code;
+    CONST char *srcStart, *operator;
+
+    HERE("inExpr", 13);
+    srcStart = infoPtr->start;
+    firstIndex = parsePtr->numTokens;
+
+    code = ParseUnaryExpr(infoPtr);
+    if (code != TCL_OK) {
+	return code;
+    }
+
+    lexeme = infoPtr->lexeme;
+    while (lexeme == LISTIN || lexeme == LISTNI) {
+	operator = infoPtr->start;
+	code = GetLexeme(infoPtr);	/* skip over the operator */
+	if (code != TCL_OK) {
+	    return code;
+	}
+	code = ParseUnaryExpr(infoPtr);
+	if (code != TCL_OK) {
+	    return code;
+	}
+
+	/*
+	 * Generate tokens for the subexpression and 'in' operator.
+	 */
+
+	PrependSubExprTokens(operator, 2, srcStart,
+		(infoPtr->prevEnd - srcStart), firstIndex, infoPtr);
+	lexeme = infoPtr->lexeme;
+    }
+    return TCL_OK;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
  * ParseUnaryExpr --
  *
  *	This procedure parses a Tcl unary expression:
@@ -1118,7 +1187,7 @@
     int firstIndex, lexeme, code;
     CONST char *srcStart, *operator;
 
-    HERE("unaryExpr", 13);
+    HERE("unaryExpr", 14);
     srcStart = infoPtr->start;
     firstIndex = parsePtr->numTokens;
     
@@ -1188,7 +1257,7 @@
      * We simply recurse on parenthesized subexpressions.
      */
 
-    HERE("primaryExpr", 14);
+    HERE("primaryExpr", 15);
     lexeme = infoPtr->lexeme;
     if (lexeme == OPEN_PAREN) {
 	code = GetLexeme(infoPtr); /* skip over the '(' */
@@ -1855,30 +1924,7 @@
 	    infoPtr->lexeme = BIT_NOT;
 	    return TCL_OK;
 
-	case 'e':
-	    if ((src[1] == 'q') && ((infoPtr->lastChar - src) > 1)) {
-		infoPtr->lexeme = STREQ;
-		infoPtr->size = 2;
-		infoPtr->next = src+2;
-		parsePtr->term = infoPtr->next;
-		return TCL_OK;
-	    } else {
-		goto checkFuncName;
-	    }
-
-	case 'n':
-	    if ((src[1] == 'e') && ((infoPtr->lastChar - src) > 1)) {
-		infoPtr->lexeme = STRNEQ;
-		infoPtr->size = 2;
-		infoPtr->next = src+2;
-		parsePtr->term = infoPtr->next;
-		return TCL_OK;
-	    } else {
-		goto checkFuncName;
-	    }
-
 	default:
-	checkFuncName:
 	    length = (infoPtr->lastChar - src);
 	    if (Tcl_UtfCharComplete(src, length)) {
 		offset = Tcl_UtfToUniChar(src, &ch);
@@ -1891,7 +1937,7 @@
 	    c = UCHAR(ch);
 	    if (isalpha(UCHAR(c))) {	/* INTL: ISO only. */
 		infoPtr->lexeme = FUNC_NAME;
-		while (isalnum(UCHAR(c)) || (c == '_')) { /* INTL: ISO only. */
+		while (isalpha(UCHAR(c))) { /* INTL: ISO only. */
 		    src += offset; length -= offset;
 		    if (Tcl_UtfCharComplete(src, length)) {
 			offset = Tcl_UtfToUniChar(src, &ch);
@@ -1907,7 +1953,8 @@
 		infoPtr->next = src;
 		parsePtr->term = infoPtr->next;
 		/*
-		 * Check for boolean literals (true, false, yes, no, on, off)
+		 * Check for boolean literals (true, false, yes, no, on, off),
+		 * and alphabetic operators (eq, ne, in, ni).
 		 */
 		switch (infoPtr->start[0]) {
 		case 'f':
@@ -1917,12 +1964,32 @@
 			return TCL_OK;
 		    }
 		    break;
-		case 'n':
+		case 'e':
 		    if (infoPtr->size == 2 &&
-			strncmp("no", infoPtr->start, 2) == 0) {
-			infoPtr->lexeme = LITERAL;
+			strncmp("eq", infoPtr->start, 2) == 0) {
+			infoPtr->lexeme = STREQ;
+			return TCL_OK;
+		    }
+		case 'i':
+		    if (infoPtr->size == 2 &&
+			strncmp("in", infoPtr->start, 2) == 0) {
+			infoPtr->lexeme = LISTIN;
 			return TCL_OK;
 		    }
+		case 'n':
+		    if (infoPtr->size == 2) {
+			switch (infoPtr->start[1]) {
+			case 'e': /* 'ne' */
+			    infoPtr->lexeme = STRNEQ;
+			    return TCL_OK;
+			case 'i': /* 'ni' */
+			    infoPtr->lexeme = LISTNI;
+			    return TCL_OK;
+			case 'o': /* 'no' */
+			    infoPtr->lexeme = LITERAL;
+			    return TCL_OK;
+			}
+		    }
 		    break;
 		case 'o':
 		    if (infoPtr->size == 3 &&
@@ -1950,6 +2017,21 @@
 		    }
 		    break;
 		}
+		while (isalnum(UCHAR(c)) || (c == '_')) { /* INTL: ISO only. */
+		    src += offset; length -= offset;
+		    if (Tcl_UtfCharComplete(src, length)) {
+			offset = Tcl_UtfToUniChar(src, &ch);
+		    } else {
+			char utfBytes[TCL_UTF_MAX];
+			memcpy(utfBytes, src, (size_t) length);
+			utfBytes[length] = '\0';
+			offset = Tcl_UtfToUniChar(utfBytes, &ch);
+		    }
+		    c = UCHAR(ch);
+		}
+		infoPtr->size = (src - infoPtr->start);
+		infoPtr->next = src;
+		parsePtr->term = infoPtr->next;
 		return TCL_OK;
 	    }
 	    infoPtr->lexeme = UNKNOWN_CHAR;
Index: tests/expr.test
===================================================================
RCS file: /cvsroot/tcl/tcl/tests/expr.test,v
retrieving revision 1.25
diff -u -r1.25 expr.test
--- tests/expr.test	19 Sep 2004 15:03:48 -0000	1.25
+++ tests/expr.test	21 Sep 2004 07:43:00 -0000
@@ -871,6 +871,161 @@
 test expr-24.8 {expr edge cases; shifting} {expr wide(10)<<63} 0
 test expr-24.9 {expr edge cases; shifting} {expr 5>>32} 0
 
+# Tests for the 'in' operator
+test expr-24.1 {'in' operator: first element matches} {
+    expr {"a" in "a b c"}
+} [expr {[lsearch -exact "a b c" a] >=0}]
+
+test expr-24.2 {'in' operator: inner element matches} {
+    expr {"a" in "b a c"}
+} [expr {[lsearch -exact "b a c" a] >= 0}]
+
+test expr-24.3 {'in' operator: last elemet matches} {
+    expr {"a" in "c b a"}
+} [expr {[lsearch -exact "c b a" a] >= 0}]
+
+test expr-24.4 {'in' operator: empty list} {
+    expr {"a" in ""}
+} [expr {[lsearch -exact "" a] >= 0}]
+
+test expr-24.5 {'in' operator: empty string - match} {
+    expr {"" in {a b c ""}}
+} [expr {[lsearch -exact {a b c ""} ""] >= 0}]
+
+test expr-24.6 {'in' operator: empty syting - no match} {
+    expr {"" in "a b c"}
+} [expr {[lsearch -exact "a b c" ""] >= 0}]
+
+test expr-24.7 {'in' operator: both empty} {
+    expr {"" in ""}
+} [expr {[lsearch -exact "" ""] >= 0}]
+
+test expr-24.8 {'in' operator: variables - match} {
+    set a a
+    set b [list a b c]
+    expr {$a in $b}
+} 1
+
+test expr-24.9 {'in' operator: variables - no match} {
+    set a a
+    set b [list b c d]
+    expr {$a in $b}
+} 0
+
+test expr-24.10 {'in' operator: empty list} {
+    expr {"" in "   "}
+} [expr {[lsearch -exact "   " ""] >= 0}]
+
+test expr-24.11 {'in' operator: checking precedence 1} {
+    expr {"1" eq "a" in "a b"}
+} 1
+
+test expr-24.12 {'in' operator: checking precedence 2} {
+    expr {"a" in "a b" eq "1"}
+} 1
+
+test expr-24.13 {'in' operator: checking precedence 3} {
+    expr {- 1 in "1 2"}
+} 0
+
+test expr-24.14 {'in' operator: checking precedence 4} {
+    expr {1 in 1 + 2}
+} 3
+
+test expr-24.15 {'in' operator: improper list 1} {
+    -body {
+	expr {"a" in "a b \""}
+    }
+    -returnCodes 1 
+    -result {unmatched open quote in list}
+}
+
+test expr-24.16 {'in' operator: improper list 2} {
+    -body {
+	expr {"a" in "\{ a"}
+    } 
+    -returnCodes 1 
+    -result {unmatched open brace in list}
+}
+
+test expr-24.17 {'in' operator: nested list 1} {
+    expr { {a b} in {{a b} {b c}} }
+} 1
+
+test expr-24.18 {'in' operator: nested list 2} {
+    expr { {a b} in {{a b} a b } }
+} 1
+
+test expr-24.19 {'in' operator: list construction} {
+    set a "A"
+    set b "A"
+    expr { $a in [list $b $b]}
+} 1
+
+test expr-24.20 {'in' operator: list construction error} \
+    -body {
+	set a "A"
+	set b "A"
+	expr " $a in [list $b $b] "
+    } \
+    -returnCodes 1 \
+    -result {syntax error in expression " A in A A ": variable references require preceding $}
+
+test expr-24.21 {'in' operator: escape characters 1} {
+    set a [list \" \"\"]
+    expr {"\"" in $a}
+} 1
+
+test expr-24.22 {'in' operator: escape characters 2} {
+    set a [list \{ \{]
+    expr {"\{" in $a}
+} 1
+
+test expr-24.23 {'in' operator: escape characters 3} {
+    set a { {} {{}} }
+    expr {{} in $a}
+} 1
+
+test expr-24.24 {'ni' operator: no match} {
+    expr {"a" ni "a b c"}
+} [expr {[lsearch "a b c" a] == -1}]
+
+test expr-24.25 {'ni' operator: match} {
+    expr {"a" ni "b c d"}
+} [expr {[lsearch "b c d" a] == -1}]
+
+test expr-24.26 {'in' oprtator: no space around} {
+    expr {1in1}
+} 1
+
+test expr-25.1 {alpha character after alpha operator: 'eq'} {
+    -body {
+	expr {1eqcos(1)}
+    } 
+    -returnCodes 1
+    -result {syntax error in expression "1eqcos(1)": extra tokens at end of expression}
+}
+
+test expr-25.2 {alpha character after alpha operator: 'ne'} {
+    -body {
+	expr {1necos(1)}
+    }
+    -returnCodes 1
+    -result {syntax error in expression "1necos(1)": extra tokens at end of expression}
+}
+
+test expr-25.3 {alpha character after alpha operator: 'in'} {
+    expr {int(1.5)}
+} 1
+
+test expr-25.4 {alpha character after alpha operator: 'ni'} {
+    -body {
+	expr {1nicos(1)}
+    }
+    -returnCodes 1
+    -result {syntax error in expression "1nicos(1)": extra tokens at end of expression}
+}
+
 # cleanup
 if {[info exists a]} {
     unset a
