Skip to content

Commit

Permalink
Add support for unsigned right shifting
Browse files Browse the repository at this point in the history
This adds the new >>> operator, used for performing an unsigned right
shift. This is needed for algorithms that perform right shifts of
integers, but expect the integer to be an unsigned integer. One such
example is SHA512, which relies on both unsigned integers and right
shifting.

Changelog: added
  • Loading branch information
yorickpeterse committed Oct 17, 2022
1 parent a169a55 commit 4325ed9
Show file tree
Hide file tree
Showing 13 changed files with 178 additions and 9 deletions.
31 changes: 29 additions & 2 deletions ast/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ pub enum TokenKind {
Pub,
Recover,
Ref,
Replace,
Return,
SelfObject,
Shl,
Expand All @@ -159,7 +160,6 @@ pub enum TokenKind {
StringText,
Sub,
SubAssign,
Replace,
Throw,
Throws,
Trait,
Expand All @@ -168,6 +168,8 @@ pub enum TokenKind {
TryPanic,
Uni,
UnicodeEscape,
UnsignedShr,
UnsignedShrAssign,
When,
While,
Whitespace,
Expand Down Expand Up @@ -248,6 +250,8 @@ impl TokenKind {
TokenKind::ShlAssign => "a '<<='",
TokenKind::Shr => "a '>>'",
TokenKind::ShrAssign => "a '>>='",
TokenKind::UnsignedShr => "a '>>>'",
TokenKind::UnsignedShrAssign => "a '>>>='",
TokenKind::SingleStringClose => "a '''",
TokenKind::SingleStringOpen => "a '''",
TokenKind::Static => "the 'static' keyword",
Expand Down Expand Up @@ -361,6 +365,7 @@ impl Token {
| TokenKind::BitXor
| TokenKind::Shl
| TokenKind::Shr
| TokenKind::UnsignedShr
| TokenKind::Lt
| TokenKind::Le
| TokenKind::Gt
Expand Down Expand Up @@ -890,7 +895,14 @@ impl Lexer {

fn greater(&mut self) -> Token {
if self.next_byte() == GREATER {
return self.double_operator(TokenKind::Shr, TokenKind::ShrAssign);
return if self.peek(2) == GREATER {
self.triple_operator(
TokenKind::UnsignedShr,
TokenKind::UnsignedShrAssign,
)
} else {
self.double_operator(TokenKind::Shr, TokenKind::ShrAssign)
};
}

self.operator(TokenKind::Gt, TokenKind::Ge, self.position)
Expand Down Expand Up @@ -1252,6 +1264,18 @@ impl Lexer {
self.next_byte() == LOWER_U && self.peek(2) == CURLY_OPEN
}

fn triple_operator(
&mut self,
kind: TokenKind,
assign_kind: TokenKind,
) -> Token {
let start = self.position;

self.position += 2;

self.operator(kind, assign_kind, start)
}

fn double_operator(
&mut self,
kind: TokenKind,
Expand Down Expand Up @@ -1476,6 +1500,7 @@ mod tests {
assert!(tok(TokenKind::Ge, "", 1..=1, 1..=1).is_operator());
assert!(tok(TokenKind::Eq, "", 1..=1, 1..=1).is_operator());
assert!(tok(TokenKind::Ne, "", 1..=1, 1..=1).is_operator());
assert!(tok(TokenKind::UnsignedShr, "", 1..=1, 1..=1).is_operator());
}

#[test]
Expand Down Expand Up @@ -2008,6 +2033,8 @@ mod tests {
assert_token!(">=", Ge, ">=", 1..=1, 1..=2);
assert_token!(">>", Shr, ">>", 1..=1, 1..=2);
assert_token!(">>=", ShrAssign, ">>=", 1..=1, 1..=3);
assert_token!(">>>", UnsignedShr, ">>>", 1..=1, 1..=3);
assert_token!(">>>=", UnsignedShrAssign, ">>>=", 1..=1, 1..=4);
}

#[test]
Expand Down
5 changes: 3 additions & 2 deletions ast/src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -965,17 +965,18 @@ pub enum OperatorKind {
BitXor,
Div,
Eq,
Gt,
Ge,
Lt,
Gt,
Le,
Lt,
Mod,
Mul,
Ne,
Pow,
Shl,
Shr,
Sub,
UnsignedShr,
}

#[derive(Debug, PartialEq, Eq)]
Expand Down
72 changes: 71 additions & 1 deletion ast/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,7 @@ impl Parser {
TokenKind::Ge => OperatorKind::Ge,
TokenKind::Shl => OperatorKind::Shl,
TokenKind::Shr => OperatorKind::Shr,
TokenKind::UnsignedShr => OperatorKind::UnsignedShr,
TokenKind::BitAnd => OperatorKind::BitAnd,
TokenKind::BitOr => OperatorKind::BitOr,
TokenKind::BitXor => OperatorKind::BitXor,
Expand Down Expand Up @@ -1753,6 +1754,10 @@ impl Parser {
TokenKind::ShrAssign => {
return self.binary_assign_field(start, OperatorKind::Shr)
}
TokenKind::UnsignedShrAssign => {
return self
.binary_assign_field(start, OperatorKind::UnsignedShr)
}
TokenKind::BitOrAssign => {
return self.binary_assign_field(start, OperatorKind::BitOr)
}
Expand Down Expand Up @@ -1855,7 +1860,11 @@ impl Parser {
return self.binary_assign_variable(start, OperatorKind::Shl)
}
TokenKind::ShrAssign => {
return self.binary_assign_variable(start, OperatorKind::Shr)
return self.binary_assign_variable(start, OperatorKind::Shr);
}
TokenKind::UnsignedShrAssign => {
return self
.binary_assign_variable(start, OperatorKind::UnsignedShr);
}
TokenKind::BitOrAssign => {
return self.binary_assign_variable(start, OperatorKind::BitOr)
Expand Down Expand Up @@ -2068,6 +2077,13 @@ impl Parser {
OperatorKind::Shr,
);
}
TokenKind::UnsignedShrAssign => {
return self.binary_assign_setter(
receiver,
name_token,
OperatorKind::UnsignedShr,
);
}
TokenKind::BitOrAssign => {
return self.binary_assign_setter(
receiver,
Expand Down Expand Up @@ -6314,6 +6330,25 @@ mod tests {
}))
);

assert_eq!(
expr("10 >>> 2"),
Expression::Binary(Box::new(Binary {
operator: Operator {
kind: OperatorKind::UnsignedShr,
location: cols(4, 6)
},
left: Expression::Int(Box::new(IntLiteral {
value: "10".to_string(),
location: cols(1, 2)
})),
right: Expression::Int(Box::new(IntLiteral {
value: "2".to_string(),
location: cols(8, 8)
})),
location: cols(1, 8)
}))
);

assert_eq!(
expr("10 & 2"),
Expression::Binary(Box::new(Binary {
Expand Down Expand Up @@ -6748,6 +6783,25 @@ mod tests {
}))
);

assert_eq!(
expr("foo >>>= 10"),
Expression::BinaryAssignVariable(Box::new(BinaryAssignVariable {
variable: Identifier {
name: "foo".to_string(),
location: cols(1, 3)
},
value: Expression::Int(Box::new(IntLiteral {
value: "10".to_string(),
location: cols(10, 11)
})),
operator: Operator {
kind: OperatorKind::UnsignedShr,
location: cols(5, 8)
},
location: cols(1, 11)
}))
);

assert_eq!(
expr("foo &= 10"),
Expression::BinaryAssignVariable(Box::new(BinaryAssignVariable {
Expand Down Expand Up @@ -6936,6 +6990,22 @@ mod tests {
}))
);

assert_eq!(
expr("@foo >>>= 10"),
Expression::BinaryAssignField(Box::new(BinaryAssignField {
field: Field { name: "foo".to_string(), location: cols(1, 4) },
value: Expression::Int(Box::new(IntLiteral {
value: "10".to_string(),
location: cols(11, 12)
})),
operator: Operator {
kind: OperatorKind::UnsignedShr,
location: cols(6, 9)
},
location: cols(1, 12)
}))
);

assert_eq!(
expr("@foo &= 10"),
Expression::BinaryAssignField(Box::new(BinaryAssignField {
Expand Down
5 changes: 5 additions & 0 deletions bytecode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ pub enum Opcode {
IntWrappingAdd,
IntWrappingSub,
IntWrappingMul,
IntUnsignedShr,
}

impl Opcode {
Expand Down Expand Up @@ -272,6 +273,7 @@ impl Opcode {
111 => Opcode::IntWrappingAdd,
112 => Opcode::IntWrappingSub,
113 => Opcode::IntWrappingMul,
114 => Opcode::IntUnsignedShr,
_ => return Err(format!("The opcode {} is invalid", byte)),
};

Expand Down Expand Up @@ -395,6 +397,7 @@ impl Opcode {
Opcode::IntWrappingAdd => 111,
Opcode::IntWrappingSub => 112,
Opcode::IntWrappingMul => 113,
Opcode::IntUnsignedShr => 114,
}
}

Expand Down Expand Up @@ -514,6 +517,7 @@ impl Opcode {
Opcode::IntWrappingAdd => "int_wrapping_add",
Opcode::IntWrappingSub => "int_wrapping_sub",
Opcode::IntWrappingMul => "int_wrapping_mul",
Opcode::IntUnsignedShr => "int_unsigned_shr",
}
}

Expand Down Expand Up @@ -651,6 +655,7 @@ impl Opcode {
Opcode::IntWrappingAdd => 3,
Opcode::IntWrappingSub => 3,
Opcode::IntWrappingMul => 3,
Opcode::IntUnsignedShr => 3,
}
}

Expand Down
21 changes: 19 additions & 2 deletions compiler/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -658,17 +658,18 @@ pub(crate) enum Operator {
BitXor,
Div,
Eq,
Gt,
Ge,
Lt,
Gt,
Le,
Lt,
Mod,
Mul,
Ne,
Pow,
Shl,
Shr,
Sub,
UnsignedShr,
}

impl Operator {
Expand All @@ -691,6 +692,7 @@ impl Operator {
Operator::Ge => ">=",
Operator::Lt => "<",
Operator::Le => "<=",
Operator::UnsignedShr => ">>>",
}
}
}
Expand Down Expand Up @@ -1837,6 +1839,7 @@ impl<'a> LowerToHir<'a> {
ast::OperatorKind::Shl => Operator::Shl,
ast::OperatorKind::Shr => Operator::Shr,
ast::OperatorKind::Sub => Operator::Sub,
ast::OperatorKind::UnsignedShr => Operator::UnsignedShr,
}
}

Expand Down Expand Up @@ -4664,6 +4667,20 @@ mod tests {
);
}

#[test]
fn test_lower_negative_hex_int() {
let hir = lower_expr("fn a { -0x4a3f043013b2c4d1 }").0;

assert_eq!(
hir,
Expression::Int(Box::new(IntLiteral {
value: -0x4a3f043013b2c4d1,
resolved_type: types::TypeRef::Unknown,
location: cols(8, 26)
}))
);
}

#[test]
fn test_lower_float() {
let hir = lower_expr("fn a { 1_0.5 }").0;
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/mir/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,9 @@ impl<'a> DefineConstants<'a> {
hir::Operator::Pow => Some(lhs.pow(rhs as u32)),
hir::Operator::Shl => lhs.checked_shl(rhs as u32),
hir::Operator::Shr => lhs.checked_shr(rhs as u32),
hir::Operator::UnsignedShr => (lhs as u64)
.checked_shr(rhs as u32)
.map(|v| v as i64),
hir::Operator::Sub => lhs.checked_sub(rhs),
_ => None,
};
Expand Down
1 change: 1 addition & 0 deletions compiler/src/type_check/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,7 @@ pub(crate) fn define_builtin_functions(state: &mut State) -> bool {
(Opcode::IntWrappingAdd, int),
(Opcode::IntWrappingSub, int),
(Opcode::IntWrappingMul, int),
(Opcode::IntUnsignedShr, int),
];

let macros = vec![
Expand Down
2 changes: 1 addition & 1 deletion docs/source/getting-started/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ Operators are left-associative. This means `5 + 10 * 2` evaluates to `30`, _not_
`25`. The following binary operators are supported:

`+` , `-` , `/` , `*` , `**` , `%` , `<` , `>` , `<=` , `>=` , `<<` , `>>` , `|`
, `&` , `^` , `==` , `!=`
, `&` , `^` , `==` , `!=`, `>>>`

Inko also supports two logical operators: `and` and `or`. These operators have a
higher precedence than the regular binary operators. This means
Expand Down
8 changes: 7 additions & 1 deletion libstd/src/std/int.inko
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import std::fmt::(Format, Formatter)
import std::hash::(Hash, Hasher)
import std::ops::(
Add, BitAnd, BitOr, BitXor, Divide, Modulo, Multiply, Power,
ShiftLeft, ShiftRight, Subtract
ShiftLeft, ShiftRight, Subtract, UnsignedShiftRight
)
import std::range::(ExclusiveRange, InclusiveRange)
import std::string::(ToString, StringBuffer)
Expand Down Expand Up @@ -406,6 +406,12 @@ impl ShiftRight for Int {
}
}

impl UnsignedShiftRight for Int {
fn pub >>>(other: ref Self) -> Self {
_INKO.int_unsigned_shr(self, other)
}
}

impl Power for Int {
fn pub **(other: ref Self) -> Self {
_INKO.int_pow(self, other)
Expand Down
7 changes: 7 additions & 0 deletions libstd/src/std/ops.inko
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,10 @@ trait pub ShiftRight {
# given object.
fn pub >>(other: ref Self) -> Self
}

# The binary `>>>` operator.
trait pub UnsignedShiftRight {
# Casts `self` to an unsigned integer, shifts it to the right, then returns
# the result as a signed integer.
fn pub >>>(other: ref Self) -> Self
}
Loading

0 comments on commit 4325ed9

Please sign in to comment.