A => examples/centroid.et +11 -0
@@ 0,0 1,11 @@
+(setq A (point 3 3))
+(setq B (point 5 8))
+(setq C (point 7 3))
+(setq triangle_a (triangle A B C))
+(setq O (centroid triangle_a))
+
+triangle_a
+O
+(lineseg A O)
+(lineseg B O)
+(lineseg C O)
A => examples/orthocenter.et +11 -0
@@ 0,0 1,11 @@
+(setq A (point 3 3))
+(setq B (point 5 6))
+(setq C (point 7 3))
+(setq triangle_a (triangle A B C))
+(setq O (orthocenter triangle_a))
+
+triangle_a
+O
+(lineseg A O)
+(lineseg B O)
+(lineseg C O)
M src/lang/functions.rs +81 -1
@@ 1,6 1,6 @@
use crate::interpreter::is_valid_variable;
use crate::lang::types::Angle;
-use crate::lang::types::{Circle, Operation, Point, Triangle, Value};
+use crate::lang::types::{Circle, Lineseg, Operation, Point, Triangle, Value};
use crate::utils::geometry::distance;
/// Macro to implement cloning a boxed trait object
@@ 218,6 218,44 @@ impl Operation for FnAngle {
}
#[derive(Clone)]
+pub struct FnLineseg;
+
+impl FnLineseg {
+ /// Case 1: create a line segment from two points
+ fn from_points(&self, args: &[Value]) -> Result<Value, String> {
+ // check for 2 arguments
+ if args.len() < 2 {
+ return Err("Line segment requires exactly 2 arguments".to_string());
+ }
+
+ // check for 2 points
+ let mut points: Vec<Point> = Vec::new();
+ for arg in args {
+ match arg {
+ Value::Point(p) => points.push(p.clone()),
+ _ => return Err("Invalid types for point".to_string()),
+ }
+ }
+
+ // try creating the line segment
+ Ok(Value::Lineseg(Lineseg {
+ start: points[0],
+ end: points[1],
+ }))
+ }
+}
+
+impl Operation for FnLineseg {
+ clone_impl!(FnLineseg);
+ fn call(&self, args: &[Value]) -> Result<Value, String> {
+ match self.from_points(args) {
+ Ok(lineseg) => Ok(lineseg),
+ _ => Err("Invalid arguments for line segment".to_string()),
+ }
+ }
+}
+
+#[derive(Clone)]
pub struct FnIncenter;
impl Operation for FnIncenter {
clone_impl!(FnIncenter);
@@ 239,6 277,48 @@ impl Operation for FnIncenter {
}
#[derive(Clone)]
+pub struct FnOrthocenter;
+impl Operation for FnOrthocenter {
+ clone_impl!(FnOrthocenter);
+ fn call(&self, args: &[Value]) -> Result<Value, String> {
+ // check for 1 argument
+ if args.len() < 1 {
+ return Err("Orthocenter requires exactly 1 argument".to_string());
+ }
+
+ // check for 1 triangle
+ let triangle = match &args[0] {
+ Value::Triangle(t) => t.clone(),
+ _ => return Err("Invalid types for triangle".to_string()),
+ };
+
+ // try getting the orthocenter
+ return Ok(Value::Point(triangle.orthocenter()));
+ }
+}
+
+#[derive(Clone)]
+pub struct FnCentroid;
+impl Operation for FnCentroid {
+ clone_impl!(FnCentroid);
+ fn call(&self, args: &[Value]) -> Result<Value, String> {
+ // check for 1 argument
+ if args.len() < 1 {
+ return Err("Centroid requires exactly 1 argument".to_string());
+ }
+
+ // check for 1 triangle
+ let triangle = match &args[0] {
+ Value::Triangle(t) => t.clone(),
+ _ => return Err("Invalid types for triangle".to_string()),
+ };
+
+ // try getting the centroid
+ return Ok(Value::Point(triangle.centroid()));
+ }
+}
+
+#[derive(Clone)]
pub struct FnPoint;
impl Operation for FnPoint {
clone_impl!(FnPoint);
M src/lang/types.rs +28 -10
@@ 17,6 17,7 @@ pub enum Value {
Triangle(Triangle),
Angle(Angle),
Circle(Circle),
+ Lineseg(Lineseg),
}
impl Element for Value {
@@ 28,6 29,7 @@ impl Element for Value {
Value::Angle(a) => a.to_svg(),
Value::Circle(c) => c.to_svg(),
Value::String(s) => s.to_svg(),
+ Value::Lineseg(l) => l.to_svg(),
Value::Undefined => vec![Box::new(SvgNothing)],
_ => vec![Box::new(SvgPolygon { points: vec![] })],
}
@@ 66,6 68,23 @@ Basic geometric types
*/
#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Lineseg {
+ pub start: Point,
+ pub end: Point,
+}
+
+impl Element for Lineseg {
+ /// Turn lineseg into a SVG element
+ fn to_svg(&self) -> Vec<Box<dyn Render>> {
+ println!("reached svg");
+ vec![Box::new(SvgLine {
+ start: self.start,
+ end: self.end,
+ })]
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point {
pub x: f64,
pub y: f64,
@@ 250,19 269,18 @@ impl Triangle {
Point { x, y }
}
- /// Return the orthocenter of the triangle
pub fn orthocenter(&self) -> Point {
// calculate the slopes of the sides
- let m1 = (self.b.y - self.a.y) / (self.b.x - self.a.x);
- let m2 = (self.c.y - self.b.y) / (self.c.x - self.b.x);
- let m3 = (self.a.y - self.c.y) / (self.a.x - self.c.x);
+ let m1: f64 = (self.b.y - self.a.y) / (self.b.x - self.a.x);
+ let m2: f64 = (self.c.y - self.b.y) / (self.c.x - self.b.x);
- // calculate the orthocenter
- let x = (m1 * m2 * (self.a.y - self.c.y)
- + m2 * m3 * (self.a.y - self.b.y)
- + m3 * m1 * (self.b.y - self.c.y))
- / (m1 * m2 + m2 * m3 + m3 * m1);
- let y = self.a.y - m1 * (x - self.a.x);
+ // calculate the perpendicular slopes
+ let p1 = -1.0 / m1;
+ let p2 = -1.0 / m2;
+
+ // calculate the coordinates of the orthocenter
+ let x = (-p1 * self.c.x + p2 * self.a.x + self.c.y - self.a.y) / (p2 - p1);
+ let y = p1 * (x - self.c.x) + self.c.y;
Point { x, y }
}
M src/lexer.rs +15 -0
@@ 98,11 98,26 @@ fn match_fn(name: String) -> Function {
args: Vec::new(),
function: Box::new(functions::FnPoint),
},
+ "lineseg" => Function {
+ name,
+ args: Vec::new(),
+ function: Box::new(functions::FnLineseg),
+ },
"incenter" => Function {
name,
args: Vec::new(),
function: Box::new(functions::FnIncenter),
},
+ "orthocenter" => Function {
+ name,
+ args: Vec::new(),
+ function: Box::new(functions::FnOrthocenter),
+ },
+ "centroid" => Function {
+ name,
+ args: Vec::new(),
+ function: Box::new(functions::FnCentroid),
+ },
// functions that return properties
"inradius" => Function {
M src/main.rs +0 -2
@@ 36,11 36,9 @@ fn main() {
// open file and read into string
let contents = std::fs::read_to_string(filename).expect("Failed to read file");
- println!("{}", contents);
// tokenize string
let tokens: Vec<Token> = tokenize(contents);
- println!("{:?}", tokens);
// evaluate tokens
let values: Vec<Value> = match evaluate(tokens) {
M src/renderer.rs +2 -0
@@ 254,6 254,7 @@ pub struct SvgLine {
impl Render for SvgLine {
impl_as_any!(SvgLine);
fn render(&self) -> String {
+ println!("reached");
format!(
"\t<line x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\" stroke=\"black\" stroke-width=\"0.02\"/>\n",
self.start.x, self.start.y, self.end.x, self.end.y
@@ 443,6 444,7 @@ fn label(svg: &mut Svg) {
pub fn render(values: Vec<Value>, is_label: bool) -> Result<String, String> {
let mut elements: Vec<Box<dyn Render>> = Vec::new();
for value in values {
+ println!("{:?}", value);
let svg_elements: Vec<Box<dyn Render>> = value.to_svg();
elements.extend(svg_elements);
}