92 lines
3.7 KiB
Rust
92 lines
3.7 KiB
Rust
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<CompletedMarker> {
|
|
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<CompletedMarker> {
|
|
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 ":"; } } }"#
|
|
}
|
|
}
|
|
}
|