use std::{collections::HashMap, rc::Rc, sync::Arc};
use crate::{
generator::{
self,
expression::{
expr::{
AngleBisector, AngleLine, AnglePoint, Average, Difference, FreePoint,
LineLineIntersection, LinePoint, Literal, Negation, ParallelThrough,
PerpendicularThrough, PointLineDistance, PointPointDistance, PointX, PointY,
Product, Quotient, Real, SetUnit, Sum,
},
AnyExpr, Expression, LineExpr, PointExpr, ScalarExpr, Weights,
},
AdjustableTemplate, DistanceLiterals, Flags, Optimizations,
},
span,
};
use super::{
figure::Figure,
parser::PredefinedType,
token::{Position, Span},
ty,
unroll::{
self, Flag, PointMeta, UnrolledExpression, UnrolledExpressionData, UnrolledRule,
UnrolledRuleKind, Variable, VariableMeta,
},
Criteria, CriteriaKind, Error, HashableRc, SimpleUnit, Weighed,
};
trait Mapping<K, T> {
fn get(&self, key: &HashableRc<K>) -> Option<&Arc<Expression<T>>>;
fn insert(
&mut self,
key: HashableRc<K>,
value: Arc<Expression<T>>,
) -> Option<Arc<Expression<T>>>;
}
#[derive(Debug, Default)]
struct ExpressionRecord {
points: HashMap<HashableRc<UnrolledExpressionData>, Arc<Expression<PointExpr>>>,
lines: HashMap<HashableRc<UnrolledExpressionData>, Arc<Expression<LineExpr>>>,
scalars: HashMap<HashableRc<UnrolledExpressionData>, Arc<Expression<ScalarExpr>>>,
}
#[derive(Debug, Default)]
struct VariableRecord {
points: HashMap<HashableRc<Variable>, Arc<Expression<PointExpr>>>,
lines: HashMap<HashableRc<Variable>, Arc<Expression<LineExpr>>>,
scalars: HashMap<HashableRc<Variable>, Arc<Expression<ScalarExpr>>>,
}
impl Mapping<Variable, PointExpr> for VariableRecord {
fn get(&self, key: &HashableRc<Variable>) -> Option<&Arc<Expression<PointExpr>>> {
self.points.get(key)
}
fn insert(
&mut self,
key: HashableRc<Variable>,
value: Arc<Expression<PointExpr>>,
) -> Option<Arc<Expression<PointExpr>>> {
self.points.insert(key, value)
}
}
impl Mapping<Variable, LineExpr> for VariableRecord {
fn get(&self, key: &HashableRc<Variable>) -> Option<&Arc<Expression<LineExpr>>> {
self.lines.get(key)
}
fn insert(
&mut self,
key: HashableRc<Variable>,
value: Arc<Expression<LineExpr>>,
) -> Option<Arc<Expression<LineExpr>>> {
self.lines.insert(key, value)
}
}
impl Mapping<Variable, ScalarExpr> for VariableRecord {
fn get(&self, key: &HashableRc<Variable>) -> Option<&Arc<Expression<ScalarExpr>>> {
self.scalars.get(key)
}
fn insert(
&mut self,
key: HashableRc<Variable>,
value: Arc<Expression<ScalarExpr>>,
) -> Option<Arc<Expression<ScalarExpr>>> {
self.scalars.insert(key, value)
}
}
trait Compile {
fn compile(
expr: &UnrolledExpression,
variables: &mut VariableRecord,
expressions: &mut ExpressionRecord,
template: &mut Vec<AdjustableTemplate>,
dst_var: &Option<Rc<Variable>>,
) -> Arc<Self>;
}
fn index_collection(expr: &UnrolledExpression, index: usize) -> &UnrolledExpression {
match expr.data.as_ref() {
UnrolledExpressionData::VariableAccess(var) => index_collection(&var.definition, index),
UnrolledExpressionData::PointCollection(col) => col.get(index).unwrap(),
UnrolledExpressionData::Boxed(expr) => index_collection(expr, index),
_ => unreachable!("PointCollection should never be achievable by this expression."),
}
}
#[must_use]
pub fn fix_distance(
expr: UnrolledExpression,
power: i8,
dst_var: &Rc<Variable>,
) -> UnrolledExpression {
let sp = expr.span;
let t = expr.ty.clone();
match power.cmp(&0) {
std::cmp::Ordering::Less => UnrolledExpression {
weight: 1.0,
data: Rc::new(UnrolledExpressionData::Divide(
fix_distance(expr, power + 1, dst_var),
UnrolledExpression {
weight: 1.0,
ty: ty::SCALAR,
span: sp,
data: Rc::new(UnrolledExpressionData::VariableAccess(Rc::clone(dst_var))),
},
)),
ty: t,
span: sp,
},
std::cmp::Ordering::Equal => expr,
std::cmp::Ordering::Greater => UnrolledExpression {
weight: 1.0,
data: Rc::new(UnrolledExpressionData::Multiply(
fix_distance(expr, power - 1, dst_var),
UnrolledExpression {
weight: 1.0,
ty: ty::SCALAR,
span: sp,
data: Rc::new(UnrolledExpressionData::VariableAccess(Rc::clone(dst_var))),
},
)),
ty: t,
span: sp,
},
}
}
impl Compile for Expression<PointExpr> {
fn compile(
expr: &UnrolledExpression,
variables: &mut VariableRecord,
expressions: &mut ExpressionRecord,
template: &mut Vec<AdjustableTemplate>,
dst_var: &Option<Rc<Variable>>,
) -> Arc<Self> {
let key = HashableRc::new(Rc::clone(&expr.data));
if let Some(v) = expressions.points.get(&key) {
return Arc::clone(v);
}
let compiled = match expr.data.as_ref() {
UnrolledExpressionData::VariableAccess(var) => {
compile_variable(var, variables, expressions, template, dst_var)
}
UnrolledExpressionData::FreePoint => {
let index = template.len();
template.push(AdjustableTemplate::Point);
Arc::new(Expression::new(PointExpr::Free(FreePoint { index }), 1.0))
}
UnrolledExpressionData::Boxed(expr) => {
Self::compile(expr, variables, expressions, template, dst_var)
}
UnrolledExpressionData::IndexCollection(expr, index) => Self::compile(
index_collection(expr, *index),
variables,
expressions,
template,
dst_var,
),
UnrolledExpressionData::LineLineIntersection(k, l) => Arc::new(Expression::new(
PointExpr::LineLineIntersection(LineLineIntersection {
k: Expression::compile(k, variables, expressions, template, dst_var),
l: Expression::compile(l, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::Average(exprs) => Arc::new(Expression::new(
PointExpr::Average(Average {
items: exprs
.iter()
.map(|expr| Self::compile(expr, variables, expressions, template, dst_var))
.collect(),
}),
1.0,
)),
_ => unreachable!("A point should never be compiled this way"),
};
expressions.points.insert(key, Arc::clone(&compiled));
compiled
}
}
impl Compile for Expression<LineExpr> {
fn compile(
expr: &UnrolledExpression,
variables: &mut VariableRecord,
expressions: &mut ExpressionRecord,
template: &mut Vec<AdjustableTemplate>,
dst_var: &Option<Rc<Variable>>,
) -> Arc<Self> {
let key = HashableRc::new(Rc::clone(&expr.data));
if let Some(v) = expressions.lines.get(&key) {
return Arc::clone(v);
}
let compiled = match expr.data.as_ref() {
UnrolledExpressionData::VariableAccess(var) => {
compile_variable(var, variables, expressions, template, dst_var)
}
UnrolledExpressionData::Boxed(expr) => {
Expression::compile(expr, variables, expressions, template, dst_var)
}
UnrolledExpressionData::LineFromPoints(p1, p2) => Arc::new(Expression::new(
LineExpr::Line(LinePoint {
a: Expression::compile(p1, variables, expressions, template, dst_var),
b: Expression::compile(p2, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::ParallelThrough(line, point) => Arc::new(Expression::new(
LineExpr::ParallelThrough(ParallelThrough {
line: Expression::compile(line, variables, expressions, template, dst_var),
point: Expression::compile(point, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::PerpendicularThrough(line, point) => Arc::new(Expression::new(
LineExpr::PerpendicularThrough(PerpendicularThrough {
line: Expression::compile(line, variables, expressions, template, dst_var),
point: Expression::compile(point, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::AngleBisector(v1, v2, v3) => Arc::new(Expression::new(
LineExpr::AngleBisector(AngleBisector {
arm1: Expression::compile(v1, variables, expressions, template, dst_var),
origin: Expression::compile(v2, variables, expressions, template, dst_var),
arm2: Expression::compile(v3, variables, expressions, template, dst_var),
}),
1.0,
)),
_ => unreachable!("A line should never be compiled this way"),
};
expressions.lines.insert(key, Arc::clone(&compiled));
compiled
}
}
impl Compile for Expression<ScalarExpr> {
#[allow(clippy::too_many_lines)]
fn compile(
expr: &UnrolledExpression,
variables: &mut VariableRecord,
expressions: &mut ExpressionRecord,
template: &mut Vec<AdjustableTemplate>,
dst_var: &Option<Rc<Variable>>,
) -> Arc<Self> {
let key = HashableRc::new(Rc::clone(&expr.data));
if let Some(v) = expressions.scalars.get(&key) {
return Arc::clone(v);
}
let compiled = match expr.data.as_ref() {
UnrolledExpressionData::VariableAccess(var) => {
compile_variable(var, variables, expressions, template, dst_var)
}
UnrolledExpressionData::Number(v) => {
if expr.ty == ty::SCALAR {
Arc::new(Expression::new(
ScalarExpr::Literal(Literal { value: *v }),
1.0,
))
} else {
Self::compile(
&UnrolledExpression {
weight: 1.0,
ty: expr.ty.clone(),
span: expr.span,
data: Rc::new(UnrolledExpressionData::SetUnit(
UnrolledExpression {
weight: 1.0,
ty: ty::SCALAR,
span: expr.span,
data: expr.data.clone(),
},
expr.ty
.as_predefined()
.unwrap()
.as_scalar()
.unwrap()
.as_ref()
.unwrap()
.clone(),
)),
},
variables,
expressions,
template,
dst_var,
)
}
}
UnrolledExpressionData::FreeReal => {
let index = template.len();
template.push(AdjustableTemplate::Real);
Arc::new(Expression::new(ScalarExpr::Real(Real { index }), 1.0))
}
UnrolledExpressionData::Boxed(expr) => {
Self::compile(expr, variables, expressions, template, dst_var)
}
UnrolledExpressionData::SetUnit(expr, unit) => Arc::new(Expression::new(
ScalarExpr::SetUnit(SetUnit {
value: Self::compile(
&fix_distance(
expr.clone(),
unit[SimpleUnit::Distance as usize]
- match expr.ty.as_predefined().unwrap().as_scalar().unwrap() {
Some(unit) => unit[SimpleUnit::Distance as usize],
None => 0,
},
dst_var.as_ref().unwrap(),
),
variables,
expressions,
template,
dst_var,
),
unit: unit.clone(),
}),
1.0,
)),
UnrolledExpressionData::PointPointDistance(p1, p2) => Arc::new(Expression::new(
ScalarExpr::PointPointDistance(PointPointDistance {
a: Expression::compile(p1, variables, expressions, template, dst_var),
b: Expression::compile(p2, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::PointLineDistance(p, l) => Arc::new(Expression::new(
ScalarExpr::PointLineDistance(PointLineDistance {
point: Expression::compile(p, variables, expressions, template, dst_var),
line: Expression::compile(l, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::Negate(expr) => Arc::new(Expression::new(
ScalarExpr::Negation(Negation {
value: Self::compile(expr, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::Add(v1, v2) => Arc::new(Expression::new(
ScalarExpr::Sum(Sum {
a: Self::compile(v1, variables, expressions, template, dst_var),
b: Self::compile(v2, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::Subtract(v1, v2) => Arc::new(Expression::new(
ScalarExpr::Difference(Difference {
a: Self::compile(v1, variables, expressions, template, dst_var),
b: Self::compile(v2, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::Multiply(v1, v2) => Arc::new(Expression::new(
ScalarExpr::Product(Product {
a: Self::compile(v1, variables, expressions, template, dst_var),
b: Self::compile(v2, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::Divide(v1, v2) => Arc::new(Expression::new(
ScalarExpr::Quotient(Quotient {
a: Self::compile(v1, variables, expressions, template, dst_var),
b: Self::compile(v2, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::ThreePointAngle(v1, v2, v3) => Arc::new(Expression::new(
ScalarExpr::AnglePoint(AnglePoint {
arm1: Expression::compile(v1, variables, expressions, template, dst_var),
origin: Expression::compile(v2, variables, expressions, template, dst_var),
arm2: Expression::compile(v3, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::TwoLineAngle(v1, v2) => Arc::new(Expression::new(
ScalarExpr::AngleLine(AngleLine {
k: Expression::compile(v1, variables, expressions, template, dst_var),
l: Expression::compile(v2, variables, expressions, template, dst_var),
}),
1.0,
)),
UnrolledExpressionData::Average(exprs) => Arc::new(Expression::new(
ScalarExpr::Average(Average {
items: exprs
.iter()
.map(|expr| Self::compile(expr, variables, expressions, template, dst_var))
.collect(),
}),
1.0,
)),
_ => unreachable!("A scalar should never be compiled this way"),
};
expressions.scalars.insert(key, Arc::clone(&compiled));
compiled
}
}
fn compile_variable<T>(
var: &Rc<Variable>,
variables: &mut VariableRecord,
expressions: &mut ExpressionRecord,
template: &mut Vec<AdjustableTemplate>,
dst_var: &Option<Rc<Variable>>,
) -> Arc<Expression<T>>
where
VariableRecord: Mapping<Variable, T>,
Expression<T>: Compile,
{
let key = HashableRc::new(Rc::clone(var));
if let Some(v) = variables.get(&key) {
return Arc::clone(v);
}
let compiled = Expression::compile(&var.definition, variables, expressions, template, dst_var);
variables.insert(HashableRc::new(Rc::clone(var)), compiled.clone());
compiled
}
fn compile_rules(
unrolled: Vec<UnrolledRule>,
variables: &mut VariableRecord,
expressions: &mut ExpressionRecord,
template: &mut Vec<AdjustableTemplate>,
dst_var: &Option<Rc<Variable>>,
) -> Vec<Criteria> {
unrolled
.into_iter()
.map(|rule| {
let crit = match rule.kind {
UnrolledRuleKind::Eq => {
if rule.lhs.ty == ty::POINT {
let lhs = Expression::compile(
&rule.lhs,
variables,
expressions,
template,
dst_var,
);
let rhs = Expression::compile(
&rule.rhs,
variables,
expressions,
template,
dst_var,
);
Weighed::one(CriteriaKind::EqualPoint(lhs, rhs))
} else {
let lhs = Expression::compile(
&rule.lhs,
variables,
expressions,
template,
dst_var,
);
let rhs = Expression::compile(
&rule.rhs,
variables,
expressions,
template,
dst_var,
);
Weighed::one(CriteriaKind::EqualScalar(lhs, rhs))
}
}
UnrolledRuleKind::Gt => {
let lhs =
Expression::compile(&rule.lhs, variables, expressions, template, dst_var);
let rhs =
Expression::compile(&rule.rhs, variables, expressions, template, dst_var);
Weighed::one(CriteriaKind::Greater(lhs, rhs))
}
UnrolledRuleKind::Lt => {
let lhs =
Expression::compile(&rule.lhs, variables, expressions, template, dst_var);
let rhs =
Expression::compile(&rule.rhs, variables, expressions, template, dst_var);
Weighed::one(CriteriaKind::Less(lhs, rhs))
}
};
if rule.inverted {
Weighed {
object: CriteriaKind::Inverse(Box::new(crit.object)),
weight: crit.weight,
}
} else {
crit
}
})
.collect()
}
fn read_flags(flags: &HashMap<String, Flag>) -> Result<Flags, Error> {
let distance_literals = &flags["distance_literals"];
Ok(Flags {
optimizations: Optimizations {
identical_expressions: flags["optimizations"].as_set().unwrap()
["identical_expressions"]
.as_bool()
.unwrap(),
},
distance_literals: match distance_literals.as_ident().unwrap().as_str() {
"none" => DistanceLiterals::None,
"adjust" => DistanceLiterals::Adjust,
"solve" => DistanceLiterals::Solve,
t => {
return Err(Error::FlagEnumInvalidValue {
error_span: distance_literals.get_span().unwrap(),
available_values: &["none", "adjust", "solve"],
received_value: t.to_string(),
})
}
},
point_bounds: flags["point_bounds"].as_bool().unwrap(),
})
}
pub fn compile(
input: &str,
canvas_size: (usize, usize),
) -> Result<
(
Vec<Criteria>,
Figure,
Vec<AdjustableTemplate>,
generator::Flags,
),
Error,
> {
let (unrolled, mut context) = unroll::unroll(input)?;
let flags = read_flags(&context.flags)?;
let are_literals_present = {
context
.variables
.values()
.map(|var| var.definition.has_distance_literal())
.find(Option::is_some)
.flatten()
.or_else(|| {
unrolled
.iter()
.map(|rule| {
rule.lhs
.has_distance_literal()
.or_else(|| rule.rhs.has_distance_literal())
})
.find(Option::is_some)
.flatten()
})
};
let dst_var = if let Some(at) = are_literals_present {
match flags.distance_literals {
DistanceLiterals::Adjust => {
Some(Rc::clone(
context
.variables
.entry(String::from("@distance"))
.or_insert_with(|| {
Rc::new(Variable {
name: String::from("@distance"),
definition_span: span!(0, 0, 0, 0),
definition: UnrolledExpression {
weight: 0.1, data: Rc::new(UnrolledExpressionData::FreeReal),
ty: ty::SCALAR,
span: span!(0, 0, 0, 0),
},
meta: VariableMeta::Scalar,
})
}),
))
}
DistanceLiterals::Solve => {
return Err(Error::FetureNotSupported {
error_span: context
.flags
.get(&String::from("distance_literals"))
.unwrap()
.get_span()
.unwrap(),
feature_name: "solve_distance",
})
}
DistanceLiterals::None => {
return Err(Error::RequiredFlagNotSet {
flag_name: "distance_literals",
required_because: at,
flagdef_span: None,
available_values: &["adjust", "solve"],
})
}
}
} else {
None
};
let mut variables = VariableRecord::default();
let mut expressions = ExpressionRecord::default();
let mut template = Vec::new();
for (_, var) in context.variables {
match var.definition.ty.as_predefined().unwrap() {
PredefinedType::Point => {
compile_variable::<PointExpr>(
&var,
&mut variables,
&mut expressions,
&mut template,
&dst_var,
);
}
PredefinedType::Line => {
compile_variable::<LineExpr>(
&var,
&mut variables,
&mut expressions,
&mut template,
&dst_var,
);
}
PredefinedType::Scalar(_) => {
compile_variable::<ScalarExpr>(
&var,
&mut variables,
&mut expressions,
&mut template,
&dst_var,
);
}
PredefinedType::PointCollection(_) => (),
}
}
let mut criteria = compile_rules(
unrolled,
&mut variables,
&mut expressions,
&mut template,
&dst_var,
);
if let Some(dst) = &dst_var {
let dst: Arc<Expression<ScalarExpr>> = compile_variable(
dst,
&mut variables,
&mut expressions,
&mut template,
&dst_var,
);
let dst_any = Arc::new(Expression {
kind: AnyExpr::Scalar(dst.kind.clone()),
weights: dst.weights.clone(),
});
criteria.push(Weighed {
object: CriteriaKind::Bias(dst_any),
weight: 10.0, });
}
add_bounds(&template, &mut criteria, &flags);
let figure = Figure {
points: variables
.points
.into_iter()
.map(|(key, def)| (key.meta.as_point().unwrap().clone(), def))
.filter(|(key, _)| key.display)
.map(|(key, def)| {
(
def,
key.meta.clone().unwrap_or(PointMeta {
letter: 'P',
primes: 0,
index: None,
}),
)
})
.collect(),
lines: Vec::new(),
angles: Vec::new(),
segments: Vec::new(),
canvas_size,
};
Ok((criteria, figure, template, flags))
}
fn add_bounds(
template: &[AdjustableTemplate],
criteria: &mut Vec<Weighed<CriteriaKind>>,
flags: &Flags,
) {
for (i, _) in template.iter().enumerate().filter(|v: _| v.1.is_point()) {
for (j, _) in template
.iter()
.enumerate()
.skip(i + 1)
.filter(|v: _| v.1.is_point())
{
criteria.push(Weighed {
object: CriteriaKind::Inverse(Box::new(CriteriaKind::EqualPoint(
Arc::new(Expression {
weights: Weights::one_at(i),
kind: PointExpr::Free(FreePoint { index: i }),
}),
Arc::new(Expression {
weights: Weights::one_at(j),
kind: PointExpr::Free(FreePoint { index: j }),
}),
))),
weight: 1.0,
});
}
if flags.point_bounds {
criteria.push(Weighed {
object: CriteriaKind::Greater(
Arc::new(Expression {
weights: Weights::one_at(i),
kind: ScalarExpr::PointX(PointX {
point: Arc::new(Expression {
weights: Weights::one_at(i),
kind: PointExpr::Free(FreePoint { index: i }),
}),
}),
}),
Arc::new(Expression {
weights: Weights::empty(),
kind: ScalarExpr::Literal(Literal { value: 0.0 }),
}),
),
weight: 1.0,
}); criteria.push(Weighed {
object: CriteriaKind::Greater(
Arc::new(Expression {
weights: Weights::one_at(i),
kind: ScalarExpr::PointY(PointY {
point: Arc::new(Expression {
weights: Weights::one_at(i),
kind: PointExpr::Free(FreePoint { index: i }),
}),
}),
}),
Arc::new(Expression {
weights: Weights::empty(),
kind: ScalarExpr::Literal(Literal { value: 1.0 }),
}),
),
weight: 1.0,
}); criteria.push(Weighed {
object: CriteriaKind::Less(
Arc::new(Expression {
weights: Weights::one_at(i),
kind: ScalarExpr::PointX(PointX {
point: Arc::new(Expression {
weights: Weights::one_at(i),
kind: PointExpr::Free(FreePoint { index: i }),
}),
}),
}),
Arc::new(Expression {
weights: Weights::empty(),
kind: ScalarExpr::Literal(Literal { value: 1.0 }),
}),
),
weight: 1.0,
}); criteria.push(Weighed {
object: CriteriaKind::Less(
Arc::new(Expression {
weights: Weights::one_at(i),
kind: ScalarExpr::PointY(PointY {
point: Arc::new(Expression {
weights: Weights::one_at(i),
kind: PointExpr::Free(FreePoint { index: i }),
}),
}),
}),
Arc::new(Expression {
weights: Weights::empty(),
kind: ScalarExpr::Literal(Literal { value: 1.0 }),
}),
),
weight: 1.0,
}); }
}
}