Add missing bitwise ops: xor, unary not (~), and shifts (#117)

* implement bitwise xor for integers and symmetric difference for sets (^ and ^= operators)
* implement unary ~ operator for integers
* implement left and right bitwise shifts for integers
* enable xor, unary not, and shifts bitwise ops only when -bitwise flag is set
* enable bitwise & and | only when -bitwise flag is set
* add &= and |= operators
diff --git a/cmd/skylark/skylark.go b/cmd/skylark/skylark.go
index 2268ab3..c9270a9 100644
--- a/cmd/skylark/skylark.go
+++ b/cmd/skylark/skylark.go
@@ -32,6 +32,7 @@
 	flag.BoolVar(&resolve.AllowSet, "set", resolve.AllowSet, "allow set data type")
 	flag.BoolVar(&resolve.AllowLambda, "lambda", resolve.AllowLambda, "allow lambda expressions")
 	flag.BoolVar(&resolve.AllowNestedDef, "nesteddef", resolve.AllowNestedDef, "allow nested def statements")
+	flag.BoolVar(&resolve.AllowBitwise, "bitwise", resolve.AllowBitwise, "allow bitwise operations (&, |, ^, ~, <<, and >>)")
 }
 
 func main() {
diff --git a/doc/spec.md b/doc/spec.md
index 8500e30..6489f7c 100644
--- a/doc/spec.md
+++ b/doc/spec.md
@@ -233,12 +233,13 @@
 characters are tokens:
 
 ```text
-+    -    *    /    //   %
-&    |    **
++    -    *    /    //   %   ^
+&    |    **                     <<    >>
+&=   |=
 .    ,    =    ;    :
 (    )    [    ]    {    }
 <    >    >=   <=   ==   !=
-+=   -=   *=   /=   //=  %=
++=   -=   *=   /=   //=  %=  ^=  <<=   >>=
 ```
 
 *Keywords*: The following tokens are keywords and may not be used as
@@ -424,8 +425,11 @@
 yields a `float` result even when its operands are both of type `int`.
 
 Integers, including negative values, may be interpreted as bit vectors.
-The `|` and `&` operators implement bitwise OR and AND, respectively.
-(This feature is not part of the Java implementation.)
+The `|`, `&`, and `^` operators implement bitwise OR, AND, and XOR,
+respectively. The unary `~` operator yields the bitwise inversion of its
+integer argument. The `<<` and `>>` operators shift the first argument
+to the left or right by the number of bits given by the second argument.
+(These features are not part of the Java implementation.)
 
 Any bool, number, or string may be interpreted as an integer by using
 the `int` built-in function.
@@ -449,6 +453,10 @@
 integers.
 The Java implementation currently supports only signed 32-bit integers.
 
+The Go implementation of the Skylark REPL requires the `-bitwise` flag to
+enable support for `&`, `|`, `^`, `~`, `<<`, and `>>` operations.
+The Java implementation does not support `^`, `~`, `<<`, and `>>` operations.
+
 
 ### Floating-point numbers
 
@@ -834,6 +842,8 @@
 iterable value.  The binary `in` operator performs a set membership
 test when its right operand is a set.
 
+The binary `^` operator performs symmetric difference of two sets.
+
 Sets are instantiated by calling the built-in `set` function, which
 returns a set containing all the elements of its optional argument,
 which must be an iterable sequence.  Sets have no literal syntax.
@@ -844,7 +854,8 @@
 
 <b>Implementation note:</b>
 The Go implementation of the Skylark REPL requires the `-set` flag to
-enable support for sets.
+enable support for sets and the `-bitwise` flag to enable support for
+the `&`, `|`, and `^` operators.
 The Java implementation does not support sets.
 
 
@@ -1632,11 +1643,12 @@
 ### Unary operators
 
 There are three unary operators, all appearing before their operand:
-`+`, `-`, and `not`.
+`+`, `-`, `~`, and `not`.
 
 ```grammar {.good}
 UnaryExpr = '+' PrimaryExpr
           | '-' PrimaryExpr
+          | '~' PrimaryExpr
           | 'not' Test
           .
 ```
@@ -1644,6 +1656,7 @@
 ```text
 + number        unary positive          (int, float)
 - number        unary negation          (int, float)
+~ number        unary bitwise inversion (int)
 not x           logical negation        (any type)
 ```
 
@@ -1673,9 +1686,18 @@
 not 0                           # True
 ```
 
+The `~` operator yields the bitwise inversion of its integer argument.
+The bitwise inversion of x is defined as -(x+1).
+
+```python
+~1                              # -2
+~-1                             # 0
+~0                              # -1
+```
+
 <b>Implementation note:</b>
 The parser in the Java implementation of Skylark does not accept unary
-`+` expressions.
+`+` and `~` expressions.
 
 ### Binary operators
 
@@ -1685,11 +1707,13 @@
 or
 and
 not
-==   !=   <   >   <=   >=   in   not in
+==   !=   <    >   <=   >=   in   not in
 |
+^
 &
--   +
-*   /   //   %
+<<   >>
+-    +
+*    /    //   %
 ```
 
 Comparison operators, `in`, and `not in` are non-associative,
@@ -1704,9 +1728,11 @@
       | 'not'
       | '==' | '!=' | '<' | '>' | '<=' | '>=' | 'in' | 'not' 'in'
       | '|'
+      | '^'
       | '&'
       | '-' | '+'
       | '*' | '%' | '/' | '//'
+      | '<<' | '>>'
       .
 ```
 
@@ -1805,6 +1831,9 @@
    number / number              # real division  (result is always a float)
    number // number             # floored division
    number % number              # remainder of floored division
+   number ^ number              # bitwise XOR
+   number << number             # bitwise left shift
+   number >> number             # bitwise right shift
 
 Concatenation
    string + string
@@ -1824,6 +1853,7 @@
       set | set                 # set union
       int & int                 # bitwise intersection (AND)
       set & set                 # set intersection
+      set ^ set                 # set symmetric difference
 ```
 
 The operands of the arithmetic operators `+`, `-`, `*`, `//`, and
@@ -1876,12 +1906,26 @@
 union of the operands, preserving the order of the elements of the
 operands, left before right.
 
+The `^` operator accepts operands of either `int` or `set` type.
+For integers, it yields the bitwise XOR (exclusive OR) of its operands.
+For sets, it yields a new set containing elements of either first or second
+operand but not both (symmetric difference).
+
+The `<<` and `>>` operators require operands of `int` type both. They shift
+the first operand to the left or right by the number of bits given by the
+second operand. It is a dynamic error if the second operand is negative.
+Implementations may impose a limit on the second operand of a left shift.
+
 ```python
 0x12345678 & 0xFF               # 0x00000078
 0x12345678 | 0xFF               # 0x123456FF
+0b01011101 ^ 0b110101101        # 0b111110000
+0b01011101 >> 2                 # 0b010111
+0b01011101 << 2                 # 0b0101110100
 
 set([1, 2]) & set([2, 3])       # set([2])
 set([1, 2]) | set([2, 3])       # set([1, 2, 3])
+set([1, 2]) ^ set([2, 3])       # set([1, 3])
 ```
 
 <b>Implementation note:</b>
@@ -2421,11 +2465,11 @@
 
 An augmented assignment, which has the form `lhs op= rhs` updates the
 variable `lhs` by applying a binary arithmetic operator `op` (one of
-`+`, `-`, `*`, `/`, `//`, `%`) to the previous value of `lhs` and the value
-of `rhs`.
+`+`, `-`, `*`, `/`, `//`, `%`, `&`, `|`, `^`, `<<`, `>>`) to the previous
+value of `lhs` and the value of `rhs`.
 
 ```grammar {.good}
-AssignStmt = Expression ('=' | '+=' | '-=' | '*=' | '/=' | '//=' | '%=') Expression .
+AssignStmt = Expression ('=' | '+=' | '-=' | '*=' | '/=' | '//=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=') Expression .
 ```
 
 The left-hand side must be a simple target:
