fea_rs/
parse.rs

1//! Load and tokenize sources.
2//!
3//! In general, you should not need to use this module directly; it is exposed
4//! so that it can be used for things like syntax highlighting.
5
6mod context;
7pub(crate) mod grammar;
8mod lexer;
9mod parser;
10mod source;
11mod tree;
12
13use std::{
14    path::{Path, PathBuf},
15    sync::Arc,
16};
17
18pub use lexer::TokenSet;
19pub use source::{FileSystemResolver, SourceLoadError, SourceResolver};
20pub use tree::ParseTree;
21
22pub(crate) use context::{IncludeStatement, ParseContext};
23pub(crate) use parser::Parser;
24pub(crate) use source::{FileId, Source, SourceList, SourceMap};
25
26use crate::{DiagnosticSet, GlyphMap};
27
28/// Attempt to parse a feature file from disk, including its imports.
29///
30/// In general, you should not need to use this method directly; instead use one
31/// of the methods in the [`compile`][crate::compile] module.
32///
33/// The `project_root` argument is optional, and is intended for the case of
34/// handling UFO files, where imports are resolved relative to the root directory,
35/// and not the feature file itself.
36///
37/// The `glyph_map`, if provided, is used to disambiguate between certain tokens
38/// that are allowed in FEA syntax but which are also legal glyph names. If it
39/// is absent, and these names are encountered, we will report an error.
40///
41/// If you are compiling from memory, or otherwise want to handle loading files
42/// and resolving imports, you can use [`parse_root`] instead.
43pub fn parse_root_file(
44    path: impl Into<PathBuf>,
45    glyph_map: Option<&GlyphMap>,
46    project_root: Option<PathBuf>,
47) -> Result<(ParseTree, DiagnosticSet), SourceLoadError> {
48    let path = path.into();
49    let project_root =
50        project_root.unwrap_or_else(|| path.parent().map(PathBuf::from).unwrap_or_default());
51    let resolver = source::FileSystemResolver::new(project_root);
52    parse_root(path, glyph_map, Box::new(resolver))
53}
54
55/// Entry point for parsing.
56///
57/// This method provides full control over how sources are located and include
58/// statements are resolved, by allowing you to provide a custom [`SourceResolver`].
59///
60/// The `path` argument is identifies the root source; it will be resolved against
61/// the provided `resolver`.
62///
63/// The `glyph_map`, if provided, is used to disambiguate between certain tokens
64/// that are allowed in FEA syntax but which are also legal glyph names. If you
65/// are not compiling the parse results, you can omit it.
66///
67/// This returns an error if a source could not be located; otherwise it returns
68/// a parse tree and a set of diagnostics, even if parsing fails. The caller must
69/// check that there are no errors (via [`DiagnosticSet::has_errors`]) to know
70/// whether or not parsing was successful.
71pub fn parse_root(
72    path: PathBuf,
73    glyph_map: Option<&GlyphMap>,
74    resolver: Box<dyn SourceResolver>,
75) -> Result<(ParseTree, DiagnosticSet), SourceLoadError> {
76    context::ParseContext::parse(path, glyph_map, resolver).map(|ctx| ctx.generate_parse_tree())
77}
78
79/// Convenience method to parse a block of FEA from memory.
80///
81/// This is useful for things like testing or syntax highlighting of a single file,
82/// but it cannot handle includes, or handle ambiguous glyph names.
83///
84/// The input text can be any of `&str`, `String`, or `Arc<str>`.
85///
86/// # Panics
87///
88/// Panics if the input contains any include statements.
89pub fn parse_string(text: impl Into<Arc<str>>) -> (ParseTree, DiagnosticSet) {
90    const SRC_NAME: &str = "parse::parse_string";
91    let text = text.into();
92    parse_root(
93        SRC_NAME.into(),
94        None,
95        Box::new(move |s: &Path| {
96            if s == Path::new(SRC_NAME) {
97                Ok(text.clone())
98            } else {
99                Err(SourceLoadError::new(
100                    s.to_path_buf(),
101                    "parse_string cannot handle imports",
102                ))
103            }
104        }),
105    )
106    .unwrap()
107}
108
109/// Parse an arbitrary block of FEA text with a specific parsing function.
110///
111/// This can be used to parse any part of the grammar, including elements that
112/// are not valid at the top level.
113#[cfg(test)]
114pub(crate) fn parse_node(text: &str, parser_fn: impl FnOnce(&mut Parser)) -> crate::Node {
115    let mut sink = crate::token_tree::AstSink::new(text, FileId::CURRENT_FILE, None);
116    let mut parser = Parser::new(text, &mut sink);
117    parser_fn(&mut parser);
118    let (root, _errs, _) = sink.finish();
119    root
120}