diff --git a/src/lexer.rs b/src/lexer.rs index 448641c..3a60a20 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -330,6 +330,8 @@ pub enum Token { GreaterThanEqual, #[token(">")] GreaterThan, + #[token("++=")] + PlusPlusEquals, #[token("++")] PlusPlus, #[token("+=")] diff --git a/src/parser.rs b/src/parser.rs index 950648c..0f0aed8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -74,21 +74,26 @@ 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, @@ -96,7 +101,7 @@ pub enum AstNode { SubtractAssignment, MultiplyAssignment, DivideAssignment, - // TODO: append assignment ++= + AppendAssignment, // Statements Let { @@ -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, } } @@ -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: '{}'", @@ -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, } diff --git a/src/snapshots/new_nu_parser__test__node_output@binary_ops_exact.nu.snap b/src/snapshots/new_nu_parser__test__node_output@binary_ops_exact.nu.snap index cb8c98e..dae92fe 100644 --- a/src/snapshots/new_nu_parser__test__node_output@binary_ops_exact.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@binary_ops_exact.nu.snap @@ -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" @@ -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 @@ -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 +30: bool +31: bool diff --git a/src/snapshots/new_nu_parser__test__node_output@binary_ops_mismatch.nu.snap b/src/snapshots/new_nu_parser__test__node_output@binary_ops_mismatch.nu.snap index 44a614a..bfbeddf 100644 --- a/src/snapshots/new_nu_parser__test__node_output@binary_ops_mismatch.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@binary_ops_mismatch.nu.snap @@ -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"" @@ -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 @@ -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 diff --git a/src/snapshots/new_nu_parser__test__node_output@binary_ops_subtypes.nu.snap b/src/snapshots/new_nu_parser__test__node_output@binary_ops_subtypes.nu.snap index 180d535..b97329d 100644 --- a/src/snapshots/new_nu_parser__test__node_output@binary_ops_subtypes.nu.snap +++ b/src/snapshots/new_nu_parser__test__node_output@binary_ops_subtypes.nu.snap @@ -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" @@ -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 @@ -95,4 +104,14 @@ snapshot_kind: text 40: list 41: list> 42: list> -43: list> +43: int +44: forbidden +45: float +46: int +47: list +48: bool +49: float +50: forbidden +51: int +52: float +53: float diff --git a/src/typechecker.rs b/src/typechecker.rs index ce978b6..7a3717d 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -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 { @@ -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); @@ -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:?}"), }; diff --git a/tests/binary_ops_exact.nu b/tests/binary_ops_exact.nu index d03b191..1977041 100644 --- a/tests/binary_ops_exact.nu +++ b/tests/binary_ops_exact.nu @@ -3,3 +3,5 @@ 1 + 1 1.0 + 1.0 true and false +"foo" =~ ".*o" +1 in [1, 2] diff --git a/tests/binary_ops_mismatch.nu b/tests/binary_ops_mismatch.nu index 6a10011..b355086 100644 --- a/tests/binary_ops_mismatch.nu +++ b/tests/binary_ops_mismatch.nu @@ -1,3 +1,4 @@ "a" + 1.0 "a" ++ 1.0 true and "a" +true !~ "true" diff --git a/tests/binary_ops_subtypes.nu b/tests/binary_ops_subtypes.nu index dfe16b6..dbc773a 100644 --- a/tests/binary_ops_subtypes.nu +++ b/tests/binary_ops_subtypes.nu @@ -4,4 +4,6 @@ [1] ++ 1.0 [1.0 1] ++ "a" [[1] [2]] ++ [[3]] -[[1] [2]] ++ [[3.0]] \ No newline at end of file +[[1] [2]] ++ [[3.0]] +1 in [1.0, 1] +2.3 mod 1