plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-core/tests/parser_test.rs
| 3d6f280 | 1 | #![allow(non_snake_case)] |
| d7e5ff4 | 2 | use plum_core::ast::*; |
| d7e5ff4 | 3 | use plum_core::AstParser; |
| d7e5ff4 | 4 | |
| d7e5ff4 | 5 | fn parse(src: &str) -> Source { |
| d7e5ff4 | 6 | let mut parser = tree_sitter::Parser::new(); |
| d7e5ff4 | 7 | parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap(); |
| d7e5ff4 | 8 | let tree = parser.parse(src, None).unwrap(); |
| d7e5ff4 | 9 | assert!(!tree.root_node().has_error(), "parse error:\n{}", tree.root_node().to_sexp()); |
| d7e5ff4 | 10 | let ap = AstParser::new(src); |
| 3d6f280 | 11 | ap.parseSource(tree.root_node()) |
| d7e5ff4 | 12 | } |
| d7e5ff4 | 13 | |
| 3d6f280 | 14 | fn onlyFn(source: &Source) -> &Fn { |
| d7e5ff4 | 15 | source.items.iter().find_map(|i| match i { Item::Fn(f) => Some(f), _ => None }).expect("expected a Fn item") |
| d7e5ff4 | 16 | } |
| d7e5ff4 | 17 | |
| 3d6f280 | 18 | fn onlyTrait(source: &Source) -> &Trait { |
| 0d64aff | 19 | source.items.iter().find_map(|i| match i { Item::Trait(t) => Some(t), _ => None }).expect("expected a Trait item") |
| 0d64aff | 20 | } |
| 0d64aff | 21 | |
| 3d6f280 | 22 | fn onlyEnum(source: &Source) -> &Enum { |
| fbfbd7b | 23 | source.items.iter().find_map(|i| match i { Item::Enum(e) => Some(e), _ => None }).expect("expected an Enum item") |
| fbfbd7b | 24 | } |
| fbfbd7b | 25 | |
| d7e5ff4 | 26 | #[test] |
| 3d6f280 | 27 | fn nestedClassMethodsBecomeTopLevelFnItemsWithTypeParamSet() { |
| 4df1312 | 28 | let src = "\ |
| 4df1312 | 29 | type Cat = |
| 4df1312 | 30 | name: Str |
| 4df1312 | 31 | |
| 4df1312 | 32 | fun getName(self) -> Str = |
| 4df1312 | 33 | self.name |
| 4df1312 | 34 | |
| 4df1312 | 35 | fun setName(self, n: Str) = |
| 4df1312 | 36 | todo |
| 4df1312 | 37 | "; |
| 4df1312 | 38 | let source = parse(src); |
| 4df1312 | 39 | assert_eq!(source.items.len(), 3, "class + 2 nested methods, in order"); |
| 4df1312 | 40 | let Item::Class(class) = &source.items[0] else { panic!("expected a Class item first") }; |
| 4df1312 | 41 | assert_eq!(class.name, "Cat"); |
| 4df1312 | 42 | let Item::Fn(get_name) = &source.items[1] else { panic!("expected getName immediately after the class") }; |
| 4df1312 | 43 | assert_eq!(get_name.name, "getName"); |
| 4df1312 | 44 | assert_eq!(get_name.type_param, Some("Cat".to_string())); |
| 4df1312 | 45 | let Item::Fn(set_name) = &source.items[2] else { panic!("expected setName immediately after getName") }; |
| 4df1312 | 46 | assert_eq!(set_name.name, "setName"); |
| 4df1312 | 47 | assert_eq!(set_name.type_param, Some("Cat".to_string())); |
| 4df1312 | 48 | } |
| 4df1312 | 49 | |
| 4df1312 | 50 | #[test] |
| 3d6f280 | 51 | fn nestedEnumMethodBecomesTopLevelFnItemWithTypeParamSet() { |
| 4df1312 | 52 | let src = "\ |
| 4df1312 | 53 | enum Step = |
| 4df1312 | 54 | | ReadMin |
| 4df1312 | 55 | | ReadMax |
| 4df1312 | 56 | |
| 4df1312 | 57 | fun toNumber(self) -> Int = |
| 4df1312 | 58 | match self |
| 4df1312 | 59 | ReadMin => 0 |
| 4df1312 | 60 | ReadMax => 1 |
| 4df1312 | 61 | "; |
| 4df1312 | 62 | let source = parse(src); |
| 4df1312 | 63 | assert_eq!(source.items.len(), 2, "enum + 1 nested method, in order"); |
| 4df1312 | 64 | let Item::Enum(e) = &source.items[0] else { panic!("expected an Enum item first") }; |
| 4df1312 | 65 | assert_eq!(e.name, "Step"); |
| 4df1312 | 66 | let Item::Fn(to_number) = &source.items[1] else { panic!("expected toNumber immediately after the enum") }; |
| 4df1312 | 67 | assert_eq!(to_number.name, "toNumber"); |
| 4df1312 | 68 | assert_eq!(to_number.type_param, Some("Step".to_string())); |
| 4df1312 | 69 | } |
| 4df1312 | 70 | |
| fbfbd7b | 71 | #[test] |
| 3d6f280 | 72 | fn enumDiscriminantValuesParseIntoParamsAndVariantValues() { |
| fbfbd7b | 73 | let src = "\ |
| fbfbd7b | 74 | enum Step(n: Int) = |
| fbfbd7b | 75 | | ReadMin(0) |
| fbfbd7b | 76 | | ReadMax(1) |
| fbfbd7b | 77 | "; |
| fbfbd7b | 78 | let source = parse(src); |
| 3d6f280 | 79 | let e = onlyEnum(&source); |
| fbfbd7b | 80 | assert_eq!(e.params, vec![EnumParam { name: "n".to_string(), ty: Type { name: "Int".to_string(), generics: vec![] } }]); |
| fbfbd7b | 81 | assert_eq!(e.variants[0].fields, Vec::<String>::new()); |
| fbfbd7b | 82 | assert_eq!(e.variants[0].values, vec![Expr::Int(0)]); |
| fbfbd7b | 83 | assert_eq!(e.variants[1].fields, Vec::<String>::new()); |
| fbfbd7b | 84 | assert_eq!(e.variants[1].values, vec![Expr::Int(1)]); |
| fbfbd7b | 85 | } |
| fbfbd7b | 86 | |
| 4df1312 | 87 | #[test] |
| 3d6f280 | 88 | fn classAndEnumWithNoNestedMethodsProduceNoExtraFnItems() { |
| 4df1312 | 89 | let src = "\ |
| 4df1312 | 90 | type Dog = |
| 4df1312 | 91 | name: Str |
| 4df1312 | 92 | |
| 4df1312 | 93 | enum Bool = |
| 4df1312 | 94 | | True |
| 4df1312 | 95 | | False |
| 4df1312 | 96 | "; |
| 4df1312 | 97 | let source = parse(src); |
| 4df1312 | 98 | assert_eq!(source.items.len(), 2, "no nested methods means no extra Item::Fn entries"); |
| 4df1312 | 99 | assert!(matches!(source.items[0], Item::Class(_))); |
| 4df1312 | 100 | assert!(matches!(source.items[1], Item::Enum(_))); |
| 4df1312 | 101 | } |
| 4df1312 | 102 | |
| 4df1312 | 103 | #[test] |
| 3d6f280 | 104 | fn closureLiteralParsesWithParamsAndBody() { |
| d7e5ff4 | 105 | let src = "\ |
| b48d3a3 | 106 | fun useClosure() -> Bool = |
| d7e5ff4 | 107 | cb = |v| |
| d7e5ff4 | 108 | True |
| d7e5ff4 | 109 | cb(5) |
| d7e5ff4 | 110 | "; |
| d7e5ff4 | 111 | let source = parse(src); |
| 3d6f280 | 112 | let f = onlyFn(&source); |
| d7e5ff4 | 113 | let FnBody::Block(block) = &f.body else { panic!("expected a block body") }; |
| d7e5ff4 | 114 | let Stmt::Assign(assign) = &block.stmts[0] else { panic!("expected an assign statement") }; |
| d7e5ff4 | 115 | let Expr::Closure(closure) = &assign.values[0] else { panic!("expected a closure expression, got {:?}", assign.values[0]) }; |
| d7e5ff4 | 116 | assert_eq!(closure.params, vec!["v".to_string()]); |
| d7e5ff4 | 117 | assert_eq!(closure.body.stmts.len(), 1); |
| d7e5ff4 | 118 | } |
| d7e5ff4 | 119 | |
| d7e5ff4 | 120 | #[test] |
| 3d6f280 | 121 | fn closureLiteralParsesWithNoParams() { |
| d7e5ff4 | 122 | let src = "\ |
| b48d3a3 | 123 | fun useClosure() -> Bool = |
| d7e5ff4 | 124 | cb = || |
| d7e5ff4 | 125 | True |
| d7e5ff4 | 126 | cb() |
| d7e5ff4 | 127 | "; |
| d7e5ff4 | 128 | let source = parse(src); |
| 3d6f280 | 129 | let f = onlyFn(&source); |
| d7e5ff4 | 130 | let FnBody::Block(block) = &f.body else { panic!("expected a block body") }; |
| d7e5ff4 | 131 | let Stmt::Assign(assign) = &block.stmts[0] else { panic!("expected an assign statement") }; |
| d7e5ff4 | 132 | let Expr::Closure(closure) = &assign.values[0] else { panic!("expected a closure expression") }; |
| d7e5ff4 | 133 | assert!(closure.params.is_empty()); |
| d7e5ff4 | 134 | } |
| d7e5ff4 | 135 | |
| 2ccae10 | 136 | #[test] |
| 3d6f280 | 137 | fn fnWithoutReceiverButWithReturnTypeHasNoTypeParam() { |
| b48d3a3 | 138 | let src = "fun main() -> Int =\n 0\n"; |
| 2ccae10 | 139 | let source = parse(src); |
| 3d6f280 | 140 | let f = onlyFn(&source); |
| 2ccae10 | 141 | assert_eq!(f.type_param, None, "return type must not be mistaken for a method receiver"); |
| 2ccae10 | 142 | assert_eq!(f.returns.as_ref().map(|t| t.name.clone()), Some("Int".to_string())); |
| 2ccae10 | 143 | } |
| 2ccae10 | 144 | |
| 2ccae10 | 145 | #[test] |
| 3d6f280 | 146 | fn nestedMethodWithReturnTypeHasCorrectTypeParam() { |
| 2b8194c | 147 | let src = "type Cat =\n name: Str\n\n fun toStr() -> Str =\n \"x\"\n"; |
| 2ccae10 | 148 | let source = parse(src); |
| 3d6f280 | 149 | let f = onlyFn(&source); |
| 2ccae10 | 150 | assert_eq!(f.type_param, Some("Cat".to_string())); |
| 2ccae10 | 151 | assert_eq!(f.returns.as_ref().map(|t| t.name.clone()), Some("Str".to_string())); |
| 2ccae10 | 152 | } |
| 2ccae10 | 153 | |
| d7e5ff4 | 154 | #[test] |
| 3d6f280 | 155 | fn fnValueTypeParamParsesWithPositionalTypesAndReturn() { |
| b48d3a3 | 156 | let src = "fun each(cb: fn(Int) -> Bool) -> Bool =\n True\n"; |
| d7e5ff4 | 157 | let source = parse(src); |
| 3d6f280 | 158 | let f = onlyFn(&source); |
| d7e5ff4 | 159 | let ParamType::Fn(param_types, ret) = &f.params[0].ty else { panic!("expected ParamType::Fn, got {:?}", f.params[0].ty) }; |
| d7e5ff4 | 160 | assert_eq!(param_types.len(), 1); |
| d7e5ff4 | 161 | assert_eq!(param_types[0].name, "Int"); |
| d7e5ff4 | 162 | assert_eq!(ret.as_ref().map(|t| t.name.clone()), Some("Bool".to_string())); |
| d7e5ff4 | 163 | } |
| d7e5ff4 | 164 | |
| 0d64aff | 165 | #[test] |
| 3d6f280 | 166 | fn traitMethodReturnTypeIsNotMangledToArrowToken() { |
| 0d64aff | 167 | let src = "trait Show =\n show() -> Str\n"; |
| 0d64aff | 168 | let source = parse(src); |
| 3d6f280 | 169 | let t = onlyTrait(&source); |
| 0d64aff | 170 | let method = &t.methods[0]; |
| 0d64aff | 171 | assert_eq!(method.name, "show"); |
| 0d64aff | 172 | assert_eq!( |
| 0d64aff | 173 | method.returns.as_ref().map(|r| r.name.clone()), |
| 0d64aff | 174 | Some("Str".to_string()), |
| 0d64aff | 175 | "trait method return type must be the real type, not the '->' token" |
| 0d64aff | 176 | ); |
| 0d64aff | 177 | } |
| 0d64aff | 178 | |
| d7e5ff4 | 179 | #[test] |
| 3d6f280 | 180 | fn fnValueTypeParamParsesWithNoReturn() { |
| b48d3a3 | 181 | let src = "fun each(cb: fn(Int)) -> Bool =\n True\n"; |
| d7e5ff4 | 182 | let source = parse(src); |
| 3d6f280 | 183 | let f = onlyFn(&source); |
| d7e5ff4 | 184 | let ParamType::Fn(param_types, ret) = &f.params[0].ty else { panic!("expected ParamType::Fn") }; |
| d7e5ff4 | 185 | assert_eq!(param_types.len(), 1); |
| d7e5ff4 | 186 | assert!(ret.is_none()); |
| d7e5ff4 | 187 | } |