use alloc::format;
use alloc::vec::Vec;
use geometry_cs::Cartesian;
use geometry_model::{
DynGeometry, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point2D, Polygon, Ring,
};
use crate::lexer::{Lexer, Token, WktError};
type Pt = Point2D<f64, Cartesian>;
const MAX_DEPTH: usize = 128;
struct Parser<'a> {
lexer: Lexer<'a>,
current: Token,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Result<Self, WktError> {
let mut lexer = Lexer::new(input);
let current = lexer.next_token()?;
Ok(Self { lexer, current })
}
fn peek(&self) -> &Token {
&self.current
}
fn next(&mut self) -> Result<Token, WktError> {
let next = self.lexer.next_token()?;
Ok(core::mem::replace(&mut self.current, next))
}
fn advance(&mut self) -> Result<(), WktError> {
self.next().map(drop)
}
fn expect_left_paren(&mut self) -> Result<(), WktError> {
match self.next()? {
Token::LeftParen => Ok(()),
other => Err(WktError::UnexpectedToken {
expected: "'('",
found: format!("{other:?}"),
}),
}
}
fn expect_right_paren(&mut self) -> Result<(), WktError> {
match self.next()? {
Token::RightParen => Ok(()),
other => Err(WktError::UnexpectedToken {
expected: "')'",
found: format!("{other:?}"),
}),
}
}
fn expect_number(&mut self) -> Result<f64, WktError> {
match self.next()? {
Token::Number(n) => Ok(n),
Token::Eof => Err(WktError::UnexpectedEof),
other => Err(WktError::UnexpectedToken {
expected: "number",
found: format!("{other:?}"),
}),
}
}
fn skip_dimension_suffix(&mut self) -> Result<(), WktError> {
match self.peek() {
Token::Ident(word) if word == "Z" || word == "M" || word == "ZM" => self.advance(),
_ => Ok(()),
}
}
fn parse_point_coords(&mut self) -> Result<Pt, WktError> {
let x = self.expect_number()?;
let y = self.expect_number()?;
while let Token::Number(_) = self.peek() {
self.advance()?;
}
Ok(Point2D::new(x, y))
}
fn parse_coord_list(&mut self) -> Result<Vec<Pt>, WktError> {
self.expect_left_paren()?;
let mut pts = Vec::new();
loop {
pts.push(self.parse_point_coords()?);
match self.peek() {
Token::Comma => {
self.advance()?;
}
_ => break,
}
}
self.expect_right_paren()?;
Ok(pts)
}
fn parse_coord_list_list(&mut self) -> Result<Vec<Vec<Pt>>, WktError> {
self.expect_left_paren()?;
let mut rings = Vec::new();
loop {
rings.push(self.parse_coord_list()?);
match self.peek() {
Token::Comma => {
self.advance()?;
}
_ => break,
}
}
self.expect_right_paren()?;
Ok(rings)
}
fn parse_point_body(&mut self) -> Result<DynGeometry<f64, Cartesian>, WktError> {
if let Token::Empty = self.peek() {
return Err(WktError::TypeMismatch {
expected: "POINT with coordinates",
found: "POINT EMPTY",
});
}
self.expect_left_paren()?;
let p = self.parse_point_coords()?;
self.expect_right_paren()?;
Ok(DynGeometry::Point(p))
}
fn parse_linestring_body(&mut self) -> Result<DynGeometry<f64, Cartesian>, WktError> {
if let Token::Empty = self.peek() {
self.advance()?;
return Ok(DynGeometry::LineString(Linestring(Vec::new())));
}
let pts = self.parse_coord_list()?;
Ok(DynGeometry::LineString(Linestring(pts)))
}
fn parse_polygon_body(&mut self) -> Result<DynGeometry<f64, Cartesian>, WktError> {
Ok(DynGeometry::Polygon(self.parse_polygon_value()?))
}
fn parse_polygon_value(&mut self) -> Result<Polygon<Pt>, WktError> {
if let Token::Empty = self.peek() {
self.advance()?;
return Ok(Polygon::new(Ring::new()));
}
let mut rings = self.parse_coord_list_list()?.into_iter();
let outer = rings.next().map_or_else(Ring::new, Ring::from_vec);
let inners: Vec<Ring<Pt>> = rings.map(Ring::from_vec).collect();
Ok(Polygon::with_inners(outer, inners))
}
fn parse_multipoint_body(&mut self) -> Result<DynGeometry<f64, Cartesian>, WktError> {
if let Token::Empty = self.peek() {
self.advance()?;
return Ok(DynGeometry::MultiPoint(MultiPoint(Vec::new())));
}
self.expect_left_paren()?;
let mut pts = Vec::new();
loop {
if let Token::LeftParen = self.peek() {
self.advance()?;
pts.push(self.parse_point_coords()?);
self.expect_right_paren()?;
} else {
pts.push(self.parse_point_coords()?);
}
match self.peek() {
Token::Comma => {
self.advance()?;
}
_ => break,
}
}
self.expect_right_paren()?;
Ok(DynGeometry::MultiPoint(MultiPoint(pts)))
}
fn parse_multilinestring_body(&mut self) -> Result<DynGeometry<f64, Cartesian>, WktError> {
if let Token::Empty = self.peek() {
self.advance()?;
return Ok(DynGeometry::MultiLineString(MultiLinestring(Vec::new())));
}
let lists = self.parse_coord_list_list()?;
let lines: Vec<Linestring<Pt>> = lists.into_iter().map(Linestring).collect();
Ok(DynGeometry::MultiLineString(MultiLinestring(lines)))
}
fn parse_multipolygon_body(&mut self) -> Result<DynGeometry<f64, Cartesian>, WktError> {
if let Token::Empty = self.peek() {
self.advance()?;
return Ok(DynGeometry::MultiPolygon(MultiPolygon(Vec::new())));
}
self.expect_left_paren()?;
let mut polys = Vec::new();
loop {
polys.push(self.parse_polygon_value()?);
match self.peek() {
Token::Comma => {
self.advance()?;
}
_ => break,
}
}
self.expect_right_paren()?;
Ok(DynGeometry::MultiPolygon(MultiPolygon(polys)))
}
fn parse_collection_body(
&mut self,
depth: usize,
) -> Result<DynGeometry<f64, Cartesian>, WktError> {
if let Token::Empty = self.peek() {
self.advance()?;
return Ok(DynGeometry::GeometryCollection(Vec::new()));
}
self.expect_left_paren()?;
let mut items = Vec::new();
loop {
items.push(self.parse_geometry(depth + 1)?);
match self.peek() {
Token::Comma => {
self.advance()?;
}
_ => break,
}
}
self.expect_right_paren()?;
Ok(DynGeometry::GeometryCollection(items))
}
fn parse_geometry(&mut self, depth: usize) -> Result<DynGeometry<f64, Cartesian>, WktError> {
if depth >= MAX_DEPTH {
return Err(WktError::NestingTooDeep);
}
let keyword = match self.next()? {
Token::Ident(word) => word,
Token::Eof => return Err(WktError::UnexpectedEof),
other => {
return Err(WktError::UnexpectedToken {
expected: "geometry type keyword",
found: format!("{other:?}"),
});
}
};
self.skip_dimension_suffix()?;
match keyword.as_str() {
"POINT" => self.parse_point_body(),
"LINESTRING" => self.parse_linestring_body(),
"POLYGON" => self.parse_polygon_body(),
"MULTIPOINT" => self.parse_multipoint_body(),
"MULTILINESTRING" => self.parse_multilinestring_body(),
"MULTIPOLYGON" => self.parse_multipolygon_body(),
"GEOMETRYCOLLECTION" => self.parse_collection_body(depth),
_ => Err(WktError::UnknownGeometryType(keyword)),
}
}
}
pub fn from_wkt<S: AsRef<str>>(input: S) -> Result<DynGeometry<f64, Cartesian>, WktError> {
let mut parser = Parser::new(input.as_ref())?;
let g = parser.parse_geometry(0)?;
match parser.peek() {
Token::Eof => Ok(g),
other => Err(WktError::UnexpectedToken {
expected: "end of input",
found: format!("{other:?}"),
}),
}
}
fn kind_name(g: &DynGeometry<f64, Cartesian>) -> &'static str {
match g {
DynGeometry::Point(_) => "POINT",
DynGeometry::LineString(_) => "LINESTRING",
DynGeometry::Polygon(_) => "POLYGON",
DynGeometry::MultiPoint(_) => "MULTIPOINT",
DynGeometry::MultiLineString(_) => "MULTILINESTRING",
DynGeometry::MultiPolygon(_) => "MULTIPOLYGON",
DynGeometry::GeometryCollection(_) => "GEOMETRYCOLLECTION",
}
}
pub fn parse_point(s: &str) -> Result<Pt, WktError> {
let g = from_wkt(s)?;
match g {
DynGeometry::Point(p) => Ok(p),
other => Err(WktError::TypeMismatch {
expected: "POINT",
found: kind_name(&other),
}),
}
}
pub fn parse_linestring(s: &str) -> Result<Linestring<Pt>, WktError> {
let g = from_wkt(s)?;
match g {
DynGeometry::LineString(ls) => Ok(ls),
other => Err(WktError::TypeMismatch {
expected: "LINESTRING",
found: kind_name(&other),
}),
}
}
pub fn parse_polygon(s: &str) -> Result<Polygon<Pt>, WktError> {
let g = from_wkt(s)?;
match g {
DynGeometry::Polygon(p) => Ok(p),
other => Err(WktError::TypeMismatch {
expected: "POLYGON",
found: kind_name(&other),
}),
}
}
pub fn parse_multi_point(s: &str) -> Result<MultiPoint<Pt>, WktError> {
let g = from_wkt(s)?;
match g {
DynGeometry::MultiPoint(mp) => Ok(mp),
other => Err(WktError::TypeMismatch {
expected: "MULTIPOINT",
found: kind_name(&other),
}),
}
}
pub fn parse_multi_linestring(s: &str) -> Result<MultiLinestring<Linestring<Pt>>, WktError> {
let g = from_wkt(s)?;
match g {
DynGeometry::MultiLineString(mls) => Ok(mls),
other => Err(WktError::TypeMismatch {
expected: "MULTILINESTRING",
found: kind_name(&other),
}),
}
}
pub fn parse_multi_polygon(s: &str) -> Result<MultiPolygon<Polygon<Pt>>, WktError> {
let g = from_wkt(s)?;
match g {
DynGeometry::MultiPolygon(mpg) => Ok(mpg),
other => Err(WktError::TypeMismatch {
expected: "MULTIPOLYGON",
found: kind_name(&other),
}),
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::float_cmp,
reason = "coordinate values come from exact integer WKT literals"
)]
use super::*;
use geometry_trait::{
Linestring as _, MultiLinestring as _, MultiPoint as _, MultiPolygon as _, Point as _,
Polygon as _, Ring as _,
};
#[test]
fn deeply_nested_collections_are_rejected_without_overflow() {
let mut s = alloc::string::String::new();
for _ in 0..10_000 {
s.push_str("GEOMETRYCOLLECTION(");
}
s.push_str("POINT(1 1)");
for _ in 0..10_000 {
s.push(')');
}
assert_eq!(from_wkt(&s).unwrap_err(), WktError::NestingTooDeep);
let ok = "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 1)))";
assert!(from_wkt(ok).is_ok());
}
#[test]
fn point_example() {
let g = from_wkt("POINT (10 10)").unwrap();
assert_eq!(g, DynGeometry::Point(Pt::new(10.0, 10.0)));
}
#[test]
fn linestring_example() {
let g = from_wkt("LINESTRING (10 10, 20 20, 30 40)").unwrap();
assert_eq!(
g,
DynGeometry::LineString(Linestring::from_vec(vec![
Pt::new(10.0, 10.0),
Pt::new(20.0, 20.0),
Pt::new(30.0, 40.0),
]))
);
}
#[test]
fn polygon_example() {
let g = from_wkt("POLYGON ((10 10, 10 20, 20 20, 20 15, 10 10))").unwrap();
assert_eq!(
g,
DynGeometry::Polygon(Polygon::new(Ring::from_vec(vec![
Pt::new(10.0, 10.0),
Pt::new(10.0, 20.0),
Pt::new(20.0, 20.0),
Pt::new(20.0, 15.0),
Pt::new(10.0, 10.0),
])))
);
}
#[test]
fn polygon_with_hole() {
let g =
from_wkt("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0), (2 2, 2 4, 4 4, 4 2, 2 2))").unwrap();
assert_eq!(
g,
DynGeometry::Polygon(Polygon::with_inners(
Ring::from_vec(vec![
Pt::new(0.0, 0.0),
Pt::new(0.0, 10.0),
Pt::new(10.0, 10.0),
Pt::new(10.0, 0.0),
Pt::new(0.0, 0.0),
]),
vec![Ring::from_vec(vec![
Pt::new(2.0, 2.0),
Pt::new(2.0, 4.0),
Pt::new(4.0, 4.0),
Pt::new(4.0, 2.0),
Pt::new(2.0, 2.0),
])],
))
);
}
#[test]
fn multipoint_parenthesised_form() {
let g = from_wkt("MULTIPOINT ((10 10), (20 20))").unwrap();
assert_eq!(
g,
DynGeometry::MultiPoint(MultiPoint::from_vec(vec![
Pt::new(10.0, 10.0),
Pt::new(20.0, 20.0),
]))
);
}
#[test]
fn multipoint_bare_form() {
let g = from_wkt("MULTIPOINT (10 10, 20 20)").unwrap();
assert_eq!(
g,
DynGeometry::MultiPoint(MultiPoint::from_vec(vec![
Pt::new(10.0, 10.0),
Pt::new(20.0, 20.0),
]))
);
}
#[test]
fn multilinestring_example() {
let g = from_wkt("MULTILINESTRING ((10 10, 20 20), (15 15, 30 15))").unwrap();
assert_eq!(
g,
DynGeometry::MultiLineString(MultiLinestring::from_vec(vec![
Linestring::from_vec(vec![Pt::new(10.0, 10.0), Pt::new(20.0, 20.0)]),
Linestring::from_vec(vec![Pt::new(15.0, 15.0), Pt::new(30.0, 15.0)]),
]))
);
}
#[test]
fn multipolygon_example() {
let g = from_wkt("MULTIPOLYGON (((10 10, 10 20, 20 20, 20 15, 10 10)))").unwrap();
assert_eq!(
g,
DynGeometry::MultiPolygon(MultiPolygon::from_vec(vec![Polygon::new(Ring::from_vec(
vec![
Pt::new(10.0, 10.0),
Pt::new(10.0, 20.0),
Pt::new(20.0, 20.0),
Pt::new(20.0, 15.0),
Pt::new(10.0, 10.0),
],
))]))
);
}
#[test]
fn geometrycollection_example() {
let g = from_wkt("GEOMETRYCOLLECTION (POINT (10 10), LINESTRING (10 10, 20 20))").unwrap();
assert_eq!(
g,
DynGeometry::GeometryCollection(vec![
DynGeometry::Point(Pt::new(10.0, 10.0)),
DynGeometry::LineString(Linestring::from_vec(vec![
Pt::new(10.0, 10.0),
Pt::new(20.0, 20.0),
])),
])
);
}
#[test]
fn linestring_empty() {
let g = from_wkt("LINESTRING EMPTY").unwrap();
assert_eq!(g, DynGeometry::LineString(Linestring::from_vec(Vec::new())));
}
#[test]
fn geometrycollection_empty() {
let g = from_wkt("GEOMETRYCOLLECTION EMPTY").unwrap();
assert_eq!(g, DynGeometry::GeometryCollection(Vec::new()));
}
#[test]
fn point_empty_is_rejected() {
let err = from_wkt("POINT EMPTY").unwrap_err();
assert!(matches!(err, WktError::TypeMismatch { .. }));
}
#[test]
fn typed_parse_type_mismatch() {
let err = parse_point("LINESTRING (10 10, 20 20)").unwrap_err();
assert_eq!(
err,
WktError::TypeMismatch {
expected: "POINT",
found: "LINESTRING",
}
);
}
#[test]
fn unknown_keyword_errors() {
let err = from_wkt("TRIANGLE (0 0, 1 0, 0 1, 0 0)").unwrap_err();
assert_eq!(err, WktError::UnknownGeometryType("TRIANGLE".into()));
}
#[test]
fn dimension_suffix_is_skipped() {
let g = from_wkt("POINT Z (10 10 5)").unwrap();
assert_eq!(g, DynGeometry::Point(Pt::new(10.0, 10.0)));
}
#[test]
fn malformed_token_after_dimension_suffix_is_reported() {
assert_eq!(
from_wkt("POINT Z @"),
Err(WktError::UnexpectedChar { pos: 8, ch: '@' })
);
}
#[test]
fn polygon_empty() {
let g = from_wkt("POLYGON EMPTY").unwrap();
assert_eq!(g, DynGeometry::Polygon(Polygon::new(Ring::new())));
}
#[test]
fn multi_kinds_empty() {
let multipoint = from_wkt("MULTIPOINT EMPTY").unwrap();
assert_eq!(
multipoint,
DynGeometry::MultiPoint(MultiPoint::from_vec(Vec::new()))
);
let multilinestring = from_wkt("MULTILINESTRING EMPTY").unwrap();
assert_eq!(
multilinestring,
DynGeometry::MultiLineString(MultiLinestring::from_vec(Vec::new()))
);
let multipolygon = from_wkt("MULTIPOLYGON EMPTY").unwrap();
assert_eq!(
multipolygon,
DynGeometry::MultiPolygon(MultiPolygon::from_vec(Vec::new()))
);
}
#[test]
fn missing_left_paren_is_reported() {
let err = from_wkt("LINESTRING 10 10").unwrap_err();
assert!(
matches!(&err, WktError::UnexpectedToken { expected, .. } if *expected == "'('"),
"got {err:?}"
);
}
#[test]
fn missing_right_paren_is_reported() {
let err = from_wkt("LINESTRING (10 10, 20 20").unwrap_err();
assert!(
matches!(&err, WktError::UnexpectedToken { expected, .. } if *expected == "')'"),
"got {err:?}"
);
}
#[test]
fn missing_ordinate_hits_eof() {
let err = from_wkt("POINT (10").unwrap_err();
assert_eq!(err, WktError::UnexpectedEof);
}
#[test]
fn non_number_ordinate_is_reported() {
let err = from_wkt("POINT (10 )").unwrap_err();
assert!(
matches!(&err, WktError::UnexpectedToken { expected, .. } if *expected == "number"),
"got {err:?}"
);
}
#[test]
fn trailing_tokens_are_rejected() {
let err = from_wkt("POINT (1 1) POINT (2 2)").unwrap_err();
assert!(
matches!(&err, WktError::UnexpectedToken { expected, .. } if *expected == "end of input"),
"got {err:?}"
);
}
#[test]
fn empty_input_is_unexpected_eof() {
assert_eq!(from_wkt("").unwrap_err(), WktError::UnexpectedEof);
}
#[test]
fn non_keyword_leading_token_is_reported() {
let err = from_wkt("(1 1)").unwrap_err();
assert!(
matches!(&err, WktError::UnexpectedToken { expected, .. }
if *expected == "geometry type keyword"),
"got {err:?}"
);
}
#[test]
fn typed_parsers_accept_their_own_kind() {
assert_eq!(parse_point("POINT (1 2)").unwrap().get::<0>(), 1.0);
assert_eq!(
parse_linestring("LINESTRING (0 0, 1 1)")
.unwrap()
.points()
.len(),
2
);
assert_eq!(
parse_polygon("POLYGON ((0 0, 1 0, 1 1, 0 0))")
.unwrap()
.exterior()
.points()
.len(),
4
);
assert_eq!(
parse_multi_point("MULTIPOINT (0 0, 1 1)")
.unwrap()
.points()
.len(),
2
);
assert_eq!(
parse_multi_linestring("MULTILINESTRING ((0 0, 1 1))")
.unwrap()
.linestrings()
.len(),
1
);
assert_eq!(
parse_multi_polygon("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))")
.unwrap()
.polygons()
.len(),
1
);
}
#[test]
fn typed_parsers_reject_wrong_kind_naming_the_kind() {
let cases: &[(WktError, &str, &str)] = &[
(
parse_linestring("POINT (1 1)").unwrap_err(),
"LINESTRING",
"POINT",
),
(
parse_polygon("LINESTRING (0 0, 1 1)").unwrap_err(),
"POLYGON",
"LINESTRING",
),
(
parse_multi_point("POLYGON ((0 0, 1 0, 1 1, 0 0))").unwrap_err(),
"MULTIPOINT",
"POLYGON",
),
(
parse_multi_linestring("MULTIPOINT (0 0, 1 1)").unwrap_err(),
"MULTILINESTRING",
"MULTIPOINT",
),
(
parse_multi_polygon("MULTILINESTRING ((0 0, 1 1))").unwrap_err(),
"MULTIPOLYGON",
"MULTILINESTRING",
),
(
parse_point("MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)))").unwrap_err(),
"POINT",
"MULTIPOLYGON",
),
(
parse_point("GEOMETRYCOLLECTION (POINT (1 1))").unwrap_err(),
"POINT",
"GEOMETRYCOLLECTION",
),
];
for (err, want_expected, want_found) in cases {
assert_eq!(
err,
&WktError::TypeMismatch {
expected: want_expected,
found: want_found,
}
);
}
}
}