Skip to main content

gremlin_rs/
lib.rs

1//! # gremlin-rs
2//!
3//! A parser for the [Gremlin graph traversal language][gremlin], generated from
4//! Apache TinkerPop's **official** `Gremlin.g4` grammar via
5//! [`antlr-rust-runtime`](https://crates.io/crates/antlr-rust-runtime).
6//!
7//! Unlike a Gremlin *client* (which builds traversals programmatically and sends
8//! bytecode to a server), this crate parses Gremlin **text** — e.g.
9//! `g.V().has('name','marko').out('knows').values('name')` — into a parse tree
10//! you can walk, translate, lint, or analyze.
11//!
12//! Because the parser is generated from the same grammar TinkerPop uses for its
13//! JVM and JavaScript language variants, it tracks the official language rather
14//! than a hand-maintained approximation.
15//!
16//! ## Example
17//!
18//! ```
19//! use gremlin_rs::parse;
20//!
21//! let tree = parse("g.V().has('name','marko').out('knows').values('name')")?;
22//! println!("{}", tree.text());
23//! # Ok::<(), gremlin_rs::GremlinParseError>(())
24//! ```
25//!
26//! [gremlin]: https://tinkerpop.apache.org/gremlin.html
27
28#![forbid(unsafe_code)]
29
30mod generated {
31    #![allow(warnings)]
32    #![allow(clippy::all, clippy::pedantic, clippy::nursery)]
33    pub mod gremlin;
34    pub mod gremlin_lexer;
35}
36
37use antlr4_runtime::{CommonTokenStream, InputStream, Parser};
38pub use antlr4_runtime::tree::ParseTree;
39
40use generated::gremlin::Gremlin;
41use generated::gremlin_lexer::GremlinLexer;
42
43/// An error produced while parsing Gremlin text.
44#[derive(Debug)]
45pub enum GremlinParseError {
46    /// The lexer or parser failed outright (e.g. an unrecoverable error).
47    Runtime(antlr4_runtime::AntlrError),
48    /// The parser produced a tree but recovered from `count` syntax error(s);
49    /// the input is not valid Gremlin. The recovered tree is provided for
50    /// callers that want to inspect a best-effort result anyway.
51    Syntax { count: usize, tree: ParseTree },
52}
53
54impl core::fmt::Display for GremlinParseError {
55    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56        match self {
57            Self::Runtime(e) => write!(f, "gremlin parse failed: {e}"),
58            Self::Syntax { count, .. } => {
59                write!(f, "gremlin input has {count} syntax error(s)")
60            }
61        }
62    }
63}
64
65impl std::error::Error for GremlinParseError {}
66
67impl From<antlr4_runtime::AntlrError> for GremlinParseError {
68    fn from(e: antlr4_runtime::AntlrError) -> Self {
69        Self::Runtime(e)
70    }
71}
72
73/// Parses a Gremlin query list — one or more `;`-separated traversals — the
74/// grammar's top-level `queryList` entry rule.
75///
76/// Returns the parse tree on success. If the input is syntactically invalid,
77/// returns [`GremlinParseError::Syntax`] (which still carries the recovered
78/// tree), or [`GremlinParseError::Runtime`] on an unrecoverable error.
79///
80/// # Errors
81/// Returns [`GremlinParseError`] if the input is not valid Gremlin.
82pub fn parse(input: &str) -> Result<ParseTree, GremlinParseError> {
83    parse_with(input, Gremlin::query_list)
84}
85
86/// Parses a single Gremlin query (the grammar's `query` entry rule) rather than
87/// a `;`-separated list. Use this when you know the input is exactly one
88/// traversal or expression.
89///
90/// # Errors
91/// Returns [`GremlinParseError`] if the input is not a valid single Gremlin query.
92pub fn parse_query(input: &str) -> Result<ParseTree, GremlinParseError> {
93    parse_with(input, Gremlin::query)
94}
95
96/// Shared driver: build lexer + token stream + parser, invoke `entry`, and
97/// promote recovered syntax errors to a hard error so callers get a clean
98/// valid/invalid signal by default.
99fn parse_with<F>(input: &str, entry: F) -> Result<ParseTree, GremlinParseError>
100where
101    F: FnOnce(
102        &mut Gremlin<GremlinLexer<InputStream>>,
103    ) -> Result<ParseTree, antlr4_runtime::AntlrError>,
104{
105    let lexer = GremlinLexer::new(InputStream::new(input));
106    let tokens = CommonTokenStream::new(lexer);
107    let mut parser = Gremlin::new(tokens);
108    let tree = entry(&mut parser)?;
109    let count = parser.number_of_syntax_errors();
110    if count > 0 {
111        return Err(GremlinParseError::Syntax { count, tree });
112    }
113    Ok(tree)
114}