Skip to content

Commit

Permalink
Add missing bitwise ops: xor, unary not (~), and shifts (google#117)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Hittorp authored and adonovan committed Aug 9, 2018
1 parent 878b17a commit 0a5e39a
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 41 deletions.
1 change: 1 addition & 0 deletions cmd/skylark/skylark.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func init() {
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() {
Expand Down
72 changes: 58 additions & 14 deletions doc/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,13 @@ Comments are treated like other white space.
characters are tokens:

```text
+ - * / // %
& | **
+ - * / // % ^
& | ** << >>
&= |=
. , = ; :
( ) [ ] { }
< > >= <= == !=
+= -= *= /= //= %=
+= -= *= /= //= %= ^= <<= >>=
```

*Keywords*: The following tokens are keywords and may not be used as
Expand Down Expand Up @@ -424,8 +425,11 @@ The `/` operator implements real division, and
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.
Expand All @@ -449,6 +453,10 @@ of protocol messages which may contain signed and unsigned 64-bit
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

Expand Down Expand Up @@ -834,6 +842,8 @@ applied to sets. The right operand of the `|` operator may be any
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.
Expand All @@ -844,7 +854,8 @@ A set used in a Boolean context is considered true if it is non-empty.

<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.


Expand Down Expand Up @@ -1632,18 +1643,20 @@ Examples:
### Unary operators

There are three unary operators, all appearing before their operand:
`+`, `-`, and `not`.
`+`, `-`, `~`, and `not`.

```grammar {.good}
UnaryExpr = '+' PrimaryExpr
| '-' PrimaryExpr
| '~' PrimaryExpr
| 'not' Test
.
```

```text
+ number unary positive (int, float)
- number unary negation (int, float)
~ number unary bitwise inversion (int)
not x logical negation (any type)
```

Expand Down Expand Up @@ -1673,9 +1686,18 @@ not "" # True
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

Expand All @@ -1685,11 +1707,13 @@ Skylark has the following binary operators, arranged in order of increasing prec
or
and
not
== != < > <= >= in not in
== != < > <= >= in not in
|
^
&
- +
* / // %
<< >>
- +
* / // %
```

Comparison operators, `in`, and `not in` are non-associative,
Expand All @@ -1704,9 +1728,11 @@ Binop = 'or'
| 'not'
| '==' | '!=' | '<' | '>' | '<=' | '>=' | 'in' | 'not' 'in'
| '|'
| '^'
| '&'
| '-' | '+'
| '*' | '%' | '/' | '//'
| '<<' | '>>'
.
```

Expand Down Expand Up @@ -1805,6 +1831,9 @@ Arithmetic (int or float; result has type float unless both operands have type i
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
Expand All @@ -1824,6 +1853,7 @@ Sets
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
Expand Down Expand Up @@ -1876,12 +1906,26 @@ The result of `set | set` is a new set whose elements are the
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>
Expand Down Expand Up @@ -2421,11 +2465,11 @@ In the Java implementation, targets cannot be dot expressions.
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:
Expand Down
48 changes: 47 additions & 1 deletion eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ func setIndex(fr *Frame, x, y, z Value) error {
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:
Expand All @@ -452,6 +452,10 @@ func Unary(op syntax.Token, x Value) (Value, error) {
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
}
Expand Down Expand Up @@ -766,6 +770,48 @@ func Binary(op syntax.Token, x, y Value) (Value, error) {
}
}

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
Expand Down
1 change: 1 addition & 0 deletions eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func init() {
resolve.AllowNestedDef = true
resolve.AllowFloat = true
resolve.AllowSet = true
resolve.AllowBitwise = true
}

func TestEvalExpr(t *testing.T) {
Expand Down
16 changes: 10 additions & 6 deletions int.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,16 @@ func (i Int) Float() Float {
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 {
Expand Down
Loading

0 comments on commit 0a5e39a

Please sign in to comment.