diff --git a/eval.go b/eval.go
index 7a7444a..f72f8ae 100644
--- a/eval.go
+++ b/eval.go
@@ -437,7 +437,7 @@
 	return nil
 }
 
-// Unary applies a unary operator (+, -, not) to its operand.
+// Unary applies a unary operator (+, -, ~, not) to its operand.
 func Unary(op syntax.Token, x Value) (Value, error) {
 	switch op {
 	case syntax.MINUS:
@@ -452,6 +452,10 @@
 		case Int, Float:
 			return x, nil
 		}
+	case syntax.TILDE:
+		if xint, ok := x.(Int); ok {
+			return xint.Not(), nil
+		}
 	case syntax.NOT:
 		return !x.Truth(), nil
 	}
@@ -766,6 +770,48 @@
 			}
 		}
 
+	case syntax.CIRCUMFLEX:
+		switch x := x.(type) {
+		case Int:
+			if y, ok := y.(Int); ok {
+				return x.Xor(y), nil
+			}
+		case *Set: // symmetric difference
+			if y, ok := y.(*Set); ok {
+				set := new(Set)
+				for _, xelem := range x.elems() {
+					if found, _ := y.Has(xelem); !found {
+						set.Insert(xelem)
+					}
+				}
+				for _, yelem := range y.elems() {
+					if found, _ := x.Has(yelem); !found {
+						set.Insert(yelem)
+					}
+				}
+				return set, nil
+			}
+		}
+
+	case syntax.LTLT, syntax.GTGT:
+		if x, ok := x.(Int); ok {
+			y, err := AsInt32(y)
+			if err != nil {
+				return nil, err
+			}
+			if y < 0 {
+				return nil, fmt.Errorf("negative shift count: %v", y)
+			}
+			if op == syntax.LTLT {
+				if y >= 512 {
+					return nil, fmt.Errorf("shift count too large: %v", y)
+				}
+				return x.Lsh(uint(y)), nil
+			} else {
+				return x.Rsh(uint(y)), nil
+			}
+		}
+
 	default:
 		// unknown operator
 		goto unknown
