Skip to main content

varyk_syntax/
lib.rs

1//! `varyk-syntax`: spans, tokens, lexer, parser, and AST for Varyk.
2//!
3//! This crate carries no semantic analysis, so a formatter or a language
4//! server can depend on it without pulling in the type checker or borrow
5//! analysis.
6
7mod ast;
8mod error;
9mod lexer;
10mod parser;
11mod source;
12mod span;
13mod token;
14
15pub use ast::{
16    BinaryOp, Block, EnumDecl, EnumVariant, Expr, ExprKind, FieldDecl, ForHead, Function, Ident,
17    ImplBlock, Item, Literal, MatchArm, ModDecl, Param, Path, PathStart, Pattern, Program,
18    SelfMode, Stmt, StructDecl, TypeExpr, UnaryOp, UseDecl, VariantField, VariantFields,
19};
20pub use error::{FixIt, SyntaxError, V0001, V0002, V0003, V0010, V0011, V0012};
21pub use lexer::{KEYWORDS, RESERVED_KEYWORDS, lex};
22pub use parser::parse;
23pub use source::SourceFile;
24pub use span::{FileId, Span};
25pub use token::{Token, TokenKind};
26
27/// Lexes `file` then parses it, merging the lexer's errors first. If the
28/// lexer reported anything, parsing is skipped: a file with lexical errors
29/// has no reliable token stream to parse from, so its `Program` is empty
30/// and only the lexer's errors are returned. The compiler's `resolve`
31/// module calls this for every file it loads.
32pub fn parse_source(file: &SourceFile) -> (Program, Vec<SyntaxError>) {
33    let (tokens, lex_errors) = lex(file);
34    if !lex_errors.is_empty() {
35        return (Program { items: Vec::new() }, lex_errors);
36    }
37    parse(&tokens, file.id)
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use std::path::{Path, PathBuf};
44
45    /// Every `.vr` file under `examples/`, found recursively so the
46    /// per-language-feature example directories (`interop/`, `modules/`)
47    /// are covered too.
48    fn example_files() -> Vec<PathBuf> {
49        let examples_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples");
50        let mut files = Vec::new();
51        collect_vr_files(&examples_dir, &mut files);
52        files
53    }
54
55    fn collect_vr_files(dir: &Path, files: &mut Vec<PathBuf>) {
56        let entries = std::fs::read_dir(dir)
57            .unwrap_or_else(|e| panic!("failed to read directory {dir:?}: {e}"));
58        for entry in entries {
59            let path = entry
60                .unwrap_or_else(|e| panic!("bad directory entry: {e}"))
61                .path();
62            if path.is_dir() {
63                collect_vr_files(&path, files);
64            } else if path.extension().and_then(|ext| ext.to_str()) == Some("vr") {
65                files.push(path);
66            }
67        }
68    }
69
70    /// The first end-to-end check of the grammar: every example file in
71    /// the repository must parse with zero errors.
72    #[test]
73    fn every_example_file_parses_with_zero_errors() {
74        let files = example_files();
75        assert!(
76            !files.is_empty(),
77            "found no .vr files under examples/; check the path"
78        );
79        for path in files {
80            let text = std::fs::read_to_string(&path)
81                .unwrap_or_else(|e| panic!("failed to read {path:?}: {e}"));
82            let file = SourceFile::new(FileId(0), path.clone(), text);
83            let (_program, errors) = parse_source(&file);
84            assert!(
85                errors.is_empty(),
86                "unexpected errors parsing {path:?}: {errors:?}"
87            );
88        }
89    }
90}