Skip to content

Commit f227d0d

Browse files
authored
Initial API for Scanner. (#12)
Provides an interface for _lexing_ PartiQL. Traditionally we would factor this as the low-level interface for the parser, but with the PEG based implementation, we just surface the relevant parts of the parser as a rule to parse with. This commit helps explore how to effectively integrate with the somewhat low-level APIs for parsing that Pest provides. * Adds `scanner` module. * Adds `Scanner` trait to the prelude. * Makes `PartiQLParser` public to crate. * Refactors `LineAndColumn` as a tuple for `Position::At`. - Adds this to the `prelude`. * Changes entry point for PEG to `Query` and added `Scanner` as the entry point rules for implementing the `Scanner` API. * Adds `PairsExt`/`PairExt` trait/impl to add utility methods for working with Pest `Pairs`/`Pair`. * Adds `LineAndColumn::position_from` and cleans up some doc/doc tests. * Adds `TryFrom` and `TryInto` into the prelude. Resolves #13.
1 parent d748ebd commit f227d0d

File tree

6 files changed

+589
-19
lines changed

6 files changed

+589
-19
lines changed

partiql-parser/src/lib.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,54 @@
22

33
//! Provides a parser for the [PartiQL][partiql] query language.
44
//!
5+
//! # Usage
6+
//!
7+
//! An API to interact with PartiQL tokens is the [`mod@scanner`] module.
8+
//! The [`scanner()`] function creates a [`Scanner`](scanner::Scanner) instance
9+
//! that one can use to parse tokens incrementally from some input slice.
10+
//!
11+
//! ```
12+
//! use partiql_parser::prelude::*;
13+
//! use partiql_parser::scanner;
14+
//!
15+
//! fn main() -> ParserResult<()> {
16+
//! use partiql_parser::scanner::Content::*;
17+
//!
18+
//! let mut scanner = scanner("SELECT FROM");
19+
//! let first = scanner.next_token()?;
20+
//!
21+
//! // get the parsed variant of the token
22+
//! match first.content() {
23+
//! Keyword(kw) => assert_eq!("SELECT", kw),
24+
//! }
25+
//! // the entire text of a token can be fetched--which looks the roughly the
26+
//! // same for a keyword.
27+
//! assert_eq!("SELECT", first.text());
28+
//!
29+
//! let second = scanner.next_token()?;
30+
//! // get the parsed variant of the token
31+
//! match second.content() {
32+
//! Keyword(kw) => assert_eq!("FROM", kw),
33+
//! }
34+
//! // the other thing we can do is get line/column information from a token
35+
//! assert_eq!(LineAndColumn::try_at(1, 8)?, second.start());
36+
//! assert_eq!(LineAndColumn::try_at(1, 12)?, second.end());
37+
//!
38+
//! // this API is built on immutable slices, so we can restart scanning from any token
39+
//! scanner = first.into();
40+
//! let second_again = scanner.next_token()?;
41+
//! assert_eq!(second, second_again);
42+
//!
43+
//! Ok(())
44+
//! }
45+
//! ```
46+
//!
547
//! [partiql]: https://partiql.org
648
749
mod peg;
850
pub mod prelude;
951
pub mod result;
52+
pub mod scanner;
1053

1154
pub use peg::recognize_partiql;
55+
pub use scanner::scanner;

partiql-parser/src/partiql.pest

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
WHITESPACE = _{ " " | "\t" | "\x0B" | "\x0C" | "\r" | "\n" }
22

3-
// really basic rules to just detect a sequence of keywords
4-
// two dip our toes in Pest
3+
// TODO implement a full grammar, this is a very primitive version to start
4+
// working with Pest and its APIs.
55

6-
Keywords = _{ SOI ~ Keyword+ ~ EOI}
6+
// Entry point for full query parsing
7+
Query = _{ SOI ~ Keyword+ ~ EOI}
8+
9+
// Entry point for query "scanning"
10+
// Note that this is factored this way to support an iteration style API
11+
// where we can call back into this rule on subsequent input.
12+
Scanner = _{ SOI ~ Keyword }
713

814
Keyword = { AllKeywords }
915

partiql-parser/src/peg.rs

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,63 @@
44
//! can be exported for users to consume.
55
66
use crate::prelude::*;
7-
use pest::Parser;
7+
use crate::result::syntax_error;
8+
use pest::iterators::{Pair, Pairs};
9+
use pest::{Parser, RuleType};
810
use pest_derive::Parser;
911

1012
#[derive(Parser)]
1113
#[grammar = "partiql.pest"]
12-
struct PartiQLParser;
14+
pub(crate) struct PartiQLParser;
15+
16+
/// Extension methods for working with [`Pairs`].
17+
pub(crate) trait PairsExt<'val, R: RuleType> {
18+
/// Consumes a [`Pairs`] as a singleton, returning an error if there are less or more than
19+
/// one [`Pair`].
20+
fn exactly_one(self) -> ParserResult<Pair<'val, R>>;
21+
}
22+
23+
impl<'val, R: RuleType> PairsExt<'val, R> for Pairs<'val, R> {
24+
fn exactly_one(mut self) -> ParserResult<Pair<'val, R>> {
25+
match self.next() {
26+
Some(pair) => {
27+
// make sure there isn't something more...
28+
if let Some(other_pair) = self.next() {
29+
syntax_error(
30+
format!("Expected one token pair, got: {:?}, {:?}", pair, other_pair),
31+
pair.start()?.into(),
32+
)?;
33+
}
34+
Ok(pair)
35+
}
36+
None => syntax_error(
37+
"Expected at one token pair, got nothing!",
38+
Position::Unknown,
39+
),
40+
}
41+
}
42+
}
43+
44+
/// Extension methods for working with [`Pair`].
45+
pub(crate) trait PairExt<'val, R: RuleType> {
46+
/// Translates the start position of the [`Pair`] into a [`LineAndColumn`].
47+
fn start(&self) -> ParserResult<LineAndColumn>;
48+
49+
/// Translates the end position of the [`Pair`] into a [`LineAndColumn`].
50+
fn end(&self) -> ParserResult<LineAndColumn>;
51+
}
52+
53+
impl<'val, R: RuleType> PairExt<'val, R> for Pair<'val, R> {
54+
#[inline]
55+
fn start(&self) -> ParserResult<LineAndColumn> {
56+
self.as_span().start_pos().line_col().try_into()
57+
}
58+
59+
#[inline]
60+
fn end(&self) -> ParserResult<LineAndColumn> {
61+
self.as_span().end_pos().line_col().try_into()
62+
}
63+
}
1364

