Composing parsers in nom

I'm currently trying to parse a small domain-specific language and am looking into parser combinator crates. It seems like nom is a popular one but I'm struggling to understand how I can use it for my use case.
As I understand it, one makes many small parser functions for basic parsing and then composes these to form a higher logic (sorry if I'm nor using the proper syntax for these terms).
So currently I have a subproblem like this:
I want to parse strings like this: "1,2,3,4-7,9:22"
which can contain an arbitrary number of comma-separated values. The values delimited by a hyphen or colon represent ranges.
So far I wrote this:

fn range_sep(input: &str) -> IResult<&str, &str> {
    recognize(alt((char('-'), char(':'))))(input)
}

fn range(input: &str) -> IResult<&str, &str> {
    let test = tuple((digit1, range_sep, digit1))(input)?;
    let string = test.1.0.to_owned() + test.1.1 + test.1.2;
    Ok(&string)
}

fn id_with_opt_range(input: &str) -> IResult<&str, &str> {
    alt((digit1, range))(input)
}

fn num_list(input: &str) -> IResult<&str, Vec<&str>> {
    separated_list1(char(','), id_with_opt_range)(input)
}

This is probably overly verbose but I'm just starting out. As can be seen, first the separators for the potential ranges are parsed, then the ranges, then the full list which can contain eithers digits or a range.
However, the range function doesn't compile because I can't return a &str from it since it is a borrow created in the same function. On the other hand, I can't return the tuple because id_with_opt_range expects a &str as input.
Since each small parser hands its results to the next one, they all need to take &str as input as far as I can see but maybe I misunderstood the concept here.
I would be grateful for any pointers as to what I can do here. Also, if anyone can suggest an accessible tutorial or some such for this crate (or another, possibly more suited one) I'd be grateful as well, I find it difficult to wrap my head around this.

The problem here is that range() is parsing, and then undoing the work of the parser by building the parsed tuple back into a string. It might be more useful if you aim at returning something like a Vec<(i32, i32)>, so that you have parsed number ranges you can use later, rather than a list of strings that just happen to look like number ranges. In that case range() may be as follows:

fn range(input: &str) -> IResult<&str, (i32, i32)> {
    separated_pair((i32, range_sep, i32))(input)
}

where i32 is nom::character::complete::i32()

However, if you do in fact want a list of strings of a particular format, then just wrap the parser in range() in a recognize() to get the string from the input rather than trying to reconstruct it yourself.

Thanks for your input. If I change range like you suggested, I can't use it in id_with_opt_range anymore though because the alt function needs to have closure or functions of the same return type, if I'm not mistaken.
Your suggestions makes sense to me but I don't see how to compose the parser functions if their return types differ.

Sorry, maybe I should have been more explicit. I was assuming that the other case in id_with_opt_range() would be changed to something returning the same type. If what you want out of it is an (i32, i32) then something like map(i32, |n| (n,n)) should work to parse a single number and lift it to a range. If you want to distinguish the single number and range cases in your output, then your parsing should output an enum with a variant for each case instead.

The output type of num_list() would need to change to be Vec<(i32, i32)> as well.

Okay, I'll think about what I want to return and try and implement it then.
Thanks again!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.