add centroid, orthocenter, and line segment functionality
7 files changed, 148 insertions(+), 13 deletions(-)

A => examples/centroid.et
A => examples/orthocenter.et
M src/lang/functions.rs
M src/lang/types.rs
M src/lexer.rs
M src/main.rs
M src/renderer.rs
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);
     }