1465
/// Recognizer for PartiQL queries.
1566
///
@@ -18,7 +69,7 @@ struct PartiQLParser;
1869
///
1970
/// This API will be replaced with one that produces an AST in the future.
2071
pub fn recognize_partiql(input: &str) -> ParserResult<()> {
21-
PartiQLParser::parse(Rule::Keywords, input)?;
72+
PartiQLParser::parse(Rule::Query, input)?;
2273
Ok(())
2374
}
2475

@@ -34,13 +85,9 @@ mod tests {
3485
#[test]
3586
fn error() -> ParserResult<()> {
3687
match recognize_partiql("SELECT FROM MOO") {
37-
Err(ParserError::SyntaxError { position, .. }) => assert_eq!(
38-
Position::At {
39-
line: 1,
40-
column: 13
41-
},
42-
position
43-
),
88+
Err(ParserError::SyntaxError { position, .. }) => {
89+
assert_eq!(Position::at(1, 13), position)
90+
}
4491
_ => panic!("Expected Syntax Error"),
4592
};
4693
Ok(())

partiql-parser/src/prelude.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
//! Convenience export of common traits and basic types that are almost always
44
//! needed when using the parser APIs.
55
6+
pub use crate::result::LineAndColumn;
67
pub use crate::result::ParserError;
78
pub use crate::result::ParserResult;
89
pub use crate::result::Position;
10+
pub use crate::scanner::Scanner;
11+
pub use std::convert::TryFrom;
12+
pub use std::convert::TryInto;

0 commit comments

Comments
 (0)