plum

#treesitter#compiler#wasm

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
}