diff --git a/eval_test.go b/eval_test.go
index 8edd8c0..153f947 100644
--- a/eval_test.go
+++ b/eval_test.go
@@ -25,6 +25,7 @@
 	resolve.AllowNestedDef = true
 	resolve.AllowFloat = true
 	resolve.AllowSet = true
+	resolve.AllowBitwise = true
 }
 
 func TestEvalExpr(t *testing.T) {
diff --git a/int.go b/int.go
index fea670d..8987cf2 100644
--- a/int.go
+++ b/int.go
@@ -134,12 +134,16 @@
 	return Float(f)
 }
 
-func (x Int) Sign() int     { return x.bigint.Sign() }
-func (x Int) Add(y Int) Int { return Int{new(big.Int).Add(x.bigint, y.bigint)} }
-func (x Int) Sub(y Int) Int { return Int{new(big.Int).Sub(x.bigint, y.bigint)} }
-func (x Int) Mul(y Int) Int { return Int{new(big.Int).Mul(x.bigint, y.bigint)} }
-func (x Int) Or(y Int) Int  { return Int{new(big.Int).Or(x.bigint, y.bigint)} }
-func (x Int) And(y Int) Int { return Int{new(big.Int).And(x.bigint, y.bigint)} }
+func (x Int) Sign() int      { return x.bigint.Sign() }
+func (x Int) Add(y Int) Int  { return Int{new(big.Int).Add(x.bigint, y.bigint)} }
+func (x Int) Sub(y Int) Int  { return Int{new(big.Int).Sub(x.bigint, y.bigint)} }
+func (x Int) Mul(y Int) Int  { return Int{new(big.Int).Mul(x.bigint, y.bigint)} }
+func (x Int) Or(y Int) Int   { return Int{new(big.Int).Or(x.bigint, y.bigint)} }
+func (x Int) And(y Int) Int  { return Int{new(big.Int).And(x.bigint, y.bigint)} }
+func (x Int) Xor(y Int) Int  { return Int{new(big.Int).Xor(x.bigint, y.bigint)} }
+func (x Int) Not() Int       { return Int{new(big.Int).Not(x.bigint)} }
+func (x Int) Lsh(y uint) Int { return Int{new(big.Int).Lsh(x.bigint, y)} }
+func (x Int) Rsh(y uint) Int { return Int{new(big.Int).Rsh(x.bigint, y)} }
 
 // Precondition: y is nonzero.
 func (x Int) Div(y Int) Int {
diff --git a/internal/compile/compile.go b/internal/compile/compile.go
index cc8f776..9f153d9 100644
--- a/internal/compile/compile.go
+++ b/internal/compile/compile.go
@@ -74,12 +74,16 @@
 	PERCENT
 	AMP
 	PIPE
+	CIRCUMFLEX
+	LTLT
+	GTGT
 
 	IN
 
 	// unary operators
 	UPLUS  // x UPLUS x
 	UMINUS // x UMINUS -x
+	TILDE  // x TILDE ~x
 
 	NONE  // - NONE None
 	TRUE  // - TRUE True
@@ -142,6 +146,7 @@
 	CALL_KW:     "call_kw ",
 	CALL_VAR:    "call_var",
 	CALL_VAR_KW: "call_var_kw",
+	CIRCUMFLEX:  "circumflex",
 	CJMP:        "cjmp",
 	CONSTANT:    "constant",
 	DUP2:        "dup2",
@@ -152,6 +157,7 @@
 	GE:          "ge",
 	GLOBAL:      "global",
 	GT:          "gt",
+	GTGT:        "gtgt",
 	IN:          "in",
 	INDEX:       "index",
 	INPLACE_ADD: "inplace_add",
@@ -163,6 +169,7 @@
 	LOAD:        "load",
 	LOCAL:       "local",
 	LT:          "lt",
+	LTLT:        "ltlt",
 	MAKEDICT:    "makedict",
 	MAKEFUNC:    "makefunc",
 	MAKELIST:    "makelist",
@@ -188,6 +195,7 @@
 	SLASHSLASH:  "slashslash",
 	SLICE:       "slice",
 	STAR:        "star",
+	TILDE:       "tilde",
 	TRUE:        "true",
 	UMINUS:      "uminus",
 	UNIVERSAL:   "universal",
@@ -207,6 +215,7 @@
 	CALL_KW:     variableStackEffect,
 	CALL_VAR:    variableStackEffect,
 	CALL_VAR_KW: variableStackEffect,
+	CIRCUMFLEX:  -1,
 	CJMP:        -1,
 	CONSTANT:    +1,
 	DUP2:        +2,
@@ -217,6 +226,7 @@
 	GE:          -1,
 	GLOBAL:      +1,
 	GT:          -1,
+	GTGT:        -1,
 	IN:          -1,
 	INDEX:       -1,
 	INPLACE_ADD: -1,
@@ -228,6 +238,7 @@
 	LOAD:        -1,
 	LOCAL:       +1,
 	LT:          -1,
+	LTLT:        -1,
 	MAKEDICT:    +1,
 	MAKEFUNC:    -1,
 	MAKELIST:    variableStackEffect,
@@ -955,7 +966,12 @@
 			syntax.STAR_EQ,
 			syntax.SLASH_EQ,
 			syntax.SLASHSLASH_EQ,
-			syntax.PERCENT_EQ:
+			syntax.PERCENT_EQ,
+			syntax.AMP_EQ,
+			syntax.PIPE_EQ,
+			syntax.CIRCUMFLEX_EQ,
+			syntax.LTLT_EQ,
+			syntax.GTGT_EQ:
 			// augmented assignment: x += y
 
 			var set func()
@@ -1211,6 +1227,8 @@
 			fcomp.emit(UPLUS)
 		case syntax.NOT:
 			fcomp.emit(NOT)
+		case syntax.TILDE:
+			fcomp.emit(TILDE)
 		default:
 			log.Fatalf("%s: unexpected unary op: %s", e.OpPos, e.Op)
 		}
