aboutsummaryrefslogtreecommitdiff
path: root/rprt-engine/src/parser.rs
diff options
context:
space:
mode:
authortslil <tslil@posteo.de>2025-10-18 18:12:46 +0100
committertslil <tslil@posteo.de>2025-10-18 20:14:52 +0100
commit02c7b10c407982ca117098bd64358a8bb910bd3a (patch)
treec96cb3265cd58e63dd31a539052c9842ca37b01d /rprt-engine/src/parser.rs
parent9a845a610aaf42aaba301d7b0ce1c88096d36915 (diff)
Some basic train and parenthesis parsing, things are beginning to become exciting
Diffstat (limited to 'rprt-engine/src/parser.rs')
-rw-r--r--rprt-engine/src/parser.rs432
1 files changed, 403 insertions, 29 deletions
diff --git a/rprt-engine/src/parser.rs b/rprt-engine/src/parser.rs
index 44feda0..c98d977 100644
--- a/rprt-engine/src/parser.rs
+++ b/rprt-engine/src/parser.rs
@@ -18,9 +18,9 @@ struct ParseContext {
}
impl ParseContext {
- fn advance(&mut self) -> Result<(), ParseError> {
- self.pos += 1;
- if self.pos >= self.tokens.len() {
+ fn advance(&mut self, how_much: usize, need_more: bool) -> Result<(), ParseError> {
+ self.pos += how_much;
+ if need_more && self.pos >= self.tokens.len() {
Err(ParseError::UnexpectedEndOfInput {
expecting: "more tokens",
})
@@ -35,48 +35,196 @@ impl ParseContext {
.cloned()
.ok_or(ParseError::UnexpectedEndOfInput { expecting: "token" })
}
+
+ fn peek(&self, offset: usize) -> Option<&Token> {
+ self.tokens.get(self.pos + offset)
+ }
}
-pub fn parse(tokens: Vec<Token>) -> Result<SelectionValuedExpr, ParseError> {
+pub fn parse(tokens: Vec<Token>) -> Result<Composite, ParseError> {
let mut context = ParseContext {
tokens: tokens,
pos: 0,
};
- let func = parse_function(&mut context)?;
- Ok(SelectionValuedExpr::NullaryApplicationS(func))
+ parse_function_in_application(&mut context)
+}
+
+fn parse_function_in_application(context: &mut ParseContext) -> Result<Composite, ParseError> {
+ let mut functions = Vec::new();
+ while can_start_selection_function(context, 0) {
+ functions.push(parse_function_atom(context)?);
+ }
+ if functions.is_empty() {
+ return Err(ParseError::UnexpectedToken {
+ found: context.current()?,
+ expecting: "function",
+ });
+ }
+ Ok(build_train(functions))
+}
+
+fn build_train(mut functions: Vec<Composite>) -> Composite {
+ // Rust is insane and has no easy way to destructure a vector and own the elements
+ // so this function is littered with unwraps for no reason, we already own the vector!
+ match functions.len() {
+ 1 => functions.pop().unwrap(),
+ 2 => {
+ let [f, g] = <[_; 2]>::try_from(functions).unwrap();
+ Composite::Train2 {
+ f: Box::new(f),
+ g: Box::new(g),
+ }
+ }
+ 3 => {
+ let [f, g, h] = <[_; 3]>::try_from(functions).unwrap();
+ Composite::Train3 {
+ f: Box::new(f),
+ g: Box::new(g),
+ h: Box::new(h),
+ }
+ }
+ n if n % 2 == 0 => {
+ let rest = functions.split_off(1);
+ let [f] = <[_; 1]>::try_from(functions).unwrap();
+ Composite::Train2 {
+ f: Box::new(f),
+ g: Box::new(build_train(rest)),
+ }
+ }
+ _ => {
+ let rest = functions.split_off(2);
+ let [f, g] = <[_; 2]>::try_from(functions).unwrap();
+ Composite::Train3 {
+ f: Box::new(f),
+ g: Box::new(g),
+ h: Box::new(build_train(rest)),
+ }
+ }
+ }
+}
+
+fn can_start_selection_function(context: &ParseContext, offset: usize) -> bool {
+ matches!(
+ context.peek(offset),
+ Some(Token::Empty)
+ | Some(Token::EndOfBuffer)
+ | Some(Token::Span)
+ | Some(Token::CharacterOffset)
+ | Some(Token::Number(_))
+ | Some(Token::Regex(_))
+ | Some(Token::AllMatches)
+ | Some(Token::LineOffset)
+ | Some(Token::BufferMatch)
+ | Some(Token::Reverse)
+ | Some(Token::Sequential)
+ | Some(Token::Complement)
+ | Some(Token::Conditional)
+ | Some(Token::LParen)
+ )
}
-fn parse_function(context: &mut ParseContext) -> Result<SelectionFn, ParseError> {
+fn parse_function_atom(context: &mut ParseContext) -> Result<Composite, ParseError> {
+ if matches!(context.current()?, Token::LParen) {
+ context.advance(1, true)?;
+ let func = parse_function_in_application(context)?;
+ if !matches!(context.current()?, Token::RParen) {
+ return Err(ParseError::UnexpectedToken {
+ found: context.current()?,
+ expecting: ")",
+ });
+ }
+ context.advance(1, false)?;
+ return Ok(func);
+ }
+ let result_trans = parse_result_trans(context)?;
+ let search_mod = parse_search_mod(context)?;
let func = parse_builtin_function_s(context)?;
- Ok(SelectionFn::Function {
+ Ok(Composite::SelectionFunction {
func: func,
- search_mod: None,
- result_transform: None,
+ search_mod: search_mod,
+ result_transform: result_trans,
})
}
+fn parse_search_mod(context: &mut ParseContext) -> Result<Option<SearchModifier>, ParseError> {
+ let maybe_mod = |token| match token {
+ Token::Sequential => Ok(Some(SearchModifier::Sequential)),
+ Token::Reverse => Ok(Some(SearchModifier::Reverse)),
+ _ => Ok(None),
+ };
+ let token = context.current()?;
+ if let Some(m1) = maybe_mod(token)? {
+ context.advance(1, true)?;
+ if let Some(m2) = maybe_mod(context.current()?)? {
+ context.advance(1, true)?;
+ return Ok(Some(match (m1, m2) {
+ (SearchModifier::Sequential, SearchModifier::Reverse) => {
+ SearchModifier::SequentialReverse
+ }
+ (SearchModifier::Reverse, SearchModifier::Sequential) => {
+ SearchModifier::ReverseSequential
+ }
+ (_, _) => unreachable!(),
+ }));
+ }
+ return Ok(Some(m1));
+ };
+ Ok(None)
+}
+
+fn parse_result_trans(context: &mut ParseContext) -> Result<Option<ResultTransform>, ParseError> {
+ let maybe_mod = |token| match token {
+ Token::Complement => Ok(Some(ResultTransform::Complement)),
+ Token::Conditional => Ok(Some(ResultTransform::Conditional)),
+ _ => Ok(None),
+ };
+ let token = context.current()?;
+ if let Some(m1) = maybe_mod(token)? {
+ context.advance(1, true)?;
+ if let Some(m2) = maybe_mod(context.current()?)? {
+ context.advance(1, true)?;
+ return Ok(Some(match (m1, m2) {
+ (ResultTransform::Complement, ResultTransform::Conditional) => {
+ ResultTransform::ComplementConditional
+ }
+ (ResultTransform::Conditional, ResultTransform::Complement) => {
+ ResultTransform::ConditionalComplement
+ }
+ (_, _) => unreachable!(),
+ }));
+ }
+ return Ok(Some(m1));
+ };
+ Ok(None)
+}
+
fn parse_builtin_function_s(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> {
let token = context.current()?;
- match token {
- Token::Empty => Ok(BuiltinSelectionFn::Empty),
- Token::EndOfBuffer => Ok(BuiltinSelectionFn::EndOfBuffer),
- Token::Span => Ok(BuiltinSelectionFn::Span),
- Token::CharacterOffset => parse_char_offset(context),
- Token::Number(n) => Ok(BuiltinSelectionFn::Line(n)),
- Token::Regex(pattern) => Ok(BuiltinSelectionFn::Regex(pattern)),
- Token::AllMatches => parse_all_matches(context),
- Token::PositiveLineOffset => parse_relative_line(context),
- Token::BufferMatch => parse_buffer_match(context),
- _ => Err(ParseError::UnexpectedToken {
- found: token,
- expecting: "selection function",
- }),
- }
+ let result = match token {
+ Token::Empty => BuiltinSelectionFn::Empty,
+ Token::EndOfBuffer => BuiltinSelectionFn::EndOfBuffer,
+ Token::Span => BuiltinSelectionFn::Span,
+ Token::CharacterOffset => return parse_char_offset(context),
+ Token::Number(n) => BuiltinSelectionFn::Line(n),
+ Token::Regex(pattern) => BuiltinSelectionFn::Regex(pattern),
+ Token::AllMatches => return parse_all_matches(context),
+ Token::LineOffset => return parse_relative_line(context),
+ Token::BufferMatch => return parse_buffer_match(context),
+ _ => {
+ return Err(ParseError::UnexpectedToken {
+ found: token,
+ expecting: "selection function",
+ });
+ }
+ };
+ context.advance(1, false)?;
+ Ok(result)
}
fn parse_char_offset(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> {
- context.advance()?;
+ context.advance(1, true)?;
let token = context.current()?;
+ context.advance(1, false)?;
match token {
Token::Number(n) => Ok(BuiltinSelectionFn::CharOffset(n)),
_ => Err(ParseError::UnexpectedToken {
@@ -87,8 +235,9 @@ fn parse_char_offset(context: &mut ParseContext) -> Result<BuiltinSelectionFn, P
}
fn parse_all_matches(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> {
- context.advance()?;
+ context.advance(1, true)?;
let token = context.current()?;
+ context.advance(1, false)?;
match token {
Token::Regex(pattern) => Ok(BuiltinSelectionFn::AllMatches(pattern)),
_ => Err(ParseError::UnexpectedToken {
@@ -99,8 +248,9 @@ fn parse_all_matches(context: &mut ParseContext) -> Result<BuiltinSelectionFn, P
}
fn parse_relative_line(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> {
- context.advance()?;
+ context.advance(1, true)?;
let token = context.current()?;
+ context.advance(1, false)?;
match token {
Token::Number(n) => Ok(BuiltinSelectionFn::RelativeLine(n)),
_ => Err(ParseError::UnexpectedToken {
@@ -111,8 +261,9 @@ fn parse_relative_line(context: &mut ParseContext) -> Result<BuiltinSelectionFn,
}
fn parse_buffer_match(context: &mut ParseContext) -> Result<BuiltinSelectionFn, ParseError> {
- context.advance()?;
+ context.advance(1, true)?;
let token = context.current()?;
+ context.advance(1, false)?;
match token {
Token::Regex(pattern) => Ok(BuiltinSelectionFn::BufferMatch(pattern)),
_ => Err(ParseError::UnexpectedToken {
@@ -121,3 +272,226 @@ fn parse_buffer_match(context: &mut ParseContext) -> Result<BuiltinSelectionFn,
}),
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::token::tokenise;
+
+ #[test]
+ fn test_parse_nullary() {
+ let input = "/foo/";
+ let tokens = tokenise(input).unwrap();
+ let result = parse(tokens).unwrap();
+ assert!(matches!(
+ result,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::Regex(_),
+ search_mod: None,
+ result_transform: None,
+ }
+ ));
+ }
+
+ #[test]
+ fn test_parse_train_with_modifiers() {
+ let input = "e ?'/foo/";
+ let tokens = tokenise(input).unwrap();
+ let result = parse(tokens).unwrap();
+ println!("{:?}", result);
+ match result {
+ Composite::Train2 { f, g } => {
+ assert!(matches!(
+ *f,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::Empty,
+ search_mod: None,
+ result_transform: None,
+ }
+ ));
+ assert!(matches!(
+ *g,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::Regex(_),
+ search_mod: Some(SearchModifier::Reverse),
+ result_transform: Some(ResultTransform::Conditional),
+ }
+ ));
+ }
+ _ => panic!(),
+ }
+ }
+
+ #[test]
+ fn test_parse_nullary_with_modifier() {
+ let input = "'e";
+ let tokens = tokenise(input).unwrap();
+ let result = parse(tokens).unwrap();
+ println!("{:?}", result);
+ match result {
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::Empty,
+ search_mod: Some(SearchModifier::Reverse),
+ result_transform: None,
+ } => {}
+ _ => panic!(),
+ }
+ }
+
+ #[test]
+ fn test_parse_2train() {
+ let input = "- $";
+ let tokens = tokenise(input).unwrap();
+ let result = parse(tokens).unwrap();
+ println!("{:?}", result);
+ match result {
+ Composite::Train2 { f, g } => {
+ assert!(matches!(
+ *f,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::Span,
+ search_mod: None,
+ result_transform: None,
+ }
+ ));
+ assert!(matches!(
+ *g,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::EndOfBuffer,
+ search_mod: None,
+ result_transform: None,
+ }
+ ));
+ }
+ _ => panic!(),
+ }
+ }
+
+ #[test]
+ fn test_parse_3train() {
+ let input = "x/start/ - x/end/";
+ let tokens = tokenise(input).unwrap();
+ let result = parse(tokens).unwrap();
+ println!("{:?}", result);
+ match result {
+ Composite::Train3 { f, g, h } => {
+ match *f {
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::AllMatches(pattern),
+ search_mod: None,
+ result_transform: None,
+ } => assert_eq!(pattern, "start"),
+ _ => panic!(),
+ }
+ assert!(matches!(
+ *g,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::Span,
+ search_mod: None,
+ result_transform: None,
+ }
+ ));
+ match *h {
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::AllMatches(pattern),
+ search_mod: None,
+ result_transform: None,
+ } => assert_eq!(pattern, "end"),
+ _ => panic!(),
+ }
+ }
+ _ => panic!(),
+ }
+ }
+
+ #[test]
+ fn test_parse_parenthesized() {
+ let input = "(- $)";
+ let tokens = tokenise(input).unwrap();
+ let result = parse(tokens).unwrap();
+ println!("{:?}", result);
+ match result {
+ Composite::Train2 { f, g } => {
+ assert!(matches!(
+ *f,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::Span,
+ search_mod: None,
+ result_transform: None,
+ }
+ ));
+ assert!(matches!(
+ *g,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::EndOfBuffer,
+ search_mod: None,
+ result_transform: None,
+ }
+ ));
+ }
+ _ => panic!(),
+ }
+ }
+
+ #[test]
+ fn test_parse_5train() {
+ let input = "#0 - #10 - #20";
+ let tokens = tokenise(input).unwrap();
+ let result = parse(tokens).unwrap();
+ println!("{:?}", result);
+ match result {
+ Composite::Train3 { f, g, h } => {
+ match *f {
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::CharOffset(n),
+ search_mod: None,
+ result_transform: None,
+ } => assert_eq!(n, 0),
+ _ => panic!(),
+ }
+ assert!(matches!(
+ *g,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::Span,
+ search_mod: None,
+ result_transform: None,
+ }
+ ));
+ match *h {
+ Composite::Train3 {
+ ref f,
+ ref g,
+ ref h,
+ } => {
+ match **f {
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::CharOffset(n),
+ search_mod: None,
+ result_transform: None,
+ } => assert_eq!(n, 10),
+ _ => panic!(),
+ }
+ assert!(matches!(
+ **g,
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::Span,
+ search_mod: None,
+ result_transform: None,
+ }
+ ));
+ match **h {
+ Composite::SelectionFunction {
+ func: BuiltinSelectionFn::CharOffset(n),
+ search_mod: None,
+ result_transform: None,
+ } => assert_eq!(n, 20),
+ _ => panic!(),
+ }
+ }
+ _ => panic!(),
+ }
+ }
+ _ => panic!(),
+ }
+ }
+}