Skip to content

Commit

Permalink
Add some operators (#45)
Browse files Browse the repository at this point in the history
As part of #21, this PR
adds binary operators that are either symbols or single barewords. I
couldn't figure out an easy way to handle operators that span multiple
tokens, such as `starts-with`. I also didn't add `not`.

I threw in some code for `++=` but no tests. I assume we'll get to that
once we implement typechecking for assignment oeprators, which this PR
doesn't do.
  • Loading branch information
ysthakur authored Jan 12, 2025
1 parent 8255e9e commit 2984e62
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 29 deletions.
2 changes: 2 additions & 0 deletions src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ pub enum Token {
GreaterThanEqual,
#[token(">")]
GreaterThan,
#[token("++=")]
PlusPlusEquals,
#[token("++")]
PlusPlus,
#[token("+=")]
Expand Down
47 changes: 34 additions & 13 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,34 @@ pub enum AstNode {
Null,

// Operators
Pow,
Multiply,
Divide,
FloorDiv,
Modulo,
Plus,
Minus,
Equal,
NotEqual,
LessThan,
GreaterThan,
LessThanOrEqual,
GreaterThanOrEqual,
Plus,
RegexMatch,
NotRegexMatch,
In,
Append,
Minus,
Multiply,
Divide,
// Modulo,
And,
Xor,
Or,
Pow,

// Assignments
Assignment,
AddAssignment,
SubtractAssignment,
MultiplyAssignment,
DivideAssignment,
// TODO: append assignment ++=
AppendAssignment,

// Statements
Let {
Expand Down Expand Up @@ -198,22 +203,27 @@ impl AstNode {
pub fn precedence(&self) -> usize {
match self {
AstNode::Pow => 100,
AstNode::Multiply | AstNode::Divide => 95,
//AstNode::Modulo => 95,
AstNode::Multiply | AstNode::Divide | AstNode::FloorDiv | AstNode::Modulo => 95,
AstNode::Plus | AstNode::Minus => 90,
AstNode::LessThan
| AstNode::LessThanOrEqual
| AstNode::GreaterThan
| AstNode::GreaterThanOrEqual
| AstNode::Equal
| AstNode::NotEqual => 80,
| AstNode::NotEqual
| AstNode::RegexMatch
| AstNode::NotRegexMatch
| AstNode::In
| AstNode::Append => 80,
AstNode::And => 50,
AstNode::Xor => 45,
AstNode::Or => 40,
AstNode::Assignment
| AstNode::AddAssignment
| AstNode::SubtractAssignment
| AstNode::MultiplyAssignment
| AstNode::DivideAssignment => ASSIGNMENT_PRECEDENCE,
| AstNode::DivideAssignment
| AstNode::AppendAssignment => ASSIGNMENT_PRECEDENCE,
_ => 0,
}
}
Expand Down Expand Up @@ -671,20 +681,27 @@ impl Parser {
Token::Dash => self.advance_node(AstNode::Minus, span),
Token::Asterisk => self.advance_node(AstNode::Multiply, span),
Token::ForwardSlash => self.advance_node(AstNode::Divide, span),
Token::ForwardSlashForwardSlash => self.advance_node(AstNode::FloorDiv, span),
Token::LessThan => self.advance_node(AstNode::LessThan, span),
Token::LessThanEqual => self.advance_node(AstNode::LessThanOrEqual, span),
Token::GreaterThan => self.advance_node(AstNode::GreaterThan, span),
Token::GreaterThanEqual => self.advance_node(AstNode::GreaterThanOrEqual, span),
Token::EqualsEquals => self.advance_node(AstNode::Equal, span),
Token::ExclamationEquals => self.advance_node(AstNode::NotEqual, span),
Token::EqualsTilde => self.advance_node(AstNode::RegexMatch, span),
Token::ExclamationTilde => self.advance_node(AstNode::NotRegexMatch, span),
Token::AsteriskAsterisk => self.advance_node(AstNode::Pow, span),
Token::Equals => self.advance_node(AstNode::Assignment, span),
Token::PlusEquals => self.advance_node(AstNode::AddAssignment, span),
Token::DashEquals => self.advance_node(AstNode::SubtractAssignment, span),
Token::AsteriskEquals => self.advance_node(AstNode::MultiplyAssignment, span),
Token::ForwardSlashEquals => self.advance_node(AstNode::DivideAssignment, span),
Token::PlusPlusEquals => self.advance_node(AstNode::AppendAssignment, span),
Token::Bareword => match self.compiler.get_span_contents_manual(span.start, span.end) {
b"mod" => self.advance_node(AstNode::Modulo, span),
b"in" => self.advance_node(AstNode::In, span),
b"and" => self.advance_node(AstNode::And, span),
b"xor" => self.advance_node(AstNode::Xor, span),
b"or" => self.advance_node(AstNode::Or, span),
op => self.error(format!(
"Unknown operator: '{}'",
Expand Down Expand Up @@ -1271,21 +1288,25 @@ impl Parser {
| Token::Dash
| Token::Asterisk
| Token::ForwardSlash
| Token::ForwardSlashForwardSlash
| Token::LessThan
| Token::LessThanEqual
| Token::GreaterThan
| Token::GreaterThanEqual
| Token::EqualsEquals
| Token::ExclamationEquals
| Token::EqualsTilde
| Token::ExclamationTilde
| Token::AsteriskAsterisk
| Token::Equals
| Token::PlusEquals
| Token::DashEquals
| Token::AsteriskEquals
| Token::ForwardSlashEquals => true,
| Token::ForwardSlashEquals
| Token::PlusPlusEquals => true,
Token::Bareword => {
let op = self.compiler.get_span_contents_manual(span.start, span.end);
op == b"and" || op == b"or"
op == b"mod" || op == b"in" || op == b"and" || op == b"xor" || op == b"or"
}
_ => false,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
source: src/test.rs
expression: evaluate_example(path)
input_file: tests/binary_ops_exact.nu
snapshot_kind: text
---
==== COMPILER ====
0: Int (0 to 1) "1"
Expand All @@ -26,9 +25,19 @@ snapshot_kind: text
18: And (44 to 47)
19: False (48 to 53)
20: BinaryOp { lhs: NodeId(17), op: NodeId(18), rhs: NodeId(19) } (39 to 53)
21: Block(BlockId(0)) (0 to 54)
21: String (54 to 59) ""foo""
22: RegexMatch (60 to 62)
23: String (63 to 68) "".*o""
24: BinaryOp { lhs: NodeId(21), op: NodeId(22), rhs: NodeId(23) } (54 to 68)
25: Int (69 to 70) "1"
26: In (71 to 73)
27: Int (75 to 76) "1"
28: Int (78 to 79) "2"
29: List([NodeId(27), NodeId(28)]) (74 to 79)
30: BinaryOp { lhs: NodeId(25), op: NodeId(26), rhs: NodeId(29) } (69 to 79)
31: Block(BlockId(0)) (0 to 81)
==== SCOPE ====
0: Frame Scope, node_id: NodeId(21) (empty)
0: Frame Scope, node_id: NodeId(31) (empty)
==== TYPES ====
0: int
1: forbidden
Expand All @@ -51,4 +60,14 @@ snapshot_kind: text
18: forbidden
19: bool
20: bool
21: bool
21: string
22: forbidden
23: string
24: bool
25: int
26: forbidden
27: int
28: int
29: list<int>
30: bool
31: bool
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
source: src/test.rs
expression: evaluate_example(path)
input_file: tests/binary_ops_mismatch.nu
snapshot_kind: text
---
==== COMPILER ====
0: String (0 to 3) ""a""
Expand All @@ -17,9 +16,13 @@ snapshot_kind: text
9: And (26 to 29)
10: String (30 to 33) ""a""
11: BinaryOp { lhs: NodeId(8), op: NodeId(9), rhs: NodeId(10) } (21 to 33)
12: Block(BlockId(0)) (0 to 34)
12: True (34 to 38)
13: NotRegexMatch (39 to 41)
14: String (42 to 48) ""true""
15: BinaryOp { lhs: NodeId(12), op: NodeId(13), rhs: NodeId(14) } (34 to 48)
16: Block(BlockId(0)) (0 to 49)
==== SCOPE ====
0: Frame Scope, node_id: NodeId(12) (empty)
0: Frame Scope, node_id: NodeId(16) (empty)
==== TYPES ====
0: string
1: error
Expand All @@ -33,8 +36,13 @@ snapshot_kind: text
9: error
10: string
11: error
12: error
12: bool
13: error
14: string
15: error
16: error
==== TYPE ERRORS ====
Error (NodeId 1): type mismatch: unsupported addition between string and float
Error (NodeId 5): type mismatch: unsupported append between string and float
Error (NodeId 9): type mismatch: unsupported logical operation between bool and string
Error (NodeId 13): type mismatch: unsupported string operation between bool and string
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
source: src/test.rs
expression: evaluate_example(path)
input_file: tests/binary_ops_subtypes.nu
snapshot_kind: text
---
==== COMPILER ====
0: Int (0 to 1) "1"
Expand Down Expand Up @@ -48,9 +47,19 @@ snapshot_kind: text
40: List([NodeId(39)]) (87 to 91)
41: List([NodeId(40)]) (86 to 92)
42: BinaryOp { lhs: NodeId(37), op: NodeId(38), rhs: NodeId(41) } (73 to 92)
43: Block(BlockId(0)) (0 to 93)
43: Int (94 to 95) "1"
44: In (96 to 98)
45: Float (100 to 103) "1.0"
46: Int (105 to 106) "1"
47: List([NodeId(45), NodeId(46)]) (99 to 106)
48: BinaryOp { lhs: NodeId(43), op: NodeId(44), rhs: NodeId(47) } (94 to 106)
49: Float (108 to 111) "2.3"
50: Modulo (112 to 115)
51: Int (116 to 117) "1"
52: BinaryOp { lhs: NodeId(49), op: NodeId(50), rhs: NodeId(51) } (108 to 117)
53: Block(BlockId(0)) (0 to 118)
==== SCOPE ====
0: Frame Scope, node_id: NodeId(43) (empty)
0: Frame Scope, node_id: NodeId(53) (empty)
==== TYPES ====
0: int
1: forbidden
Expand Down Expand Up @@ -95,4 +104,14 @@ snapshot_kind: text
40: list<float>
41: list<list<float>>
42: list<list<number>>
43: list<list<number>>
43: int
44: forbidden
45: float
46: int
47: list<number>
48: bool
49: float
50: forbidden
51: int
52: float
53: float
41 changes: 38 additions & 3 deletions src/typechecker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,12 @@ impl<'a> Typechecker<'a> {
Some(Type::Bool)
}
}
AstNode::Minus | AstNode::Multiply | AstNode::Divide | AstNode::Pow => {
AstNode::Minus
| AstNode::Multiply
| AstNode::Divide
| AstNode::FloorDiv
| AstNode::Modulo
| AstNode::Pow => {
let type_id = check_numeric_op(lhs_type, rhs_type);

if type_id == Type::Unknown {
Expand All @@ -525,7 +530,36 @@ impl<'a> Typechecker<'a> {
Some(type_id)
}
}
AstNode::And | AstNode::Or => match (lhs_type, rhs_type) {
AstNode::RegexMatch | AstNode::NotRegexMatch => match (lhs_type, rhs_type) {
(Type::String | Type::Any, Type::String | Type::Any) => Some(Type::Bool),
_ => {
self.binary_op_err("string operation", lhs, op, rhs);
None
}
},
AstNode::In => match rhs_type {
Type::String => match lhs_type {
Type::String | Type::Any => Some(Type::Bool),
_ => {
self.binary_op_err("string operation", lhs, op, rhs);
None
}
},
Type::List(elem_ty) => {
if is_type_compatible(lhs_type, self.types[elem_ty.0]) {
Some(Type::Bool)
} else {
self.binary_op_err("list operation", lhs, op, rhs);
None
}
}
Type::Any => Some(Type::Bool),
_ => {
self.binary_op_err("list/string operation", lhs, op, rhs);
None
}
},
AstNode::And | AstNode::Xor | AstNode::Or => match (lhs_type, rhs_type) {
(Type::Bool, Type::Bool) => Some(Type::Bool),
_ => {
self.binary_op_err("logical operation", lhs, op, rhs);
Expand Down Expand Up @@ -576,7 +610,8 @@ impl<'a> Typechecker<'a> {
| AstNode::AddAssignment
| AstNode::SubtractAssignment
| AstNode::MultiplyAssignment
| AstNode::DivideAssignment => Some(Type::None),
| AstNode::DivideAssignment
| AstNode::AppendAssignment => Some(Type::None),
_ => panic!("internal error: unsupported node passed as binary op: {op:?}"),
};

Expand Down
2 changes: 2 additions & 0 deletions tests/binary_ops_exact.nu
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
1 + 1
1.0 + 1.0
true and false
"foo" =~ ".*o"
1 in [1, 2]
1 change: 1 addition & 0 deletions tests/binary_ops_mismatch.nu
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"a" + 1.0
"a" ++ 1.0
true and "a"
true !~ "true"
4 changes: 3 additions & 1 deletion tests/binary_ops_subtypes.nu
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
[1] ++ 1.0
[1.0 1] ++ "a"
[[1] [2]] ++ [[3]]
[[1] [2]] ++ [[3.0]]
[[1] [2]] ++ [[3.0]]
1 in [1.0, 1]
2.3 mod 1

0 comments on commit 2984e62

Please sign in to comment.