home ~ projects ~ socials

Getting Better nom Errors With nom_supreme

This page is a draft prototype effort to get more helpful error reporting when using the nom parsing library in rust.

My current approach is to use the nom_supreme crate to add extra context and create a custom error struct to pass back at the end of the parsing run. Here's what it looks like:

```cargo
[dependencies]
nom = "7.1.1"
nom-supreme = "0.8.0"
```

use nom_supreme::error::ErrorTree;
use nom::IResult;
use nom::Parser;
use nom_supreme::final_parser::final_parser;
use nom_supreme::error::GenericErrorTree;
use nom_supreme::final_parser::Location;
use nom_supreme::final_parser::RecreateContext;
use nom::bytes::complete::tag;
use nom_supreme::parser_ext::ParserExt;

#[derive(Debug)]
pub struct ParserError {
  line: usize,
  column: usize,
  remainder: String,
  source: String,
  message: String,
}

fn main() {
  let content = "the quick brown fox";
  match parse(content) {
    Ok(matched) => {
      println!("Success: {}", matched)
    }
    Err(e) => {
      println!(
        "Error: {} - line {} - column: {}\nAT: {}\nFROM: {}",
        e.message,
        e.line, 
        e.column,
        e.remainder, 
        e.source, 
      )
    }
  }
}

fn parse(source: &str) -> Result<&str, ParserError> {
  match final_parser(do_parse)(source)  {
    Ok(text) => Ok(text), 
    Err(e) => Err(get_error(source, &e))
  }
}

fn do_parse(source: &str) -> IResult<&str, &str, ErrorTree<&str>> {
  let (source , _)  = tag("the")
    .context("should be the")
    .parse(source)?;
  let (source , result)  = tag("slow")
    .context("should be slow")
    .parse(source)?;
  Ok((source, result))
}

fn get_error(content: &str, tree: &ErrorTree<&str>) -> ParserError {
  match tree {
    GenericErrorTree::Base{ location, kind }=> { 
      let details = Location::recreate_context(content, location); 
      ParserError{
        line: details.line, 
        column: details.column,
        source: content.to_string(),
        remainder: location.to_string(),
        message: kind.to_string()
      }
    },
    GenericErrorTree::Stack{  contexts, ..}=> { 
    let context = contexts[0];
    let details = Location::recreate_context(content, context.0); 
      ParserError{
        line: details.line, 
        column: details.column,
        source: content.to_string(),
        remainder: context.0.to_string(),
        message: context.1.to_string()
      }
    },
    GenericErrorTree::Alt(items) => { 
      get_error(content, &items[0])
    },
  }
}
Output:
Error: in section "should be slow" - line 1 - column: 4
AT:  quick brown fox
FROM: the quick brown fox
-- end of line --