From 37f2104224965a4238d5d1e55eb5ed8a186d17f2 Mon Sep 17 00:00:00 2001 From: Sven Thiele Date: Sun, 3 Dec 2023 11:45:32 +0100 Subject: [PATCH] Add some cuts and other tweaks --- README.md | 2 +- examples/fz-parser.rs | 15 ++++++++------- src/basic_types.rs | 8 ++++---- src/comments.rs | 11 +++++++++-- src/constraints.rs | 35 +++++++++++++++++++++++++--------- src/expressions.rs | 12 ++++++------ src/lib.rs | 2 +- src/parameters/declarations.rs | 7 +++++-- src/parameters/types.rs | 11 +++++------ src/predicates/declarations.rs | 17 +++++++++++++---- src/predicates/types.rs | 15 +++++++-------- src/primitive_literals.rs | 14 +++++++++----- src/solve_items.rs | 20 +++++++++++++------ src/statements/mod.rs | 11 +++++++++-- src/variables/declarations.rs | 3 ++- src/variables/types.rs | 25 +++++++++++++----------- 16 files changed, 133 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index fff03c9..590b1b6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ In your code: ```rust use flatzinc::*; -match flatzinc::model::>(&buf) { +match flatzinc::model::>(&buf) { Ok(result) => println!("{:#?}", result), Err(e) => { error!("Failed to parse flatzinc!\n{}", e) diff --git a/examples/fz-parser.rs b/examples/fz-parser.rs index 945c3a0..7e5fcc6 100644 --- a/examples/fz-parser.rs +++ b/examples/fz-parser.rs @@ -1,12 +1,13 @@ use anyhow::Result; -use clap::Parser as clapParser; +use clap::Parser; +use flatzinc::statements::parse_statement; use log::error; use std::path::PathBuf; use stderrlog; -use winnow::{error::ContextError, Parser}; +use winnow::error::InputError; /// flatzinc parser -#[derive(clapParser, Debug)] +#[derive(Parser, Debug)] #[clap(name = "fz-parser")] struct Opt { /// Input in flatzinc format @@ -29,12 +30,12 @@ fn run() -> Result<()> { let opt = Opt::parse(); let buf = std::fs::read_to_string(opt.file)?; - let mut parser = flatzinc::statement::, &str>(); - for line in buf.lines() { - match parser.parse(line) { + for mut line in buf.lines() { + match parse_statement(&mut line) { Ok(result) => println!("{:#?}", result), Err(e) => { - error!("Failed to parse flatzinc!\n{:?}", e) + let y: InputError<&str> = e.into_inner().unwrap(); + error!("Failed to parse flatzinc!\n{:?}", y); } } } diff --git a/src/basic_types.rs b/src/basic_types.rs index 32e502b..62549e4 100644 --- a/src/basic_types.rs +++ b/src/basic_types.rs @@ -1,4 +1,4 @@ -use winnow::{combinator::alt, error::ParserError, token::tag, PResult, Parser}; +use winnow::{combinator::alt, error::ParserError, PResult, Parser}; #[derive(PartialEq, Clone, Debug)] pub enum BasicType { @@ -13,16 +13,16 @@ pub fn basic_type<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult>(input: &mut &'a str) -> PResult { - tag("bool").parse_next(input)?; + "bool".parse_next(input)?; Ok(BasicType::Bool) } fn int<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult { - tag("int").parse_next(input)?; + "int".parse_next(input)?; Ok(BasicType::Int) } fn float<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult { - tag("float").parse_next(input)?; + "float".parse_next(input)?; Ok(BasicType::Float) } diff --git a/src/comments.rs b/src/comments.rs index f3af1cb..fb7d5cb 100644 --- a/src/comments.rs +++ b/src/comments.rs @@ -1,7 +1,6 @@ use winnow::{ ascii::{multispace0, multispace1}, - combinator::alt, - combinator::opt, + combinator::{alt, eof, opt}, error::ParserError, token::take_till0, PResult, Parser, @@ -11,6 +10,7 @@ use crate::statements::Stmt; pub fn space_or_comment<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult { let s = space_or_comment0(input)?; + eof.parse_next(input)?; Ok(Stmt::Comment(s.into())) } pub fn space_or_comment0<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult<&'a str, E> { @@ -36,3 +36,10 @@ fn test_comment() { Ok(" Comments can have anyth!ng in it really <3".into()) ); } +#[test] +fn test_comment2() { + use winnow::error::ContextError; + let mut input = "5 % Comments can have anyth!ng in it really <3"; + let res = comment::>(&mut input); + assert!(res.is_err()); +} diff --git a/src/constraints.rs b/src/constraints.rs index 7acdb7a..5c04a2c 100644 --- a/src/constraints.rs +++ b/src/constraints.rs @@ -1,15 +1,13 @@ -use winnow::{ - combinator::separated, - error::{FromExternalError, ParserError}, - token::tag, - PResult, Parser, -}; - use crate::{ comments::{space_or_comment0, space_or_comment1}, expressions::{annotations, expr, Annotation, Expr}, primitive_literals::identifier, }; +use winnow::{ + combinator::{eof, separated}, + error::{FromExternalError, ParserError}, + PResult, Parser, +}; #[derive(PartialEq, Clone, Debug)] pub struct ConstraintItem { @@ -26,7 +24,17 @@ where + FromExternalError<&'a str, std::num::ParseFloatError>, { space_or_comment0(input)?; - tag("constraint").parse_next(input)?; + "constraint".parse_next(input)?; + let item = constraint_tail(input).map_err(|e| e.cut())?; + Ok(item) +} +pub fn constraint_tail<'a, E: ParserError<&'a str>>( + input: &mut &'a str, +) -> PResult +where + E: FromExternalError<&'a str, std::num::ParseIntError> + + FromExternalError<&'a str, std::num::ParseFloatError>, +{ space_or_comment1(input)?; let id = identifier(input)?; '('.parse_next(input)?; @@ -39,10 +47,11 @@ where space_or_comment0(input)?; ';'.parse_next(input)?; space_or_comment0(input)?; + eof.parse_next(input)?; Ok(ConstraintItem { id, exprs, annos }) } #[test] -fn test_constraint_item() { +fn test_constraint_item_1() { use crate::{AnnExpr, Annotation, Expr, IntExpr, SetLiteralExpr}; use winnow::error::ContextError; let mut input = "constraint set_in_reif(X_26,1..2,X_52):: defines_var(X_52);"; @@ -171,3 +180,11 @@ fn test_constraint_item_5() { }) ); } +#[test] +fn test_constraint_item_6() { + use winnow::error::InputError; + let mut input = "constraintX int_lin_le_reif(X_INTRODUCED_22_,[X_INTRODUCED_7_,X_INTRODUCED_8_],,-2,X_INTRODUCED_58_):: defines_var(X_INTRODUCED_58_);"; + let res = constraint_item::>(&mut input); + assert!(res.is_err()); + assert_eq!("Err(Cut(InputError { input: \"X int_lin_le_reif(X_INTRODUCED_22_,[X_INTRODUCED_7_,X_INTRODUCED_8_],,-2,X_INTRODUCED_58_):: defines_var(X_INTRODUCED_58_);\", kind: Slice }))", format!("{:?}", res)); +} diff --git a/src/expressions.rs b/src/expressions.rs index d0fdae3..836cd8c 100644 --- a/src/expressions.rs +++ b/src/expressions.rs @@ -2,7 +2,7 @@ use winnow::{ ascii::multispace1, combinator::{alt, delimited, fold_repeat, opt, preceded, repeat, separated}, error::{FromExternalError, ParserError}, - token::{tag, take_till1, take_while}, + token::{take_till1, take_while}, PResult, Parser, }; @@ -28,7 +28,7 @@ where E: FromExternalError<&'a str, std::num::ParseIntError> + FromExternalError<&'a str, std::num::ParseFloatError>, { - tag("::").parse_next(input)?; + "::".parse_next(input)?; space_or_comment0(input)?; annotation(input) } @@ -526,7 +526,7 @@ where { let lb = int_expr(input)?; space_or_comment0(input)?; - tag("..").parse_next(input)?; + "..".parse_next(input)?; space_or_comment0(input)?; let ub = int_expr(input)?; Ok(SetLiteralExpr::IntInRange(lb, ub)) @@ -538,7 +538,7 @@ where { let lb = float_expr(input)?; space_or_comment0(input)?; - tag("..").parse_next(input)?; + "..".parse_next(input)?; space_or_comment0(input)?; let ub = float_expr(input)?; Ok(SetLiteralExpr::BoundedFloat(lb, ub)) @@ -599,7 +599,7 @@ where { let lb = int_literal(input)?; space_or_comment0(input)?; - tag("..").parse_next(input)?; + "..".parse_next(input)?; space_or_comment0(input)?; let ub = int_literal(input)?; Ok(SetLiteral::IntRange(lb, ub)) @@ -611,7 +611,7 @@ where { let lb = float_literal(input)?; space_or_comment0(input)?; - tag("..").parse_next(input)?; + "..".parse_next(input)?; space_or_comment0(input)?; let ub = float_literal(input)?; Ok(SetLiteral::BoundedFloat(lb, ub)) diff --git a/src/lib.rs b/src/lib.rs index 0586e65..cadfe85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ pub use predicates::{ }; pub use primitive_literals::IndexSet; pub use solve_items::{Goal, OptimizationType, SolveItem}; -pub use statements::{statement, Stmt}; +pub use statements::{parse_statement, Stmt}; pub use variables::{declarations::VarDeclItem, types::BasicVarType}; pub mod basic_types; diff --git a/src/parameters/declarations.rs b/src/parameters/declarations.rs index 5195766..3e9a987 100644 --- a/src/parameters/declarations.rs +++ b/src/parameters/declarations.rs @@ -1,4 +1,5 @@ use winnow::{ + combinator::eof, error::{FromExternalError, ParserError}, PResult, Parser, }; @@ -69,7 +70,7 @@ where space_or_comment0(input)?; '='.parse_next(input)?; space_or_comment0(input)?; - match ptype { + let item = match ptype { ParType::BasicParType(bpt) => match bpt { BasicParType::BasicType(bt) => match bt { BasicType::Bool => { @@ -134,7 +135,9 @@ where Ok(ParDeclItem::ArrayOfSet { ix, id, v }) } }, - } + }; + eof.parse_next(input)?; + item } #[test] fn test_par_decl_item_1() { diff --git a/src/parameters/types.rs b/src/parameters/types.rs index 762cf6e..2be6938 100644 --- a/src/parameters/types.rs +++ b/src/parameters/types.rs @@ -1,7 +1,6 @@ use winnow::{ combinator::alt, error::{FromExternalError, ParserError}, - token::tag, PResult, Parser, }; @@ -31,11 +30,11 @@ fn bpt_basic_type<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult>(input: &mut &'a str) -> PResult { - tag("set").parse_next(input)?; + "set".parse_next(input)?; space_or_comment1(input)?; - tag("of").parse_next(input)?; + "of".parse_next(input)?; space_or_comment1(input)?; - tag("int").parse_next(input)?; + "int".parse_next(input)?; Ok(BasicParType::SetOfInt) } @@ -77,7 +76,7 @@ fn array_par_type<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult

, { - tag("array").parse_next(input)?; + "array".parse_next(input)?; space_or_comment1(input)?; '['.parse_next(input)?; space_or_comment0(input)?; @@ -85,7 +84,7 @@ where space_or_comment0(input)?; ']'.parse_next(input)?; space_or_comment1(input)?; - tag("of").parse_next(input)?; + "of".parse_next(input)?; space_or_comment1(input)?; let par_type = basic_par_type(input)?; Ok(ParType::Array { ix, par_type }) diff --git a/src/predicates/declarations.rs b/src/predicates/declarations.rs index 499c049..43ed088 100644 --- a/src/predicates/declarations.rs +++ b/src/predicates/declarations.rs @@ -1,7 +1,6 @@ use winnow::{ - combinator::separated, + combinator::{eof, separated}, error::{FromExternalError, ParserError}, - token::tag, PResult, Parser, }; @@ -23,7 +22,17 @@ where + FromExternalError<&'a str, std::num::ParseFloatError>, { space_or_comment0(input)?; - tag("predicate").parse_next(input)?; + "predicate".parse_next(input)?; + let item = predicate_item_tail(input).map_err(|e| e.cut())?; + Ok(item) +} +pub fn predicate_item_tail<'a, E: ParserError<&'a str>>( + input: &mut &'a str, +) -> PResult +where + E: FromExternalError<&'a str, std::num::ParseIntError> + + FromExternalError<&'a str, std::num::ParseFloatError>, +{ space_or_comment1(input)?; let id = identifier(input)?; '('.parse_next(input)?; @@ -32,9 +41,9 @@ where space_or_comment0(input)?; ';'.parse_next(input)?; space_or_comment0(input)?; + eof.parse_next(input)?; Ok(PredicateItem { id, parameters }) } - #[test] fn test_predicate_item() { use crate::predicates::types::BasicPredParType; diff --git a/src/predicates/types.rs b/src/predicates/types.rs index a2323e2..ef898c0 100644 --- a/src/predicates/types.rs +++ b/src/predicates/types.rs @@ -1,7 +1,6 @@ use winnow::{ combinator::alt, error::{FromExternalError, ParserError}, - token::tag, PResult, Parser, }; @@ -82,13 +81,13 @@ fn bppt_var_set_of_int<'a, E: ParserError<&'a str>>( input: &mut &'a str, ) -> PResult { space_or_comment0(input)?; - tag("var").parse_next(input)?; + "var".parse_next(input)?; space_or_comment1(input)?; - tag("set").parse_next(input)?; + "set".parse_next(input)?; space_or_comment1(input)?; - tag("of").parse_next(input)?; + "of".parse_next(input)?; space_or_comment1(input)?; - tag("int").parse_next(input)?; + "int".parse_next(input)?; space_or_comment0(input)?; Ok(BasicPredParType::VarSetOfInt) } @@ -223,7 +222,7 @@ where + FromExternalError<&'a str, std::num::ParseFloatError>, { space_or_comment0(input)?; - tag("array").parse_next(input)?; + "array".parse_next(input)?; space_or_comment1(input)?; '['.parse_next(input)?; space_or_comment0(input)?; @@ -231,7 +230,7 @@ where space_or_comment0(input)?; ']'.parse_next(input)?; space_or_comment1(input)?; - tag("of").parse_next(input)?; + "of".parse_next(input)?; space_or_comment1(input)?; let par_type = basic_pred_par_type(input)?; Ok(PredParType::Array { ix, par_type }) @@ -251,7 +250,7 @@ where } fn pis_int<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult { - tag("int").parse_next(input)?; + "int".parse_next(input)?; Ok(PredIndexSet::Int) } diff --git a/src/primitive_literals.rs b/src/primitive_literals.rs index fd06d5b..1b9d6be 100644 --- a/src/primitive_literals.rs +++ b/src/primitive_literals.rs @@ -5,6 +5,8 @@ use winnow::{ PResult, Parser, }; +use crate::comments::space_or_comment0; + pub fn identifier<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult { let first = one_of([ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', @@ -230,7 +232,7 @@ where E: FromExternalError<&'a str, std::num::ParseIntError>, { let negation = opt('-').parse_next(input)?; - tag("0x").parse_next(input)?; + "0x".parse_next(input)?; let int = take_while(1.., is_hex_digit).parse_next(input)?; let int = i128::from_str_radix(int, 16) .map_err(|e| winnow::error::ErrMode::from_external_error(input, ErrorKind::Verify, e))?; @@ -253,7 +255,7 @@ where E: FromExternalError<&'a str, std::num::ParseIntError>, { let negation = opt('-').parse_next(input)?; - tag("0o").parse_next(input)?; + "0o".parse_next(input)?; let int = take_while(1.., is_oct_digit).parse_next(input)?; let int = i128::from_str_radix(int, 8) .map_err(|e| winnow::error::ErrMode::from_external_error(input, ErrorKind::Verify, e))?; @@ -349,7 +351,7 @@ fn fz_float2<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult<&'a st let sign = opt('-'); let digits = take_while(1.., is_dec_digit); let e = alt(('e', 'E')); - let sign2 = opt(alt((tag("-"), tag("+")))); + let sign2 = opt(alt(("-", "+"))); let digits2 = take_while(1.., is_dec_digit); (sign, digits, e, sign2, digits2) .recognize() @@ -358,7 +360,7 @@ fn fz_float2<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult<&'a st fn bpart<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult { let e = alt(('e', 'E')).parse_next(input)?; - let sign = opt(alt((tag("-"), tag("+")))).parse_next(input)?; + let sign = opt(alt(("-", "+"))).parse_next(input)?; let digits = take_while(1.., is_dec_digit).parse_next(input)?; if let Some(sign) = sign { Ok(format!("{}{}{}", e, sign, digits)) @@ -375,7 +377,9 @@ where E: FromExternalError<&'a str, std::num::ParseIntError>, { '1'.parse_next(input)?; - tag("..").parse_next(input)?; + space_or_comment0(input)?; + "..".parse_next(input)?; + space_or_comment0(input)?; let int = int_literal.parse_next(input)?; Ok(IndexSet(int)) } diff --git a/src/solve_items.rs b/src/solve_items.rs index 64bf5b9..9f953df 100644 --- a/src/solve_items.rs +++ b/src/solve_items.rs @@ -1,7 +1,6 @@ use winnow::{ - combinator::alt, + combinator::{alt, eof}, error::{FromExternalError, ParserError}, - token::tag, PResult, Parser, }; @@ -25,7 +24,15 @@ where + FromExternalError<&'a str, std::num::ParseFloatError>, { space_or_comment0(input)?; - tag("solve").parse_next(input)?; + "solve".parse_next(input)?; + let item = solve_item_tail(input).map_err(|e| e.cut())?; + Ok(item) +} +pub fn solve_item_tail<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult +where + E: FromExternalError<&'a str, std::num::ParseIntError> + + FromExternalError<&'a str, std::num::ParseFloatError>, +{ space_or_comment1(input)?; let annotations = annotations(input)?; space_or_comment0(input)?; @@ -40,6 +47,7 @@ where space_or_comment0(input)?; ';'.parse_next(input)?; space_or_comment0(input)?; + eof.parse_next(input)?; Ok(SolveItem { goal, annotations }) } #[test] @@ -84,7 +92,7 @@ pub enum OptimizationType { } pub fn satisfy<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult { - tag("satisfy").parse_next(input)?; + "satisfy".parse_next(input)?; Ok(Goal::Satisfy) } @@ -93,12 +101,12 @@ fn opt_type<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult>(input: &mut &'a str) -> PResult { - tag("minimize").parse_next(input)?; + "minimize".parse_next(input)?; Ok(OptimizationType::Minimize) } fn maximize<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult { - tag("maximize").parse_next(input)?; + "maximize".parse_next(input)?; Ok(OptimizationType::Maximize) } diff --git a/src/statements/mod.rs b/src/statements/mod.rs index ff1317b..2349419 100644 --- a/src/statements/mod.rs +++ b/src/statements/mod.rs @@ -1,5 +1,5 @@ use winnow::{ - combinator::{alt, eof}, + combinator::alt, error::{FromExternalError, ParserError}, PResult, Parser, }; @@ -26,7 +26,7 @@ pub enum Stmt { SolveItem(SolveItem), } -pub fn statement<'a, E: ParserError<&'a str>, I>() -> impl Parser<&'a str, Stmt, E> +fn statement<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Stmt, E> where E: FromExternalError<&'a str, std::num::ParseIntError> + FromExternalError<&'a str, std::num::ParseFloatError>, @@ -41,6 +41,13 @@ where )) } +pub fn parse_statement<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult +where + E: FromExternalError<&'a str, std::num::ParseIntError> + + FromExternalError<&'a str, std::num::ParseFloatError>, +{ + statement::().parse_next(input) +} fn stmt_predicate<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PResult where E: FromExternalError<&'a str, std::num::ParseIntError> diff --git a/src/variables/declarations.rs b/src/variables/declarations.rs index 0accae5..4490e54 100644 --- a/src/variables/declarations.rs +++ b/src/variables/declarations.rs @@ -1,5 +1,5 @@ use winnow::{ - combinator::opt, + combinator::{eof, opt}, error::{FromExternalError, ParserError}, PResult, Parser, }; @@ -147,6 +147,7 @@ where space_or_comment0(input)?; ';'.parse_next(input)?; space_or_comment0(input)?; + eof.parse_next(input)?; Ok(item) } #[test] diff --git a/src/variables/types.rs b/src/variables/types.rs index dbd9c33..fd3f7bd 100644 --- a/src/variables/types.rs +++ b/src/variables/types.rs @@ -2,7 +2,6 @@ use winnow::{ combinator::alt, combinator::separated, error::{FromExternalError, ParserError}, - token::tag, PResult, Parser, }; @@ -43,7 +42,7 @@ where E: FromExternalError<&'a str, std::num::ParseIntError> + FromExternalError<&'a str, std::num::ParseFloatError>, { - tag("array").parse_next(input)?; + "array".parse_next(input)?; space_or_comment0(input)?; '['.parse_next(input)?; space_or_comment0(input)?; @@ -51,7 +50,7 @@ where space_or_comment0(input)?; ']'.parse_next(input)?; space_or_comment1(input)?; - tag("of").parse_next(input)?; + "of".parse_next(input)?; space_or_comment1(input)?; let var_type = basic_var_type(input)?; Ok(VarType::Array { ix, var_type }) @@ -73,7 +72,7 @@ where + FromExternalError<&'a str, std::num::ParseFloatError>, { space_or_comment0(input)?; - tag("var").parse_next(input)?; + "var".parse_next(input)?; space_or_comment1(input)?; let vt = alt(( bvt_basic_type, @@ -142,7 +141,7 @@ where { let lb = int_literal(input)?; space_or_comment0(input)?; - tag("..").parse_next(input)?; + "..".parse_next(input)?; space_or_comment0(input)?; let ub = int_literal(input)?; Ok((lb, ub)) @@ -154,7 +153,7 @@ where { let lb = float_literal(input)?; space_or_comment0(input)?; - tag("..").parse_next(input)?; + "..".parse_next(input)?; space_or_comment0(input)?; let ub = float_literal(input)?; Ok((lb, ub)) @@ -180,13 +179,13 @@ pub fn subset_of_int_range<'a, E: ParserError<&'a str>>( where E: FromExternalError<&'a str, std::num::ParseIntError>, { - tag("set").parse_next(input)?; + "set".parse_next(input)?; space_or_comment1(input)?; - tag("of").parse_next(input)?; + "of".parse_next(input)?; space_or_comment1(input)?; let lb = int_literal(input)?; space_or_comment0(input)?; - tag("..").parse_next(input)?; + "..".parse_next(input)?; space_or_comment0(input)?; let ub = int_literal(input)?; Ok((lb, ub)) @@ -197,11 +196,15 @@ pub fn subset_of_int_set<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> PR where E: FromExternalError<&'a str, std::num::ParseIntError>, { - tag("set of {").parse_next(input)?; + "set".parse_next(input)?; + space_or_comment1(input)?; + "of".parse_next(input)?; + space_or_comment1(input)?; + '{'.parse_next(input)?; space_or_comment0(input)?; let v = separated(0.., int_literal, ',').parse_next(input)?; space_or_comment0(input)?; - tag("}").parse_next(input)?; + '}'.parse_next(input)?; Ok(v) }