From b81169fe1452d056c4a3e4d0e4d03e2be768cda5 Mon Sep 17 00:00:00 2001 From: raffitz Date: Fri, 26 Mar 2021 15:56:53 +0000 Subject: [PATCH] Restrict ident labels, fix negation bug --- examples/circle.solid | 9 - examples/sphere.solid | 6 + examples/torus1.solid | 10 +- src/astree.rs | 2 + src/error.rs | 8 + src/main.rs | 371 +++++++++++++++++++++++------------------- 6 files changed, 228 insertions(+), 178 deletions(-) delete mode 100644 examples/circle.solid create mode 100644 examples/sphere.solid diff --git a/examples/circle.solid b/examples/circle.solid deleted file mode 100644 index d137fe8..0000000 --- a/examples/circle.solid +++ /dev/null @@ -1,9 +0,0 @@ -# Circle with radius s - -define @-s 0 - s - -@-s <= x <= s -@-s <= y <= s -@-s <= z <= s - -r <= s diff --git a/examples/sphere.solid b/examples/sphere.solid new file mode 100644 index 0000000..bf857e6 --- /dev/null +++ b/examples/sphere.solid @@ -0,0 +1,6 @@ +# Sphere with radius s +-s <= x <= s +-s <= y <= s +-s <= z <= s + +r <= s diff --git a/examples/torus1.solid b/examples/torus1.solid index 44eec0b..6035304 100644 --- a/examples/torus1.solid +++ b/examples/torus1.solid @@ -1,9 +1,7 @@ -# Circle with radius s +# torus with radius s, inner radius 2s -define @-s 0 - s - -5*@-s <= x <= 5*s -5*@-s <= y <= 5*s -@-s <= z <= s +-5*s <= x <= 5*s +-5*s <= y <= 5*s +-s <= z <= s (ρ - 3*s)^2 + z^2 <= s^2 diff --git a/src/astree.rs b/src/astree.rs index 57bd661..7c481c4 100644 --- a/src/astree.rs +++ b/src/astree.rs @@ -15,6 +15,7 @@ pub enum FunctionType { Exp, Ln, Log, + Neg, } #[derive(Debug)] @@ -49,6 +50,7 @@ impl FunctionData { FunctionType::Exp => Ok(value.exp()), FunctionType::Ln => Ok(value.ln()), FunctionType::Log => Ok(value.log10()), + FunctionType::Neg => Ok(-value), } } } diff --git a/src/error.rs b/src/error.rs index 13eed02..5b57752 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ use lodepng::Error as LPNGError; +use std::io::Error as IOError; #[derive(Debug)] pub enum Error { @@ -14,6 +15,7 @@ pub enum Error { IllegarBoundedVar, UnboundedVar, LodePNG(LPNGError), + Io(IOError), } impl From<()> for Error { @@ -27,3 +29,9 @@ impl From for Error { Error::LodePNG(lode) } } + +impl From for Error { + fn from(io: IOError) -> Self { + Error::Io(io) + } +} diff --git a/src/main.rs b/src/main.rs index 6376407..1a3b266 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ mod error; macro_rules! scale_message { ($n:ident) => { - Err(format!("<{}> is not a positive integer", $n)) + Err(format!("<{}> is not a valid scale value", $n)) }; } @@ -81,7 +81,7 @@ pomelo! { %type #[regex("s|x|y|z|r|ρ|θ|φ", read_var)] Var char; - %type #[regex(r"@[^@ \t\f\n]+", |lex| String::from(lex.slice()))] Ident String; + %type #[regex(r"@[\p{Letter}\p{Number}\p{Greek}]+", |lex| String::from(lex.slice()))] Ident String; %type #[token("set")] #[token("define")] @@ -117,7 +117,7 @@ pomelo! { #[token("τ", |_| std::f64::consts::TAU)] #[regex("√2\\s", |_| std::f64::consts::SQRT_2)] #[regex("√(2)", |_| std::f64::consts::SQRT_2)] - #[regex(r"[+-]?(?:\d*\.)?\d+", |lex| lex.slice().parse())] + #[regex(r"(?:\d*\.)?\d+", |lex| lex.slice().parse())] Float f64; %type #[token("+")] Sum; @@ -152,6 +152,7 @@ pomelo! { #[token("exp", |_| FunctionType::Exp)] #[token("ln", |_| FunctionType::Ln)] #[token("log", |_| FunctionType::Log)] + #[token("neg", |_| FunctionType::Neg)] Function FunctionType; %type #[token("(")] LParen; @@ -235,6 +236,7 @@ pomelo! { expr ::= expr(L) Power expr(R) { Expression::operation('^',L,R) } expr ::= Function(F) expr(A) { Expression::function(F,A) } expr ::= LParen expr(E) RParen { E } + expr ::= Subtraction expr(E) { Expression::function(FunctionType::Neg,E) } expr ::= Var(V) { Expression::var(V) } expr ::= Float(F) { Expression::float(F) } expr ::= Ident(S) { Expression::ident(S) } @@ -256,15 +258,10 @@ fn main() -> Result<(), error::Error> { .multiple(false) .value_name("N") .validator(|n: String| -> Result<(), String> { - match n.parse::() { - Ok(x) => { - if x > 0 { - Ok(()) - } else { - scale_message!(n) - } - } - Err(_) => scale_message!(n), + if n.parse::().is_err() { + scale_message!(n) + } else { + Ok(()) } }), ) @@ -276,6 +273,22 @@ fn main() -> Result<(), error::Error> { .takes_value(false) .multiple(false), ) + .arg( + Arg::with_name("debug") + .short("d") + .long("debug") + .help("Show parsing steps") + .takes_value(false) + .multiple(false), + ) + .arg( + Arg::with_name("test") + .short("t") + .long("test") + .help("Parses the input file, does not output") + .takes_value(false) + .multiple(false), + ) .arg( Arg::with_name("FILE") .help("The file describing the shape to map") @@ -298,184 +311,216 @@ fn main() -> Result<(), error::Error> { Ok(_) => Ok(()), Err(error) => Err(io_error(error, &path)), } - }), + }) + .conflicts_with("test"), ) .get_matches(); - let scale = matches.value_of("scale").map(|s| s.parse::().unwrap()); + let scale = matches.value_of("scale").map(|s| s.parse::().unwrap()); let mut object_description = fs::File::open(matches.value_of("FILE").unwrap()).unwrap(); - let output_folder = matches.value_of("OUTPUT_DIR").unwrap(); - let offset = matches.is_present("offset"); + let debug = matches.is_present("debug"); + let test = matches.is_present("test"); + + let output_folder = if test { + "" + } else { + matches.value_of("OUTPUT_DIR").unwrap() + }; let mut data = String::new(); - if object_description.read_to_string(&mut data).is_ok() { - let lex = parser::Token::lexer(&data); + let read_count = object_description.read_to_string(&mut data)?; - let mut p = parser::Parser::new(); + if debug { + println!( + "\nRead {} bytes, scale is {}", + read_count, + scale.unwrap_or(1.0_f64) + ); + } - let mut line_ends = false; + let lex = parser::Token::lexer(&data); - for token in lex { - //println!("{:?}", token); - if token == parser::Token::LineEnd { - if line_ends { - continue; - } else { - line_ends = true; - } + let mut p = parser::Parser::new(); + + let mut line_ends = false; + + for token in lex { + if debug { + println!("{:?}", token); + } + if token == parser::Token::LineEnd { + if line_ends { + continue; } else { - line_ends = false; + line_ends = true; } - p.parse(token)?; + } else { + line_ends = false; } + p.parse(token)?; + } - let (assigns, limits, tree) = p.end_of_input()?; - - let idents = assigns.unwrap_or_default(); - let ident_arg = Some(&idents); - //println!("\n{:?}", tree); - //println!("\nRead {} bytes, scale is {}", size, scale.unwrap_or(1)); - let mut min_x: Option = None; - let mut max_x: Option = None; - let mut min_y: Option = None; - let mut max_y: Option = None; - let mut min_z: Option = None; - let mut max_z: Option = None; - - let mut vars = HashMap::new(); - vars.insert('s', scale.unwrap_or(1) as f64); - - for limit in &limits { - for dep in limit.min.var_dependencies(&ident_arg)? { - if dep != 's' { - eprintln!("Boundaries can only refer to s, not {}", dep); - return Err(Error::IllegalVarInBoundary); - } + let (assigns, limits, tree) = p.end_of_input()?; + + let idents = assigns.unwrap_or_default(); + let ident_arg = Some(&idents); + let mut min_x: Option = None; + let mut max_x: Option = None; + let mut min_y: Option = None; + let mut max_y: Option = None; + let mut min_z: Option = None; + let mut max_z: Option = None; + + let mut vars = HashMap::new(); + vars.insert('s', scale.unwrap_or(1_f64)); + + for limit in &limits { + for dep in limit.min.var_dependencies(&ident_arg)? { + if dep != 's' { + eprintln!("Boundaries can only refer to s, not {}", dep); + return Err(Error::IllegalVarInBoundary); } - for dep in limit.max.var_dependencies(&ident_arg)? { - if dep != 's' { - eprintln!("Boundaries can only refer to s, not {}", dep); - return Err(Error::IllegalVarInBoundary); - } + } + for dep in limit.max.var_dependencies(&ident_arg)? { + if dep != 's' { + eprintln!("Boundaries can only refer to s, not {}", dep); + return Err(Error::IllegalVarInBoundary); } - let var_arg = Some(&vars); - let min = - (limit.min.eval(&ident_arg, &var_arg)?.floor() as i64) - if offset { 1 } else { 0 }; - let max = limit.max.eval(&ident_arg, &var_arg)?.ceil() as i64; - match limit.var { - 'x' => { - min_x = Some(min); - max_x = Some(max); - } - 'y' => { - min_y = Some(min); - max_y = Some(max); - } - 'z' => { - min_z = Some(min); - max_z = Some(max); - } - c => { - eprintln!("Bounded variables are x,y,z only, not {}", c); - return Err(Error::IllegarBoundedVar); - } + } + let var_arg = Some(&vars); + let min = + (limit.min.eval(&ident_arg, &var_arg)?.floor() as i64) - if offset { 1 } else { 0 }; + let max = limit.max.eval(&ident_arg, &var_arg)?.ceil() as i64; + match limit.var { + 'x' => { + min_x = Some(min); + max_x = Some(max); + } + 'y' => { + min_y = Some(min); + max_y = Some(max); + } + 'z' => { + min_z = Some(min); + max_z = Some(max); + } + c => { + eprintln!("Bounded variables are x,y,z only, not {}", c); + return Err(Error::IllegarBoundedVar); } } + } - let mut unbounded = false; - if min_x.is_none() || max_x.is_none() { - unbounded = true; - eprintln!("x is unbounded"); - } - if min_y.is_none() || max_y.is_none() { - unbounded = true; - eprintln!("y is unbounded"); - } - if min_z.is_none() || max_z.is_none() { - unbounded = true; - eprintln!("z is unbounded"); - } - if unbounded { - return Err(Error::UnboundedVar); - } + let mut unbounded = false; + if min_x.is_none() || max_x.is_none() { + unbounded = true; + eprintln!("x is unbounded"); + } + if min_y.is_none() || max_y.is_none() { + unbounded = true; + eprintln!("y is unbounded"); + } + if min_z.is_none() || max_z.is_none() { + unbounded = true; + eprintln!("z is unbounded"); + } + if unbounded { + return Err(Error::UnboundedVar); + } + + if debug { + println!("\n{:?}", tree); + } + + if test { + return Ok(()); + } + + let min_x: i64 = min_x.unwrap(); + let max_x: i64 = max_x.unwrap(); + let min_y: i64 = min_y.unwrap(); + let max_y: i64 = max_y.unwrap(); + let min_z: i64 = min_z.unwrap(); + let max_z: i64 = max_z.unwrap(); + + let width: i64 = 1 + max_x - min_x; + let height: i64 = 1 + max_y - min_y; + + let pix_width: usize = 6 * (width as usize) + 1; + let pix_height: usize = 6 * (height as usize) + 1; + let pix_size: usize = pix_width * pix_height; + + for z in min_z..=max_z { + let name = format! {"{}/layer{:04}.png",output_folder,1 + z - min_z}; + + let filled_in = RGBA8::new(0, 0, 0, 255); // Black + let empty = RGBA8::new(255, 255, 255, 255); // White + let multiple_filled_in = RGBA8::new(0, 0, 255, 255); // Blue + let multiple_empty = RGBA8::new(255, 255, 0, 255); // Yellow + let grid = RGBA8::new(255, 128, 128, 255); // Coral (Grid) + + let mut pixels: Vec = Vec::with_capacity(pix_size); + + pixels.resize(pix_size, grid); + + for y in min_y..=max_y { + let square_start_y = 6 * (y - min_y) as usize; + let grid_y = y.abs() % 10 == 0; + for x in min_x..=max_x { + let x_f: f64 = (x as f64) + if offset { 0.5_f64 } else { 0_f64 }; + let y_f: f64 = (y as f64) + if offset { 0.5_f64 } else { 0_f64 }; + let z_f: f64 = (z as f64) + if offset { 0.5_f64 } else { 0_f64 }; + let rho: f64 = (x_f.powi(2) + y_f.powi(2)).sqrt(); + let r: f64 = (z_f.powi(2) + rho.powi(2)).sqrt(); + let tht: f64 = (z_f / rho).atan(); + + let phi: f64; + + if rho < 2_f64 * f64::EPSILON { + phi = f64::NAN; + } else if y_f >= 0_f64 { + phi = (x_f / rho).acos(); + } else { + phi = -((x_f / rho).acos()); + } - let min_x: i64 = min_x.unwrap(); - let max_x: i64 = max_x.unwrap(); - let min_y: i64 = min_y.unwrap(); - let max_y: i64 = max_y.unwrap(); - let min_z: i64 = min_z.unwrap(); - let max_z: i64 = max_z.unwrap(); - - let width: i64 = 1 + max_x - min_x; - let height: i64 = 1 + max_y - min_y; - - let pix_width: usize = 6 * (width as usize) + 1; - let pix_height: usize = 6 * (height as usize) + 1; - let pix_size: usize = pix_width * pix_height; - - for z in min_z..=max_z { - let name = format! {"{}/layer{:04}.png",output_folder,1 + z - min_z}; - - let filled_in = RGBA8::new(0, 0, 0, 255); // Black - let empty = RGBA8::new(255, 255, 255, 255); // White - let multiple_filled_in = RGBA8::new(0, 0, 255, 255); // Blue - let multiple_empty = RGBA8::new(255, 255, 0, 255); // Yellow - let grid = RGBA8::new(255, 128, 128, 255); // Coral (Grid) - - let mut pixels: Vec = Vec::with_capacity(pix_size); - - pixels.resize(pix_size, grid); - - for y in min_y..=max_y { - let square_start_y = 6 * (y - min_y) as usize; - let grid_y = y.abs() % 10 == 0; - for x in min_x..=max_x { - let x_f: f64 = (x as f64) + if offset { 0.5_f64 } else { 0_f64 }; - let y_f: f64 = (y as f64) + if offset { 0.5_f64 } else { 0_f64 }; - let z_f: f64 = (z as f64) + if offset { 0.5_f64 } else { 0_f64 }; - let rho: f64 = (x_f.powi(2) + y_f.powi(2)).sqrt(); - let phi: f64 = y_f.atan2(x_f); - let r: f64 = (z_f.powi(2) + rho.powi(2)).sqrt(); - let tht: f64 = (z_f / rho).atan(); - - vars.insert('x', x_f); - vars.insert('y', y_f); - vars.insert('z', z_f); - vars.insert('ρ', rho); - vars.insert('φ', phi); - vars.insert('r', r); - vars.insert('θ', tht); - - let var_arg = Some(&vars); - - let square_start_x = 6 * (x - min_x) as usize; - - let grid = if grid_y { true } else { x.abs() % 10 == 0 }; - let is_filled = tree.eval(&ident_arg, &var_arg)?; - - let color = match (is_filled, grid) { - (false, false) => empty, - (false, true) => multiple_empty, - (true, false) => filled_in, - (true, true) => multiple_filled_in, - }; - - for pix_x in 0..5 { - for pix_y in 0..5 { - pixels[(1 + square_start_y + pix_y) * pix_width - + 1 - + square_start_x - + pix_x] = color; - } + vars.insert('x', x_f); + vars.insert('y', y_f); + vars.insert('z', z_f); + vars.insert('ρ', rho); + vars.insert('φ', phi); + vars.insert('r', r); + vars.insert('θ', tht); + + let var_arg = Some(&vars); + + let square_start_x = 6 * (x - min_x) as usize; + + let grid = if grid_y { true } else { x.abs() % 10 == 0 }; + let is_filled = tree.eval(&ident_arg, &var_arg)?; + + let color = match (is_filled, grid) { + (false, false) => empty, + (false, true) => multiple_empty, + (true, false) => filled_in, + (true, true) => multiple_filled_in, + }; + + for pix_x in 0..5 { + for pix_y in 0..5 { + pixels[(1 + square_start_y + pix_y) * pix_width + + 1 + + square_start_x + + pix_x] = color; } } } - lodepng::encode32_file(name, &pixels, pix_width, pix_height)?; } + lodepng::encode32_file(name, &pixels, pix_width, pix_height)?; } Ok(())