use geo_aid_derive::Definition;
use std::{collections::HashMap, fmt::Debug};
use crate::script::builtins::macros::{intersection, number};
use crate::script::unroll::{AnyExpr, Simplify};
use crate::script::{
builtins::macros::{circle_center, circle_radius, distance, rule},
compile::PreFigure,
};
use super::{
Circle as UnrolledCircle, Expr, FlagSet, FlagSetConstructor, Line as UnrolledLine,
Point as UnrolledPoint, Scalar as UnrolledScalar, UnrolledRule,
};
pub trait Definition {
fn order(&self, context: &CompileContext) -> usize;
fn contains_entity(&self, entity: usize, context: &CompileContext) -> bool;
}
#[derive(Debug, Clone)]
pub enum Scalar {
Free,
Bind(Expr<UnrolledScalar>),
}
impl Definition for Scalar {
fn order(&self, context: &CompileContext) -> usize {
match self {
Self::Free => 1,
Self::Bind(expr) => expr.order(context),
}
}
fn contains_entity(&self, entity: usize, context: &CompileContext) -> bool {
match self {
Self::Free => false,
Self::Bind(expr) => expr.contains_entity(entity, context),
}
}
}
#[derive(Debug, Clone, Definition)]
pub enum Point {
#[def(order(2))]
Free,
OnCircle(Expr<UnrolledCircle>),
OnLine(Expr<UnrolledLine>),
Bind(Expr<UnrolledPoint>),
}
#[derive(Debug, Clone, Definition)]
pub enum Line {
Bind(Expr<UnrolledLine>),
}
#[derive(Debug, Clone, Definition)]
pub enum Circle {
Bind(Expr<UnrolledCircle>),
#[def(order(usize::MAX))]
Temporary,
}
#[derive(Debug, Clone)]
pub enum Entity {
Scalar(Scalar),
Point(Point),
Line(Line),
Circle(Circle),
}
impl Entity {
#[must_use]
pub fn free_point() -> Self {
Self::Point(Point::Free)
}
#[must_use]
pub fn free_scalar() -> Self {
Self::Scalar(Scalar::Free)
}
#[must_use]
pub fn free_circle() -> Self {
Self::Circle(Circle::Temporary)
}
#[must_use]
pub fn as_point(&self) -> Option<&Point> {
if let Self::Point(v) = self {
Some(v)
} else {
None
}
}
#[must_use]
pub fn as_point_mut(&mut self) -> Option<&mut Point> {
if let Self::Point(v) = self {
Some(v)
} else {
None
}
}
}
impl Definition for Entity {
fn order(&self, context: &CompileContext) -> usize {
match self {
Self::Scalar(v) => v.order(context),
Self::Point(v) => v.order(context),
Self::Line(v) => v.order(context),
Self::Circle(v) => v.order(context),
}
}
fn contains_entity(&self, entity: usize, context: &CompileContext) -> bool {
match self {
Self::Scalar(v) => v.contains_entity(entity, context),
Self::Point(v) => v.contains_entity(entity, context),
Self::Line(v) => v.contains_entity(entity, context),
Self::Circle(v) => v.contains_entity(entity, context),
}
}
}
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct CompileContext {
pub variables: HashMap<String, AnyExpr>, pub flags: FlagSet,
pub entities: Vec<Entity>,
pub rules: Vec<UnrolledRule>,
pub figure: PreFigure,
}
impl Default for CompileContext {
fn default() -> Self {
Self::new()
}
}
impl CompileContext {
#[must_use]
pub fn new() -> Self {
Self {
variables: HashMap::new(),
flags: FlagSetConstructor::new()
.add_set(
&"optimizations",
FlagSetConstructor::new().add_bool_def(&"identical_expressions", true),
)
.add_bool_def(&"point_bounds", false)
.finish(),
entities: vec![],
rules: Vec::new(),
figure: PreFigure::default(),
}
}
#[must_use]
pub fn get_entity(&self, i: usize) -> &Entity {
&self.entities[i]
}
#[must_use]
pub fn get_entity_mut(&mut self, i: usize) -> &mut Entity {
&mut self.entities[i]
}
#[must_use]
pub fn add_scalar(&mut self) -> usize {
self.entities.push(Entity::free_scalar());
self.entities.len() - 1
}
#[must_use]
pub fn add_point(&mut self) -> usize {
self.entities.push(Entity::free_point());
self.entities.len() - 1
}
#[must_use]
pub fn get_point_by_index(&self, index: usize) -> Expr<UnrolledPoint> {
let entity = self.entities.get(index).unwrap();
if let Entity::Point(_) = entity {
return Expr::new_spanless(UnrolledPoint::Entity(index));
}
panic!("Requested entity is not a point.");
}
#[must_use]
pub fn get_point_entity_mut(&mut self, expr: &Expr<UnrolledPoint>) -> Option<&mut Point> {
match expr.simplify(self).data.as_ref() {
UnrolledPoint::Entity(i) => self.get_entity_mut(*i).as_point_mut(),
_ => None,
}
}
#[must_use]
pub fn get_point_entity(&self, expr: &Expr<UnrolledPoint>) -> Option<&Point> {
match expr.simplify(self).data.as_ref() {
UnrolledPoint::Entity(i) => self.get_entity(*i).as_point(),
_ => None,
}
}
pub fn add_circle(&mut self) -> usize {
self.entities.push(Entity::free_circle());
self.entities.len() - 1
}
#[must_use]
pub fn get_circle_by_index(&self, index: usize) -> Expr<UnrolledCircle> {
let entity = self.entities.get(index).unwrap();
if let Entity::Circle(_) = entity {
return Expr::new_spanless(UnrolledCircle::Entity(index));
}
panic!("Requested entity is not a circle.");
}
#[must_use]
pub fn get_line_by_index(&self, index: usize) -> Expr<UnrolledLine> {
let entity = self.entities.get(index).unwrap();
if let Entity::Line(_) = entity {
return Expr::new_spanless(UnrolledLine::Entity(index));
}
panic!("Requested entity is not a line.");
}
}
impl CompileContext {
pub fn point_on_circle(&mut self, lhs: &Expr<UnrolledPoint>, rhs: &Expr<UnrolledCircle>) {
if let Some(point) = self.get_point_entity_mut(lhs) {
match point {
Point::Free => {
*point = Point::OnCircle(rhs.clone());
return;
}
Point::OnCircle(_) | Point::OnLine(_) | Point::Bind(_) => (),
}
}
rule!(self:S=(
distance!(PP: lhs, circle_center!(rhs)),
circle_radius!(rhs)
));
}
pub fn point_on_line(&mut self, lhs: &Expr<UnrolledPoint>, rhs: &Expr<UnrolledLine>) {
if let Some(point) = self.get_point_entity_mut(lhs) {
match point {
Point::Free => {
*point = Point::OnLine(rhs.clone());
return;
}
Point::OnLine(k) => {
*point = Point::Bind(intersection!(k, rhs));
return;
}
Point::OnCircle(_) | Point::Bind(_) => (),
}
}
rule!(self:S=(
distance!(PL: lhs, rhs),
number!(=0.0)
));
}
}