use crate::{grammar::value, syntax_error::SyntaxError, syntax_kind::SyntaxKind}; use super::{CompletedMarker, Parser, BASIC_VALUE_TOKENS}; pub(super) fn object(p: &mut Parser) -> Option { let obj_start = p.start("object"); if !p.eat(SyntaxKind::BRACE_OPEN) { obj_start.abandon(p); return None; } member(p); while p.at(SyntaxKind::COMMA) { // not always an error, later configurable let potential_trailing_comma = p.start("potential_trailing_comma"); p.eat(SyntaxKind::COMMA); if member(p).is_none() { potential_trailing_comma.complete(p, SyntaxKind::TRAILING_COMMA); } else { potential_trailing_comma.abandon(p); } } Some(if p.eat(SyntaxKind::BRACE_CLOSE) { obj_start.complete(p, SyntaxKind::OBJECT) } else { obj_start.error(p, SyntaxError::UnclosedObject) }) } fn member(p: &mut Parser) -> Option { let member_start = p.start("member"); if p.at(SyntaxKind::BRACE_CLOSE) { member_start.abandon(p); return None; } else if p.at(SyntaxKind::STRING) { let member_name_start = p.start("member_name"); p.eat(SyntaxKind::STRING); member_name_start.complete(p, SyntaxKind::MEMBER_NAME); } else { return todo!("handle other tokens: {:?}", p.current()); } if !p.eat(SyntaxKind::COLON) { todo!("handle wrong tokens") } let member_value_start = p.start("member_value_start"); if value(p) { member_value_start.complete(p, SyntaxKind::MEMBER_VALUE); Some(member_start.complete(p, SyntaxKind::MEMBER)) } else { member_value_start.abandon(p); let e = member_start.error(p, SyntaxError::MemberMissingValue); Some( e.precede(p, "member but failed already") .complete(p, SyntaxKind::MEMBER), ) } } #[cfg(test)] mod tests { use crate::grammar::{ object::{member, object}, test_utils::gen_checks, }; #[test] fn object_basic() { gen_checks! {object; r#"{"a": "b"}"# => r#"ROOT { OBJECT { BRACE_OPEN "{"; MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { STRING "\"b\""; } } BRACE_CLOSE "}"; } }"#, r#"{"a": 42}"# => r#"ROOT { OBJECT { BRACE_OPEN "{"; MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { NUMBER "42"; } } BRACE_CLOSE "}"; } }"#, r#"{"a": "b""# => r#"ROOT { PARSE_ERR: UnclosedObject { BRACE_OPEN "{"; MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { STRING "\"b\""; } } } }"#, r#"{"a": }"# => r#"ROOT { OBJECT { BRACE_OPEN "{"; MEMBER { PARSE_ERR: MemberMissingValue { MEMBER_NAME { STRING "\"a\""; } COLON ":"; } } WHITESPACE " "; BRACE_CLOSE "}"; } }"#, r#"{"a":"# => r#"ROOT { PARSE_ERR: UnclosedObject { BRACE_OPEN "{"; MEMBER { PARSE_ERR: MemberMissingValue { MEMBER_NAME { STRING "\"a\""; } COLON ":"; } } } }"#, r#"{"a":true,}"# => r#"ROOT { OBJECT { BRACE_OPEN "{"; MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; MEMBER_VALUE { BOOL "true"; } } TRAILING_COMMA { COMMA ","; } BRACE_CLOSE "}"; } }"# } } #[test] fn member_basic() { gen_checks! {member; r#""a": "b""# => r#"ROOT { MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { STRING "\"b\""; } } }"#, r#""a": 42"# => r#"ROOT { MEMBER { MEMBER_NAME { STRING "\"a\""; } COLON ":"; WHITESPACE " "; MEMBER_VALUE { NUMBER "42"; } } }"#, r#""a":"# => r#"ROOT { MEMBER { PARSE_ERR: MemberMissingValue { MEMBER_NAME { STRING "\"a\""; } COLON ":"; } } }"# } } }