1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
#[macro_use]
mod macros;
mod kinds;
pub mod parser;
pub mod tokenizer;
pub mod types;
pub mod value;

pub use self::{
    kinds::SyntaxKind,
    parser::AST,
    value::{StrPart, Value as NixValue},
};

pub use rowan::{
    NodeOrToken, SmolStr, SyntaxElementChildren, SyntaxNodeChildren, TextRange, TextUnit,
    TokenAtOffset, WalkEvent,
};

use self::tokenizer::Tokenizer;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NixLanguage {}

impl rowan::Language for NixLanguage {
    type Kind = SyntaxKind;
    fn kind_from_raw(raw: rowan::cursor::SyntaxKind) -> Self::Kind {
        let discriminant: u16 = raw.0;
        assert!(discriminant <= (SyntaxKind::__LAST as u16));
        unsafe { std::mem::transmute::<u16, SyntaxKind>(discriminant) }
    }
    fn kind_to_raw(kind: Self::Kind) -> rowan::cursor::SyntaxKind {
        rowan::cursor::SyntaxKind(kind as u16)
    }
}

pub type SyntaxNode = rowan::SyntaxNode<NixLanguage>;
pub type SyntaxToken = rowan::SyntaxToken<NixLanguage>;
pub type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;

/// A convenience function for first tokenizing and then parsing given input
pub fn parse(input: &str) -> AST {
    parser::parse(Tokenizer::new(input))
}

#[cfg(test)]
mod tests {
    use super::{parse, types::*, value::StrPart, NixValue, SyntaxKind};

    #[test]
    fn interpolation() {
        let ast = parse(include_str!("../test_data/general/interpolation.nix"));

        let let_in = ast.root().inner().and_then(LetIn::cast).unwrap();
        let set = let_in.body().and_then(AttrSet::cast).unwrap();
        let entry = set.entries().nth(1).unwrap();
        let value = entry.value().and_then(Str::cast).unwrap();

        match &*value.parts() {
            &[
                StrPart::Literal(ref s1),
                StrPart::Ast(_),
                StrPart::Literal(ref s2),
                StrPart::Ast(_),
                StrPart::Literal(ref s3)
            ]
            if s1 == "The set\'s x value is: "
                && s2 == "\n\nThis line shall have no indention\n  This line shall be indented by 2\n\n\n"
                && s3 == "\n" => (),
            parts => panic!("did not match: {:#?}", parts)
        }
    }
    #[test]
    fn inherit() {
        let ast = parse(include_str!("../test_data/general/inherit.nix"));

        let let_in = ast.root().inner().and_then(LetIn::cast).unwrap();
        let set = let_in.body().and_then(AttrSet::cast).unwrap();
        let inherit = set.inherits().nth(1).unwrap();

        let from = inherit.from().unwrap().inner().and_then(Ident::cast).unwrap();
        assert_eq!(from.as_str(), "set");
        let mut children = inherit.idents();
        assert_eq!(children.next().unwrap().as_str(), "z");
        assert_eq!(children.next().unwrap().as_str(), "a");
        assert!(children.next().is_none());
    }
    #[test]
    fn math() {
        let ast = parse(include_str!("../test_data/general/math.nix"));
        let root = ast.root().inner().and_then(BinOp::cast).unwrap();
        let operation = root.lhs().and_then(BinOp::cast).unwrap();

        assert_eq!(root.operator(), BinOpKind::Add);
        assert_eq!(operation.operator(), BinOpKind::Add);

        let lhs = operation.lhs().and_then(Value::cast).unwrap();
        assert_eq!(lhs.to_value(), Ok(NixValue::Integer(1)));

        let rhs = operation.rhs().and_then(BinOp::cast).unwrap();
        assert_eq!(rhs.operator(), BinOpKind::Mul);
    }
    #[test]
    fn t_macro() {
        assert_eq!(T![@], SyntaxKind::TOKEN_AT);
        assert!(match SyntaxKind::TOKEN_PAREN_OPEN {
            T!["("] => true,
            _ => false,
        });
    }
}