@@ -1435,6 +1453,12 @@
 		fcomp.emit(AMP)
 	case syntax.PIPE:
 		fcomp.emit(PIPE)
+	case syntax.CIRCUMFLEX:
+		fcomp.emit(CIRCUMFLEX)
+	case syntax.LTLT:
+		fcomp.emit(LTLT)
+	case syntax.GTGT:
+		fcomp.emit(GTGT)
 	case syntax.IN:
 		fcomp.emit(IN)
 	case syntax.NOT_IN:
diff --git a/interp.go b/interp.go
index baa83c1..df5c74d 100644
--- a/interp.go
+++ b/interp.go
@@ -131,8 +131,11 @@
 			compile.SLASH,
 			compile.SLASHSLASH,
 			compile.PERCENT,
-			compile.PIPE,
 			compile.AMP,
+			compile.PIPE,
+			compile.CIRCUMFLEX,
+			compile.LTLT,
+			compile.GTGT,
 			compile.IN:
 			binop := syntax.Token(op-compile.PLUS) + syntax.PLUS
 			if op == compile.IN {
@@ -149,8 +152,13 @@
 			stack[sp] = z
 			sp++
 
-		case compile.UPLUS, compile.UMINUS:
-			unop := syntax.Token(op-compile.UPLUS) + syntax.PLUS
+		case compile.UPLUS, compile.UMINUS, compile.TILDE:
+			var unop syntax.Token
+			if op == compile.TILDE {
+				unop = syntax.TILDE
+			} else {
+				unop = syntax.Token(op-compile.UPLUS) + syntax.PLUS
+			}
 			x := stack[sp-1]
 			y, err2 := Unary(unop, x)
 			if err2 != nil {
diff --git a/resolve/resolve.go b/resolve/resolve.go
index b70110e..676b811 100644
--- a/resolve/resolve.go
+++ b/resolve/resolve.go
@@ -90,6 +90,7 @@
 	AllowFloat          = false // allow floating point literals, the 'float' built-in, and x / y
 	AllowSet            = false // allow the 'set' built-in
 	AllowGlobalReassign = false // allow reassignment to globals declared in same file (deprecated)
+	AllowBitwise        = false // allow bitwise operations (&, |, ^, ~, <<, and >>)
 )
 
 // File resolves the specified file.
@@ -404,6 +405,12 @@
 		r.stmts(stmt.False)
 
 	case *syntax.AssignStmt:
+		if !AllowBitwise {
+			switch stmt.Op {
+			case syntax.AMP_EQ, syntax.PIPE_EQ, syntax.CIRCUMFLEX_EQ, syntax.LTLT_EQ, syntax.GTGT_EQ:
+				r.errorf(stmt.OpPos, doesnt+"support bitwise operations")
+			}
+		}
 		r.expr(stmt.RHS)
 		// x += y may be a re-binding of a global variable,
 		// but we cannot tell without knowing the type of x.
@@ -591,12 +598,21 @@
 		}
 
 	case *syntax.UnaryExpr:
+		if !AllowBitwise && e.Op == syntax.TILDE {
+			r.errorf(e.OpPos, doesnt+"support bitwise operations")
+		}
 		r.expr(e.X)
 
 	case *syntax.BinaryExpr:
 		if !AllowFloat && e.Op == syntax.SLASH {
 			r.errorf(e.OpPos, doesnt+"support floating point (use //)")
 		}
+		if !AllowBitwise {
+			switch e.Op {
+			case syntax.AMP, syntax.PIPE, syntax.CIRCUMFLEX, syntax.LTLT, syntax.GTGT:
+				r.errorf(e.OpPos, doesnt+"support bitwise operations")
+			}
+		}
 		r.expr(e.X)
 		r.expr(e.Y)
 
diff --git a/syntax/grammar.txt b/syntax/grammar.txt
index 47f0312..fffc8ad 100644
--- a/syntax/grammar.txt
+++ b/syntax/grammar.txt
@@ -32,7 +32,7 @@
 BreakStmt    = 'break' .
 ContinueStmt = 'continue' .
 PassStmt     = 'pass' .
-AssignStmt   = Expression ('=' | '+=' | '-=' | '*=' | '/=' | '//=' | '%=') Expression .
+AssignStmt   = Expression ('=' | '+=' | '-=' | '*=' | '/=' | '//=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=') Expression .
 ExprStmt     = Expression .
 
 LoadStmt = 'load' '(' string {',' [identifier '='] string} [','] ')' .
