diff --git a/protobuf-parse/src/pure/model.rs b/protobuf-parse/src/pure/model.rs index 8e2cb5fa9..25a57de47 100644 --- a/protobuf-parse/src/pure/model.rs +++ b/protobuf-parse/src/pure/model.rs @@ -426,11 +426,11 @@ impl fmt::Display for ProtobufConstant { impl ProtobufConstantMessage { pub fn format(&self) -> String { let mut s = String::new(); - write!(s, "{{").unwrap(); + write!(s, "{{ ").unwrap(); for (n, v) in &self.fields { match v { ProtobufConstant::Message(m) => write!(s, "{} {}", n, m.format()).unwrap(), - v => write!(s, "{}: {}", n, v.format()).unwrap(), + v => write!(s, "{}: {} ", n, v.format()).unwrap(), } } write!(s, "}}").unwrap(); diff --git a/protobuf-parse/src/pure/parser.rs b/protobuf-parse/src/pure/parser.rs index a4f16e87a..a61a110b1 100644 --- a/protobuf-parse/src/pure/parser.rs +++ b/protobuf-parse/src/pure/parser.rs @@ -377,6 +377,18 @@ impl<'a> Parser<'a> { while !self.tokenizer.lookahead_is_symbol('}')? { let n = self.next_message_constant_field_name()?; let v = self.next_field_value()?; + + // Consume the comma or semicolon if present. Commas and semicolons + // between message fields are optional, all these are valid: + // + // {foo: 1,bar: 2,baz: 3,} + // {foo: 1;bar: 2;baz: 3;} + // {foo: 1 bar: 2 baz: 3} + // {foo: 1,bar: 2;baz: 3} + // {foo: 1,bar: 2 baz: 3} + // + self.tokenizer.next_symbol_if_in(&[',', ';'])?; + r.fields.insert(n, v); } self.tokenizer @@ -1336,6 +1348,29 @@ mod test { assert_eq!("10", mess.t.options[0].value.format()); } + #[test] + fn test_field_options() { + let msg = r#" (my_opt).my_field = {foo: 1 bar: 2} "#; + let opt = parse(msg, |p| p.next_field_option()); + assert_eq!(r#"{ foo: 1 bar: 2 }"#, opt.value.format()); + + let msg = r#" (my_opt).my_field = {foo: 1; bar:2;} "#; + let opt = parse(msg, |p| p.next_field_option()); + assert_eq!(r#"{ foo: 1 bar: 2 }"#, opt.value.format()); + + let msg = r#" (my_opt).my_field = {foo: 1, bar: 2} "#; + let opt = parse(msg, |p| p.next_field_option()); + assert_eq!(r#"{ foo: 1 bar: 2 }"#, opt.value.format()); + + let msg = r#" (my_opt).my_field = "foo" "#; + let opt = parse(msg, |p| p.next_field_option()); + assert_eq!(r#""foo""#, opt.value.format()); + + let msg = r#" (my_opt) = { my_field: "foo"} "#; + let opt = parse(msg, |p| p.next_field_option()); + assert_eq!(r#"{ my_field: "foo" }"#, opt.value.format()); + } + #[test] fn test_message() { let msg = r#"message ReferenceData diff --git a/protobuf-support/src/lexer/tokenizer.rs b/protobuf-support/src/lexer/tokenizer.rs index c5e84a0a9..5541b1d2d 100644 --- a/protobuf-support/src/lexer/tokenizer.rs +++ b/protobuf-support/src/lexer/tokenizer.rs @@ -194,11 +194,16 @@ impl<'a> Tokenizer<'a> { Ok(()) } - pub fn next_symbol_if_eq(&mut self, symbol: char) -> TokenizerResult { - Ok(self.next_token_if(|token| match token { - &Token::Symbol(c) if c == symbol => true, + pub fn next_symbol_if_in(&mut self, symbols: &[char]) -> TokenizerResult { + self.next_token_if(|token| match token { + Token::Symbol(c) if symbols.contains(c) => true, _ => false, - })? != None) + }) + .map(|token| token.is_some()) + } + + pub fn next_symbol_if_eq(&mut self, symbol: char) -> TokenizerResult { + self.next_symbol_if_in(&[symbol]) } pub fn next_symbol_expect_eq(