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::{Token, WktError, tokenize};
type Pt = Point2D<f64, Cartesian>;
const MAX_DEPTH: usize = 128;
struct Parser {
tokens: Vec<Token>,
pos: usize,
}
impl Parser {
fn new(tokens: Vec<Token>) -> Self {
Self { tokens, pos: 0 }
}
fn peek(&self) -> &Token {
&self.tokens[self.pos]
}
fn next(&mut self) -> Token {
let t = self.tokens[self.pos].clone();
self.pos += 1;
t
}
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) {
if let Token::Ident(word) = self.peek() {
if word == "Z" || word == "M" || word == "ZM" {
self.pos += 1;
}
}
}
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.pos += 1;
}
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.pos += 1;
}
_ => 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.pos += 1;
}
_ => 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.pos += 1;
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.pos += 1;
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.pos += 1;
return Ok(DynGeometry::MultiPoint(MultiPoint(Vec::new())));
}
self.expect_left_paren()?;
let mut pts = Vec::new();
loop {
if let Token::LeftParen = self.peek() {
self.pos += 1;
pts.push(self.parse_point_coords()?);
self.expect_right_paren()?;
} else {
pts.push(self.parse_point_coords()?);
}
match self.peek() {
Token::Comma => {
self.pos += 1;
}
_ => 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.pos += 1;
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.pos += 1;
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.pos += 1;
}
_ => 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.pos += 1;
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.pos += 1;
}
_ => 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 tokens = tokenize(input.as_ref())?;
let mut parser = Parser::new(tokens);
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_model::DynKind;
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.kind(), DynKind::Point);
if let DynGeometry::Point(p) = g {
assert_eq!(p.get::<0>(), 10.0);
assert_eq!(p.get::<1>(), 10.0);
} else {
unreachable!();
}
}
#[test]
fn linestring_example() {
let g = from_wkt("LINESTRING (10 10, 20 20, 30 40)").unwrap();
assert_eq!(g.kind(), DynKind::LineString);
if let DynGeometry::LineString(ls) = g {
assert_eq!(ls.points().len(), 3);
let last = ls.points().last().unwrap();
assert_eq!(last.get::<0>(), 30.0);
assert_eq!(last.get::<1>(), 40.0);
} else {
unreachable!();
}
}
#[test]
fn polygon_example() {
let g = from_wkt("POLYGON ((10 10, 10 20, 20 20, 20 15, 10 10))").unwrap();
assert_eq!(g.kind(), DynKind::Polygon);
if let DynGeometry::Polygon(p) = g {
assert_eq!(p.exterior().points().len(), 5);
assert_eq!(p.interiors().count(), 0);
} else {
unreachable!();
}
}
#[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();
if let DynGeometry::Polygon(p) = g {
assert_eq!(p.interiors().count(), 1);
} else {
unreachable!();
}
}
#[test]
fn multipoint_parenthesised_form() {
let g = from_wkt("MULTIPOINT ((10 10), (20 20))").unwrap();
assert_eq!(g.kind(), DynKind::MultiPoint);
if let DynGeometry::MultiPoint(mp) = g {
assert_eq!(mp.points().len(), 2);
} else {
unreachable!();
}
}
#[test]
fn multipoint_bare_form() {
let g = from_wkt("MULTIPOINT (10 10, 20 20)").unwrap();
if let DynGeometry::MultiPoint(mp) = g {
assert_eq!(mp.points().len(), 2);
let second = mp.points().nth(1).unwrap();
assert_eq!(second.get::<0>(), 20.0);
} else {
unreachable!();
}
}
#[test]
fn multilinestring_example() {
let g = from_wkt("MULTILINESTRING ((10 10, 20 20), (15 15, 30 15))").unwrap();
assert_eq!(g.kind(), DynKind::MultiLineString);
if let DynGeometry::MultiLineString(mls) = g {
assert_eq!(mls.linestrings().len(), 2);
} else {
unreachable!();
}
}
#[test]
fn multipolygon_example() {
let g = from_wkt("MULTIPOLYGON (((10 10, 10 20, 20 20, 20 15, 10 10)))").unwrap();
assert_eq!(g.kind(), DynKind::MultiPolygon);
if let DynGeometry::MultiPolygon(mpg) = g {
assert_eq!(mpg.polygons().len(), 1);
} else {
unreachable!();
}
}
#[test]
fn geometrycollection_example() {
let g = from_wkt("GEOMETRYCOLLECTION (POINT (10 10), LINESTRING (10 10, 20 20))").unwrap();
assert_eq!(g.kind(), DynKind::GeometryCollection);
if let DynGeometry::GeometryCollection(items) = g {
assert_eq!(items.len(), 2);
assert_eq!(items[0].kind(), DynKind::Point);
assert_eq!(items[1].kind(), DynKind::LineString);
} else {
unreachable!();
}
}
#[test]
fn linestring_empty() {
let g = from_wkt("LINESTRING EMPTY").unwrap();
if let DynGeometry::LineString(ls) = g {
assert_eq!(ls.points().len(), 0);
} else {
unreachable!();
}
}
#[test]
fn geometrycollection_empty() {
let g = from_wkt("GEOMETRYCOLLECTION EMPTY").unwrap();
if let DynGeometry::GeometryCollection(items) = g {
assert_eq!(items.len(), 0);
} else {
unreachable!();
}
}
#[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();
if let DynGeometry::Point(p) = g {
assert_eq!(p.get::<0>(), 10.0);
assert_eq!(p.get::<1>(), 10.0);
} else {
unreachable!();
}
}
}