use std::collections::BTreeMap;
use reblessive::Stk;
use crate::{
sql::{Block, Geometry, Object, Strand, Value},
syn::{
error::bail,
lexer::compound,
parser::{enter_object_recursion, mac::expected, ParseResult, Parser},
token::{t, Glued, Span, TokenKind},
},
};
use super::mac::unexpected;
impl Parser<'_> {
pub(super) async fn parse_object_like(
&mut self,
ctx: &mut Stk,
start: Span,
) -> ParseResult<Value> {
if self.eat(t!("}")) {
enter_object_recursion!(_this = self => {
return Ok(Value::Object(Object::default()));
})
}
if self.glue_and_peek1()?.kind == t!(":") {
enter_object_recursion!(this = self => {
return this.parse_object_or_geometry(ctx, start).await;
})
}
self.parse_block(ctx, start).await.map(Box::new).map(Value::Block)
}
async fn parse_object_or_geometry_after_type(
&mut self,
ctx: &mut Stk,
start: Span,
key: String,
) -> ParseResult<Value> {
expected!(self, t!(":"));
let (t!("\"") | t!("'") | TokenKind::Glued(Glued::Strand)) = self.peek_kind() else {
return self
.parse_object_from_key(ctx, key, BTreeMap::new(), start)
.await
.map(Value::Object);
};
let type_value = self.next_token_value::<Strand>()?.0;
match type_value.as_str() {
"Point" => {
self.parse_geometry_after_type(
ctx,
start,
key,
type_value,
Geometry::array_to_point,
|x| Value::Geometry(Geometry::Point(x)),
)
.await
}
"LineString" => {
self.parse_geometry_after_type(
ctx,
start,
key,
type_value,
Geometry::array_to_line,
|x| Value::Geometry(Geometry::Line(x)),
)
.await
}
"Polygon" => {
self.parse_geometry_after_type(
ctx,
start,
key,
type_value,
Geometry::array_to_polygon,
|x| Value::Geometry(Geometry::Polygon(x)),
)
.await
}
"MultiPoint" => {
self.parse_geometry_after_type(
ctx,
start,
key,
type_value,
Geometry::array_to_multipoint,
|x| Value::Geometry(Geometry::MultiPoint(x)),
)
.await
}
"MultiLineString" => {
self.parse_geometry_after_type(
ctx,
start,
key,
type_value,
Geometry::array_to_multiline,
|x| Value::Geometry(Geometry::MultiLine(x)),
)
.await
}
"MultiPolygon" => {
self.parse_geometry_after_type(
ctx,
start,
key,
type_value,
Geometry::array_to_multipolygon,
|x| Value::Geometry(Geometry::MultiPolygon(x)),
)
.await
}
"GeometryCollection" => {
if !self.eat(t!(",")) {
return self
.parse_object_from_map(
ctx,
BTreeMap::from([(key, Value::Strand(type_value.into()))]),
start,
)
.await
.map(Value::Object);
}
let coord_key = self.parse_object_key()?;
if coord_key != "geometries" {
expected!(self, t!(":"));
return self
.parse_object_from_key(
ctx,
coord_key,
BTreeMap::from([(key, Value::Strand(type_value.into()))]),
start,
)
.await
.map(Value::Object);
}
expected!(self, t!(":"));
let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
if !self.eat(t!(",")) {
self.expect_closing_delimiter(t!("}"), start)?;
} else {
if self.peek_kind() != t!("}") {
return self
.parse_object_from_map(
ctx,
BTreeMap::from([
(key, Value::Strand(type_value.into())),
(coord_key, value),
]),
start,
)
.await
.map(Value::Object);
}
self.pop_peek();
}
if let Value::Array(x) = value {
if x.iter().all(|x| matches!(x, Value::Geometry(_))) {
let geometries =
x.0.into_iter()
.map(|x| {
if let Value::Geometry(x) = x {
x
} else {
unreachable!()
}
})
.collect();
return Ok(Value::Geometry(Geometry::Collection(geometries)));
}
return Ok(Value::Object(Object(BTreeMap::from([
(key, Value::Strand(type_value.into())),
(coord_key, Value::Array(x)),
]))));
}
Ok(Value::Object(Object(BTreeMap::from([
(key, Value::Strand(type_value.into())),
(coord_key, value),
]))))
}
_ => {
let object = BTreeMap::from([(key, Value::Strand(type_value.into()))]);
if self.eat(t!(",")) {
self.parse_object_from_map(ctx, object, start).await.map(Value::Object)
} else {
self.expect_closing_delimiter(t!("}"), start)?;
Ok(Value::Object(Object(object)))
}
}
}
}
async fn parse_object_or_geometry_after_coordinates(
&mut self,
ctx: &mut Stk,
start: Span,
key: String,
) -> ParseResult<Value> {
expected!(self, t!(":"));
let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
if !self.eat(t!(",")) {
self.expect_closing_delimiter(t!("}"), start)?;
return Ok(Value::Object(Object(BTreeMap::from([(key, value)]))));
}
if self.eat(t!("}")) {
return Ok(Value::Object(Object(BTreeMap::from([(key, value)]))));
}
let type_key = self.parse_object_key()?;
if type_key != "type" {
expected!(self, t!(":"));
return self
.parse_object_from_key(ctx, type_key, BTreeMap::from([(key, value)]), start)
.await
.map(Value::Object);
}
expected!(self, t!(":"));
let (t!("\"") | t!("'")) = self.peek_kind() else {
return self
.parse_object_from_key(ctx, type_key, BTreeMap::from([(key, value)]), start)
.await
.map(Value::Object);
};
let type_value = self.next_token_value::<Strand>()?.0;
let ate_comma = self.eat(t!(","));
match type_value.as_str() {
"Point" => {
if self.eat(t!("}")) {
if let Some(point) = Geometry::array_to_point(&value) {
return Ok(Value::Geometry(Geometry::Point(point)));
}
}
}
"LineString" => {
if self.eat(t!("}")) {
if let Some(point) = Geometry::array_to_line(&value) {
return Ok(Value::Geometry(Geometry::Line(point)));
}
}
}
"Polygon" => {
if self.eat(t!("}")) {
if let Some(point) = Geometry::array_to_polygon(&value) {
return Ok(Value::Geometry(Geometry::Polygon(point)));
}
}
}
"MultiPoint" => {
if self.eat(t!("}")) {
if let Some(point) = Geometry::array_to_multipolygon(&value) {
return Ok(Value::Geometry(Geometry::MultiPolygon(point)));
}
}
}
"MultiLineString" => {
if self.eat(t!("}")) {
if let Some(point) = Geometry::array_to_multiline(&value) {
return Ok(Value::Geometry(Geometry::MultiLine(point)));
}
}
}
"MultiPolygon" => {
if self.eat(t!("}")) {
if let Some(point) = Geometry::array_to_multipolygon(&value) {
return Ok(Value::Geometry(Geometry::MultiPolygon(point)));
}
}
}
_ => {}
};
if !ate_comma {
self.expect_closing_delimiter(t!("}"), start)?;
return Ok(Value::Object(Object(BTreeMap::from([
(key, value),
(type_key, Value::Strand(type_value.into())),
]))));
}
self.parse_object_from_map(
ctx,
BTreeMap::from([(key, value), (type_key, Value::Strand(type_value.into()))]),
start,
)
.await
.map(Value::Object)
}
async fn parse_object_or_geometry_after_geometries(
&mut self,
ctx: &mut Stk,
start: Span,
key: String,
) -> ParseResult<Value> {
expected!(self, t!(":"));
let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
if !self.eat(t!(",")) || self.peek_kind() == t!("}") {
self.expect_closing_delimiter(t!("}"), start)?;
return Ok(Value::Object(Object(BTreeMap::from([(key, value)]))));
}
let type_key = self.parse_object_key()?;
if type_key != "type" {
expected!(self, t!(":"));
return self
.parse_object_from_key(ctx, type_key, BTreeMap::from([(key, value)]), start)
.await
.map(Value::Object);
}
expected!(self, t!(":"));
let (t!("\"") | t!("'")) = self.peek_kind() else {
return self
.parse_object_from_key(ctx, type_key, BTreeMap::from([(key, value)]), start)
.await
.map(Value::Object);
};
let type_value = self.next_token_value::<Strand>()?.0;
let ate_comma = self.eat(t!(","));
if type_value == "GeometryCollection" && self.eat(t!("}")) {
if let Value::Array(ref x) = value {
if x.iter().all(|x| matches!(x, Value::Geometry(_))) {
let Value::Array(x) = value else {
unreachable!()
};
let geometries = x
.into_iter()
.map(|x| {
if let Value::Geometry(x) = x {
x
} else {
unreachable!()
}
})
.collect();
return Ok(Value::Geometry(Geometry::Collection(geometries)));
}
}
}
if !ate_comma {
self.expect_closing_delimiter(t!("}"), start)?;
return Ok(Value::Object(Object(BTreeMap::from([
(key, value),
(type_key, Value::Strand(type_value.into())),
]))));
}
self.parse_object_from_map(
ctx,
BTreeMap::from([(key, value), (type_key, Value::Strand(type_value.into()))]),
start,
)
.await
.map(Value::Object)
}
async fn parse_object_or_geometry(&mut self, ctx: &mut Stk, start: Span) -> ParseResult<Value> {
let key = self.parse_object_key()?;
match key.as_str() {
"type" => self.parse_object_or_geometry_after_type(ctx, start, key).await,
"coordinates" => self.parse_object_or_geometry_after_coordinates(ctx, start, key).await,
"geometries" => self.parse_object_or_geometry_after_geometries(ctx, start, key).await,
_ => {
expected!(self, t!(":"));
self.parse_object_from_key(ctx, key, BTreeMap::new(), start)
.await
.map(Value::Object)
}
}
}
async fn parse_geometry_after_type<F, Fm, R>(
&mut self,
ctx: &mut Stk,
start: Span,
key: String,
strand: String,
capture: F,
map: Fm,
) -> ParseResult<Value>
where
F: FnOnce(&Value) -> Option<R>,
Fm: FnOnce(R) -> Value,
{
if !self.eat(t!(",")) {
self.expect_closing_delimiter(t!("}"), start)?;
return Ok(Value::Object(Object(BTreeMap::from([(
key,
Value::Strand(strand.into()),
)]))));
}
let coord_key = self.parse_object_key()?;
if coord_key != "coordinates" {
expected!(self, t!(":"));
return self
.parse_object_from_key(
ctx,
coord_key,
BTreeMap::from([(key, Value::Strand(strand.into()))]),
start,
)
.await
.map(Value::Object);
}
expected!(self, t!(":"));
let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
let comma = self.eat(t!(","));
if !self.eat(t!("}")) {
if !comma {
bail!("Unexpected token, expected delimiter `}}`",
@self.recent_span(),
@start => "expected this delimiter to close"
);
}
return self
.parse_object_from_map(
ctx,
BTreeMap::from([(key, Value::Strand(strand.into())), (coord_key, value)]),
start,
)
.await
.map(Value::Object);
}
let Some(v) = capture(&value) else {
return Ok(Value::Object(Object(BTreeMap::from([
(key, Value::Strand(strand.into())),
(coord_key, value),
]))));
};
Ok(map(v))
}
async fn parse_object_from_key(
&mut self,
ctx: &mut Stk,
key: String,
mut map: BTreeMap<String, Value>,
start: Span,
) -> ParseResult<Object> {
let v = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
map.insert(key, v);
if !self.eat(t!(",")) {
self.expect_closing_delimiter(t!("}"), start)?;
return Ok(Object(map));
}
self.parse_object_from_map(ctx, map, start).await
}
pub(super) async fn parse_object(&mut self, ctx: &mut Stk, start: Span) -> ParseResult<Object> {
enter_object_recursion!(this = self => {
this.parse_object_from_map(ctx, BTreeMap::new(), start).await
})
}
async fn parse_object_from_map(
&mut self,
ctx: &mut Stk,
mut map: BTreeMap<String, Value>,
start: Span,
) -> ParseResult<Object> {
loop {
if self.eat(t!("}")) {
return Ok(Object(map));
}
let (key, value) = self.parse_object_entry(ctx).await?;
map.insert(key, value);
if !self.eat(t!(",")) {
self.expect_closing_delimiter(t!("}"), start)?;
return Ok(Object(map));
}
}
}
pub async fn parse_block(&mut self, ctx: &mut Stk, start: Span) -> ParseResult<Block> {
let mut statements = Vec::new();
loop {
while self.eat(t!(";")) {}
if self.eat(t!("}")) {
break;
}
let stmt = ctx.run(|ctx| self.parse_entry(ctx)).await?;
statements.push(stmt);
if !self.eat(t!(";")) {
self.expect_closing_delimiter(t!("}"), start)?;
break;
}
}
Ok(Block(statements))
}
async fn parse_object_entry(&mut self, ctx: &mut Stk) -> ParseResult<(String, Value)> {
let text = self.parse_object_key()?;
expected!(self, t!(":"));
let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
Ok((text, value))
}
pub(super) fn parse_object_key(&mut self) -> ParseResult<String> {
let token = self.peek();
match token.kind {
x if Self::kind_is_keyword_like(x) => {
self.pop_peek();
let str = self.lexer.span_str(token.span);
Ok(str.to_string())
}
TokenKind::Identifier => {
self.pop_peek();
let str = self.lexer.string.take().unwrap();
Ok(str)
}
t!("\"") | t!("'") | TokenKind::Glued(Glued::Strand) => {
let str = self.next_token_value::<Strand>()?.0;
Ok(str)
}
TokenKind::Digits => {
self.pop_peek();
let span = self.lexer.lex_compound(token, compound::number)?.span;
let str = self.lexer.span_str(span);
Ok(str.to_string())
}
TokenKind::Glued(Glued::Number) => {
self.pop_peek();
let str = self.lexer.span_str(token.span);
Ok(str.to_string())
}
_ => unexpected!(self, token, "an object key"),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::syn::Parse;
#[test]
fn block_value() {
let sql = "{ 80 }";
let out = Value::parse(sql);
assert_eq!(sql, out.to_string())
}
#[test]
fn block_ifelse() {
let sql = "{ RETURN IF true THEN 50 ELSE 40 END; }";
let out = Value::parse(sql);
assert_eq!(sql, out.to_string())
}
#[test]
fn block_multiple() {
let sql = r#"{
LET $person = (SELECT * FROM person WHERE first = $first AND last = $last AND birthday = $birthday);
RETURN IF $person[0].id THEN
$person[0]
ELSE
(CREATE person SET first = $first, last = $last, birthday = $birthday)
END;
}"#;
let out = Value::parse(sql);
assert_eq!(sql, format!("{:#}", out))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syn::Parse;
#[test]
fn simple() {
let sql = "(-0.118092, 51.509865)";
let out = Value::parse(sql);
assert!(matches!(out, Value::Geometry(_)));
assert_eq!("(-0.118092, 51.509865)", format!("{}", out));
}
#[test]
fn point() {
let sql = r#"{
type: 'Point',
coordinates: [-0.118092, 51.509865]
}"#;
let out = Value::parse(sql);
assert!(matches!(out, Value::Geometry(_)));
assert_eq!("(-0.118092, 51.509865)", format!("{}", out));
}
#[test]
fn polygon_exterior() {
let sql = r#"{
type: 'Polygon',
coordinates: [
[
[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
[-0.38314819, 51.37692386]
]
]
}"#;
let out = Value::parse(sql);
assert!(matches!(out, Value::Geometry(_)));
assert_eq!("{ type: 'Polygon', coordinates: [[[-0.38314819, 51.37692386], [0.1785278, 51.37692386], [0.1785278, 51.6146057], [-0.38314819, 51.6146057], [-0.38314819, 51.37692386]]] }", format!("{}", out));
}
#[test]
fn polygon_interior() {
let sql = r#"{
type: 'Polygon',
coordinates: [
[
[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
[-0.38314819, 51.37692386]
],
[
[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
[-0.38314819, 51.37692386]
]
]
}"#;
let out = Value::parse(sql);
assert!(matches!(out, Value::Geometry(_)));
assert_eq!("{ type: 'Polygon', coordinates: [[[-0.38314819, 51.37692386], [0.1785278, 51.37692386], [0.1785278, 51.6146057], [-0.38314819, 51.6146057], [-0.38314819, 51.37692386]], [[-0.38314819, 51.37692386], [0.1785278, 51.37692386], [0.1785278, 51.6146057], [-0.38314819, 51.6146057], [-0.38314819, 51.37692386]]] }", format!("{}", out));
}
}