@@ -90,6 +90,7 @@
       | 'and'
       | '==' | '!=' | '<' | '>' | '<=' | '>=' | 'in' | 'not' 'in'
       | '|'
+      | '^'
       | '&'
       | '-' | '+'
       | '*' | '%' | '/' | '//'
diff --git a/syntax/parse.go b/syntax/parse.go
index ed8aeaf..b733b8b 100644
--- a/syntax/parse.go
+++ b/syntax/parse.go
@@ -226,7 +226,7 @@
 // small_stmt = RETURN expr?
 //            | PASS | BREAK | CONTINUE
 //            | LOAD ...
-//            | expr ('=' | '+=' | '-=' | '*=' | '/=' | '%=') expr   // assign
+//            | expr ('=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=') expr   // assign
 //            | expr
 func (p *parser) parseSmallStmt() Stmt {
 	switch p.tok {
@@ -250,7 +250,7 @@
 	// Assignment
 	x := p.parseExpr(false)
 	switch p.tok {
-	case EQ, PLUS_EQ, MINUS_EQ, STAR_EQ, SLASH_EQ, SLASHSLASH_EQ, PERCENT_EQ:
+	case EQ, PLUS_EQ, MINUS_EQ, STAR_EQ, SLASH_EQ, SLASHSLASH_EQ, PERCENT_EQ, AMP_EQ, PIPE_EQ, CIRCUMFLEX_EQ, LTLT_EQ, GTGT_EQ:
 		op := p.tok
 		pos := p.nextToken() // consume op
 		rhs := p.parseExpr(false)
@@ -588,7 +588,7 @@
 
 // preclevels groups operators of equal precedence.
 // Comparisons are nonassociative; other binary operators associate to the left.
-// Unary MINUS and PLUS have higher precedence so are handled in parsePrimary.
+// Unary MINUS, unary PLUS, and TILDE have higher precedence so are handled in parsePrimary.
 // See https://github.com/google/skylark/blob/master/doc/spec.md#binary-operators
 var preclevels = [...][]Token{
 	{OR},  // or
@@ -596,7 +596,9 @@
 	{NOT}, // not (unary)
 	{EQL, NEQ, LT, GT, LE, GE, IN, NOT_IN}, // == != < > <= >= in not in
 	{PIPE},                             // |
+	{CIRCUMFLEX},                       // ^
 	{AMP},                              // &
+	{LTLT, GTGT},                       // << >>
 	{MINUS, PLUS},                      // -
 	{STAR, PERCENT, SLASH, SLASHSLASH}, // * % / //
 }
@@ -803,8 +805,8 @@
 			Rparen: rparen,
 		}
 
-	case MINUS, PLUS:
-		// unary minus/plus:
+	case MINUS, PLUS, TILDE:
+		// unary minus/plus/tilde:
 		tok := p.tok
 		pos := p.nextToken()
 		x := p.parsePrimaryWithSuffix()
diff --git a/syntax/scan.go b/syntax/scan.go
index 3a0239a..d38db8d 100644
--- a/syntax/scan.go
+++ b/syntax/scan.go
@@ -44,6 +44,10 @@
 	PERCENT       // %
 	AMP           // &
 	PIPE          // |
+	CIRCUMFLEX    // ^
+	LTLT          // <<
+	GTGT          // >>
+	TILDE         // ~
 	DOT           // .
 	COMMA         // ,
 	EQ            // =
@@ -61,12 +65,17 @@
 	LE            // <=
 	EQL           // ==
 	NEQ           // !=
-	PLUS_EQ       // +=    (keep order consistent with PLUS..PERCENT)
+	PLUS_EQ       // +=    (keep order consistent with PLUS..GTGT)
 	MINUS_EQ      // -=
 	STAR_EQ       // *=
 	SLASH_EQ      // /=
 	SLASHSLASH_EQ // //=
 	PERCENT_EQ    // %=
+	AMP_EQ        // &=
+	PIPE_EQ       // |=
+	CIRCUMFLEX_EQ // ^=
+	LTLT_EQ       // <<=
+	GTGT_EQ       // >>=
 	STARSTAR      // **
 
 	// Keywords
@@ -119,6 +128,10 @@
 	PERCENT:       "%",
 	AMP:           "&",
 	PIPE:          "|",
+	CIRCUMFLEX:    "^",
+	LTLT:          "<<",
+	GTGT:          ">>",
+	TILDE:         "~",
 	DOT:           ".",
 	COMMA:         ",",
 	EQ:            "=",
@@ -142,6 +155,11 @@
 	SLASH_EQ:      "/=",
 	SLASHSLASH_EQ: "//=",
 	PERCENT_EQ:    "%=",
+	AMP_EQ:        "&=",
+	PIPE_EQ:       "|=",
+	CIRCUMFLEX_EQ: "^=",
+	LTLT_EQ:       "<<=",
+	GTGT_EQ:       ">>=",
 	STARSTAR:      "**",
 	AND:           "and",
 	BREAK:         "break",
@@ -624,7 +642,7 @@
 	// other punctuation
 	defer sc.endToken(val)
 	switch c {
-	case '=', '<', '>', '!', '+', '-', '%', '/': // possibly followed by '='
+	case '=', '<', '>', '!', '+', '-', '%', '/', '&', '|', '^', '~': // possibly followed by '='
 		start := sc.pos
 		sc.readRune()
 		if sc.peekRune() == '=' {
@@ -646,14 +664,38 @@
 				return SLASH_EQ
 			case '%':
 				return PERCENT_EQ
+			case '&':
+				return AMP_EQ
+			case '|':
+				return PIPE_EQ
+			case '^':
+				return CIRCUMFLEX_EQ
 			}
 		}
 		switch c {
 		case '=':
 			return EQ
 		case '<':
+			if sc.peekRune() == '<' {
+				sc.readRune()
+				if sc.peekRune() == '=' {
+					sc.readRune()
+					return LTLT_EQ
+				} else {
+					return LTLT
+				}
+			}
 			return LT
 		case '>':
+			if sc.peekRune() == '>' {
+				sc.readRune()
+				if sc.peekRune() == '=' {
+					sc.readRune()
+					return GTGT_EQ
+				} else {
+					return GTGT
+				}
+			}
 			return GT
 		case '!':
 			sc.error(start, "unexpected input character '!'")
@@ -674,20 +716,24 @@
 			return SLASH
 		case '%':
 			return PERCENT
+		case '&':
+			return AMP
+		case '|':
+			return PIPE
+		case '^':
+			return CIRCUMFLEX
+		case '~':
+			return TILDE
 		}
 		panic("unreachable")
 
-	case ':', ';', '|', '&': // single-char tokens (except comma)
+	case ':', ';': // single-char tokens (except comma)
 		sc.readRune()
 		switch c {
 		case ':':
 			return COLON
 		case ';':
 			return SEMI
-		case '|':
-			return PIPE
-		case '&':
-			return AMP
 		}
 		panic("unreachable")
 
diff --git a/testdata/int.sky b/testdata/int.sky
index 13081fb..f964c14 100644
--- a/testdata/int.sky
+++ b/testdata/int.sky
@@ -51,6 +51,18 @@
   assert.eq(x, 5)
   x %= 3
   assert.eq(x, 2)
+  # use resolve.AllowBitwise to enable the ops:
+  x = 2
+  x &= 1
+  assert.eq(x, 0)
+  x |= 2
+  assert.eq(x, 2)
+  x ^= 3
+  assert.eq(x, 1)
+  x <<= 2
+  assert.eq(x, 4)
+  x >>=2
+  assert.eq(x, 1)
 
 compound()
 
@@ -119,12 +131,24 @@
 assert.fails(lambda: int("0123", 0), "invalid literal.*base 0") # valid in Python 2.7
 assert.fails(lambda: int("-0123", 0), "invalid literal.*base 0")
 
-# bitwise union (int|int) and intersection (int&int).
+# bitwise union (int|int), intersection (int&int), XOR (int^int), unary not (~int),
+# left shift (int<<int), and right shift (int>>int).
+# use resolve.AllowBitwise to enable the ops.
 # TODO(adonovan): this is not yet in the Skylark spec,
 # but there is consensus that it should be.
 assert.eq(1|2, 3)
 assert.eq(3|6, 7)
 assert.eq((1|2) & (2|4), 2)
+assert.eq(1 ^ 2, 3)
+assert.eq(2 ^ 2, 0)
+assert.eq(1 | 0 ^ 1, 1) # check | and ^ operators precedence
+assert.eq(~1, -2)
+assert.eq(~-2, 1)
+assert.eq(~0, -1)
+assert.eq(1 << 2, 4)
+assert.eq(2 >> 1, 1)
+assert.fails(lambda: 2 << -1, "negative shift count")
+assert.fails(lambda: 1 << 512, "shift count too large")
 
 # comparisons
 # TODO(adonovan): test: < > == != etc
diff --git a/testdata/set.sky b/testdata/set.sky
index 50e4754..fcaf1fd 100644
--- a/testdata/set.sky
+++ b/testdata/set.sky
@@ -47,7 +47,7 @@
 # set + any is not defined
 assert.fails(lambda: x + y, "unknown.*: set \+ set")
 
-# set | set
+# set | set (use resolve.AllowBitwise to enable it)
 assert.eq(list(set("a".elems()) | set("b".elems())), ["a", "b"])
 assert.eq(list(set("ab".elems()) | set("bc".elems())), ["a", "b", "c"])
 assert.fails(lambda: set() | [], "unknown binary op: set | list")
@@ -66,10 +66,23 @@
 assert.eq(list(x.union((6, 5, 4))), [1, 2, 3, 6, 5, 4])
 assert.fails(lambda: x.union([1, 2, {}]), "unhashable type: dict")
 
-# intersection, set & set
+# intersection, set & set (use resolve.AllowBitwise to enable it)
 assert.eq(list(set("a".elems()) & set("b".elems())), [])
 assert.eq(list(set("ab".elems()) & set("bc".elems())), ["b"])
 
+# symmetric difference, set ^ set (use resolve.AllowBitwise to enable it)
+assert.eq(set([1, 2, 3]) ^ set([4, 5, 3]), set([1, 2, 4, 5]))
+
+def test_set_augmented_assign():
+  x = set([1, 2, 3])
+  x &= set([2, 3])
+  assert.eq(x, set([2, 3]))
+  x |= set([1])
+  assert.eq(x, set([1, 2, 3]))
+  x ^= set([4, 5, 3])
+  assert.eq(x, set([1, 2, 4, 5]))
+test_set_augmented_assign()
+
 # len
 assert.eq(len(x), 3)
 assert.eq(len(y), 3)