use crate::types::{Angle, Length, OffsetIn, UnitVec};
use glam::DVec2;
#[derive(Debug, Clone)]
pub struct Program {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone)]
pub enum Statement {
Direction(Direction),
Assignment(Assignment),
Define(Define),
MacroCall(MacroCall),
Assert(Assert),
Print(Print),
Error(ErrorStmt),
Labeled(LabeledStatement),
Object(ObjectStatement),
}
#[derive(Debug, Clone)]
pub struct ErrorStmt {
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
#[inline]
pub fn unit_vector(self) -> DVec2 {
match self {
Direction::Right => DVec2::X,
Direction::Left => DVec2::NEG_X,
Direction::Up => DVec2::Y, Direction::Down => DVec2::NEG_Y, }
}
#[inline]
pub fn offset(self, distance: Length) -> OffsetIn {
let v = self.unit_vector() * distance.0;
OffsetIn::new(Length(v.x), Length(v.y))
}
#[inline]
pub fn opposite(self) -> Direction {
match self {
Direction::Right => Direction::Left,
Direction::Left => Direction::Right,
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
}
}
#[inline]
pub fn arc_exit(self, clockwise: bool) -> Direction {
let dir_num = match self {
Direction::Right => 0,
Direction::Down => 1,
Direction::Left => 2,
Direction::Up => 3,
};
let out_num = (dir_num + if clockwise { 1 } else { 3 }) % 4;
match out_num {
0 => Direction::Right,
1 => Direction::Down,
2 => Direction::Left,
_ => Direction::Up, }
}
pub fn from_edge_point(ep: &EdgePoint) -> Option<Direction> {
match ep {
EdgePoint::North | EdgePoint::Top | EdgePoint::N => Some(Direction::Up),
EdgePoint::South | EdgePoint::Bottom | EdgePoint::S => Some(Direction::Down),
EdgePoint::East | EdgePoint::Right | EdgePoint::E => Some(Direction::Right),
EdgePoint::West | EdgePoint::Left | EdgePoint::W => Some(Direction::Left),
EdgePoint::NorthEast => Some(Direction::Up), EdgePoint::NorthWest => Some(Direction::Up), EdgePoint::SouthEast => Some(Direction::Down), EdgePoint::SouthWest => Some(Direction::Down), _ => None, }
}
}
#[derive(Debug, Clone)]
pub struct Assignment {
pub lvalue: LValue,
pub op: AssignOp,
pub rvalue: RValue,
}
#[derive(Debug, Clone)]
pub enum LValue {
Variable(String),
Fill,
Color,
Thickness,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssignOp {
Assign, AddAssign, SubAssign, MulAssign, DivAssign, }
#[derive(Debug, Clone)]
pub enum RValue {
Expr(Expr),
PlaceName(String), }
#[derive(Debug, Clone)]
pub struct Define {
pub name: String,
pub body: String, }
#[derive(Debug, Clone)]
pub struct MacroCall {
pub name: String,
pub args: Vec<MacroArg>,
}
#[derive(Debug, Clone)]
pub enum MacroArg {
String(String),
Expr(Expr),
Ident(String),
}
#[derive(Debug, Clone)]
pub struct Assert {
pub condition: AssertCondition,
}
#[derive(Debug, Clone)]
pub enum AssertCondition {
ExprEqual(Expr, Expr),
PositionEqual(Box<Position>, Box<Position>),
}
#[derive(Debug, Clone)]
pub struct Print {
pub args: Vec<PrintArg>,
}
#[derive(Debug, Clone)]
pub enum PrintArg {
String(String),
Expr(Expr),
PlaceName(String),
}
#[derive(Debug, Clone)]
pub struct LabeledStatement {
pub label: String,
pub content: LabeledContent,
}
#[derive(Debug, Clone)]
pub enum LabeledContent {
Position(Position),
Object(ObjectStatement),
}
#[derive(Debug, Clone)]
pub struct ObjectStatement {
pub basetype: BaseType,
pub attributes: Vec<Attribute>,
}
#[derive(Debug, Clone)]
pub enum BaseType {
Class(ClassName),
Text(StringLit, Option<TextPosition>),
Sublist(Vec<Statement>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClassName {
Arc,
Arrow,
Box,
Circle,
Cylinder,
Diamond,
Dot,
Ellipse,
File,
Line,
Move,
Oval,
Spline,
Sublist,
Text,
}
#[derive(Debug, Clone)]
pub enum Attribute {
NumProperty(NumProperty, RelExpr),
DashProperty(DashProperty, Option<Expr>),
ColorProperty(ColorProperty, RValue),
BoolProperty(BoolProperty),
DirectionMove(Option<bool>, Direction, Option<RelExpr>), DirectionEven(Option<bool>, Direction, Position),
DirectionUntilEven(Option<bool>, Direction, Position),
Heading(Option<RelExpr>, Expr),
Close,
Chop,
From(Position),
To(Position),
Then(Option<ThenClause>),
At(Position),
With(WithClause),
Same(Option<Object>),
StringAttr(StringLit, Option<TextPosition>),
Fit,
Behind(Object),
BareExpr(RelExpr),
}
#[derive(Debug, Clone)]
pub enum ThenClause {
To(Position),
DirectionEven(Direction, Position),
DirectionUntilEven(Direction, Position),
DirectionMove(Direction, Option<RelExpr>),
Heading(Option<RelExpr>, Expr),
EdgePoint(Option<RelExpr>, EdgePoint),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumProperty {
Height,
Width,
Radius,
Diameter,
Thickness,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DashProperty {
Dotted,
Dashed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorProperty {
Fill,
Color,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoolProperty {
Clockwise,
CounterClockwise,
Invisible,
Thick,
Thin,
Solid,
ArrowBoth, ArrowRight, ArrowLeft, }
#[derive(Debug, Clone)]
pub struct WithClause {
pub edge: WithEdge,
pub position: Position,
}
#[derive(Debug, Clone)]
pub enum WithEdge {
DotEdge(EdgePoint),
EdgePoint(EdgePoint),
}
#[derive(Debug, Clone)]
pub struct TextPosition {
pub attrs: Vec<TextAttr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextAttr {
Above,
Below,
Center,
LJust,
RJust,
Bold,
Italic,
Mono,
Big,
Small,
Aligned,
}
#[derive(Debug, Clone)]
pub struct RelExpr {
pub expr: Expr,
pub is_percent: bool,
}
#[derive(Debug, Clone)]
pub enum Expr {
Number(f64), Variable(String),
PlaceName(String),
ParenExpr(Box<Expr>),
BuiltinVar(BuiltinVar),
FuncCall(FuncCall),
DistCall(Box<Position>, Box<Position>),
ObjectProp(Object, NumProperty),
ObjectCoord(Object, Coord),
ObjectEdgeCoord(Object, EdgePoint, Coord),
VertexCoord(Nth, Object, Coord),
BinaryOp(Box<Expr>, BinaryOp, Box<Expr>),
UnaryOp(UnaryOp, Box<Expr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuiltinVar {
Fill,
Color,
Thickness,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Coord {
X,
Y,
}
#[derive(Debug, Clone)]
pub struct FuncCall {
pub func: Function,
pub args: Vec<Expr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Function {
Abs,
Cos,
Sin,
Int,
Sqrt,
Max,
Min,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Neg,
Pos,
}
#[derive(Debug, Clone)]
pub enum Position {
Coords(Expr, Expr),
Tuple(Box<Position>, Box<Position>),
Place(Place),
PlaceOffset(Place, BinaryOp, Expr, Expr),
Between(Expr, Box<Position>, Box<Position>),
Bracket(Expr, Box<Position>, Box<Position>),
AboveBelow(Expr, AboveBelow, Box<Position>),
LeftRightOf(Expr, LeftRight, Box<Position>),
Heading(Expr, HeadingDir, Box<Position>),
EdgePointOf(Expr, EdgePoint, Box<Position>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AboveBelow {
Above,
Below,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LeftRight {
Left,
Right,
}
#[derive(Debug, Clone)]
pub enum HeadingDir {
EdgePoint(EdgePoint),
Expr(Expr),
}
#[derive(Debug, Clone)]
pub enum Place {
Vertex(Nth, Object),
EdgePointOf(EdgePoint, Object),
ObjectEdge(Object, EdgePoint),
Object(Object),
}
#[derive(Debug, Clone)]
pub enum Object {
Named(ObjectName),
Nth(Nth),
}
#[derive(Debug, Clone)]
pub struct ObjectName {
pub base: ObjectNameBase,
pub path: Vec<String>, }
#[derive(Debug, Clone)]
pub enum ObjectNameBase {
This,
PlaceName(String),
}
#[derive(Debug, Clone)]
pub enum Nth {
Ordinal(u32, bool, Option<NthClass>), First(Option<NthClass>),
Last(Option<NthClass>),
Previous,
}
#[derive(Debug, Clone)]
pub enum NthClass {
ClassName(ClassName),
Sublist,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgePoint {
North,
South,
East,
West,
Start,
End,
Center,
Bottom,
Top,
Left,
Right,
NorthEast,
NorthWest,
SouthEast,
SouthWest,
N,
S,
E,
W,
C,
T,
}
impl EdgePoint {
pub fn to_unit_vec(self) -> UnitVec {
match self {
Self::North | Self::N | Self::Top | Self::T => UnitVec::NORTH,
Self::South | Self::S | Self::Bottom => UnitVec::SOUTH,
Self::East | Self::E | Self::Right => UnitVec::EAST,
Self::West | Self::W | Self::Left => UnitVec::WEST,
Self::NorthEast => UnitVec::NORTH_EAST,
Self::NorthWest => UnitVec::NORTH_WEST,
Self::SouthEast => UnitVec::SOUTH_EAST,
Self::SouthWest => UnitVec::SOUTH_WEST,
Self::Center | Self::C => UnitVec::ZERO,
Self::Start => UnitVec::WEST, Self::End => UnitVec::EAST, }
}
pub fn to_angle(self) -> Angle {
Angle::degrees(match self {
Self::North | Self::N | Self::Top | Self::T => 0.0,
Self::NorthEast => 45.0,
Self::East | Self::E | Self::Right => 90.0,
Self::SouthEast => 135.0,
Self::South | Self::S | Self::Bottom => 180.0,
Self::SouthWest => 225.0,
Self::West | Self::W | Self::Left => 270.0,
Self::NorthWest => 315.0,
_ => 0.0,
})
}
}
#[derive(Debug, Clone)]
pub struct StringLit {
pub value: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_direction_offset_svg_coordinates() {
let d = Length::inches(1.0);
let r = Direction::Right.offset(d);
assert!(r.dx > Length::ZERO, "Right should increase X");
assert_eq!(r.dy, Length::ZERO, "Right should not change Y");
let l = Direction::Left.offset(d);
assert!(l.dx < Length::ZERO, "Left should decrease X");
assert_eq!(l.dy, Length::ZERO, "Left should not change Y");
let u = Direction::Up.offset(d);
assert_eq!(u.dx, Length::ZERO, "Up should not change X");
assert!(
u.dy > Length::ZERO,
"Up should increase Y (Y-up internally)"
);
let down = Direction::Down.offset(d);
assert_eq!(down.dx, Length::ZERO, "Down should not change X");
assert!(
down.dy < Length::ZERO,
"Down should decrease Y (Y-up internally)"
);
}
#[test]
fn test_direction_unit_vector() {
let r = Direction::Right.unit_vector();
assert_eq!(r.x, 1.0);
assert_eq!(r.y, 0.0);
let l = Direction::Left.unit_vector();
assert_eq!(l.x, -1.0);
assert_eq!(l.y, 0.0);
let u = Direction::Up.unit_vector();
assert_eq!(u.x, 0.0);
assert_eq!(u.y, 1.0);
let d = Direction::Down.unit_vector();
assert_eq!(d.x, 0.0);
assert_eq!(d.y, -1.0);
}
#[test]
fn test_direction_opposite() {
assert_eq!(Direction::Right.opposite(), Direction::Left);
assert_eq!(Direction::Left.opposite(), Direction::Right);
assert_eq!(Direction::Up.opposite(), Direction::Down);
assert_eq!(Direction::Down.opposite(), Direction::Up);
}
}