use crate::{
Id,
builtin::ExporterAccess,
eval::{self, *},
lower::{Identifiable, ir},
model::{Attributes, CustomCommand, ExportCommand, MeasureCommand, ResolutionAttribute},
parameter,
};
use microcad_lang_base::{Identifier, PushDiag, SrcRef};
use miette::Diagnostic;
use std::str::FromStr;
use microcad_core::{Color, Size2};
use thiserror::Error;
#[derive(Debug, Error, Diagnostic)]
pub enum AttributeError {
#[error("Cannot assign attribute to expression `{0}`")]
CannotAssignAttribute(String),
#[error("Not found: {0}")]
NotFound(Identifier),
#[error("Invalid command list for attribute `{0}`")]
InvalidCommand(Identifier),
}
impl Eval<Option<ExportCommand>> for ir::AttributeCommand {
fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<ExportCommand>> {
match self {
ir::AttributeCommand::Call(call) => {
match ArgumentMatch::find_match(
&call.argument_list.eval(context)?,
&[
parameter!(filename: String),
(
Identifier::no_ref("size"),
eval::ParameterValue {
specified_type: Some(Type::Tuple(Box::new(TupleType::new_size2()))),
default_value: Some(Value::Tuple(Box::new(Size2::A4.into()))),
src_ref: SrcRef::none(),
},
),
]
.into_iter()
.collect(),
) {
Ok(arguments) => {
let filename: std::path::PathBuf =
arguments.get::<String>("filename").into();
let id: Option<Id> = if let Ok(id) = arguments.by_str::<String>("id") {
Some(id.into())
} else {
None
};
match context.find_exporter(&filename, &id) {
Ok(exporter) => Ok(Some(ExportCommand { filename, exporter })),
Err(err) => {
context.warning(self, err)?;
Ok(None)
}
}
}
Err(err) => {
context.warning(self, err)?;
Ok(None)
}
}
}
ir::AttributeCommand::Assignment { value, .. } => {
let value: Value = value.eval(context)?;
match value {
Value::String(filename) => {
let filename = std::path::PathBuf::from(filename);
match context.find_exporter(&filename, &None) {
Ok(exporter) => Ok(Some(ExportCommand { filename, exporter })),
Err(err) => {
context.warning(self, err)?;
Ok(None)
}
}
}
_ => unimplemented!(),
}
}
_ => Ok(None),
}
}
}
impl Eval<Vec<ExportCommand>> for ir::Attribute {
fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<ExportCommand>> {
self.commands
.iter()
.try_fold(Vec::new(), |mut commands, attribute| {
assert_eq!(attribute.id_as_str(), "export");
if let Some(export_command) = attribute.eval(context)? {
commands.push(export_command)
}
Ok(commands)
})
}
}
impl Eval<Vec<MeasureCommand>> for ir::Attribute {
fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<MeasureCommand>> {
let mut commands = Vec::new();
for command in &self.commands {
match command {
ir::AttributeCommand::Call(_) => match command.id_as_str() {
"width" => commands.push(MeasureCommand::Width),
"height" => commands.push(MeasureCommand::Height),
"size" => commands.push(MeasureCommand::Size),
_ => context
.warning(self, AttributeError::InvalidCommand(command.id().clone()))?,
},
_ => unimplemented!(),
}
}
Ok(commands)
}
}
impl Eval<Vec<CustomCommand>> for ir::Attribute {
fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<CustomCommand>> {
let mut commands = Vec::new();
for command in &self.commands {
match command {
ir::AttributeCommand::Call(call) => {
match context.exporters().exporter_by_id(command.id().id()) {
Ok(exporter) => {
match ArgumentMatch::find_match(
&call.argument_list.eval(context)?,
&exporter.model_parameters(),
) {
Ok(tuple) => commands.push(CustomCommand::new(command.id(), tuple)),
Err(err) => {
context.warning(self, err)?;
}
}
}
Err(err) => {
context.warning(self, err)?;
}
}
}
ir::AttributeCommand::Assignment { name, .. } => {
match context.exporters().exporter_by_id(name.id()) {
Ok(_) => {
commands.push(CustomCommand::new(command.id(), Default::default()))
}
Err(err) => {
context.warning(self, err)?;
}
}
}
_ => unimplemented!(),
}
}
Ok(commands)
}
}
impl Eval<Option<Color>> for ir::AttributeCommand {
fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<Color>> {
match self {
ir::AttributeCommand::Assignment { value, .. } => {
let value: Value = value.eval(context)?;
match value {
Value::String(s) => match Color::from_str(&s) {
Ok(color) => Ok(Some(color)),
Err(err) => {
context.warning(self, err)?;
Ok(None)
}
},
Value::Tuple(tuple) => match Color::try_from(tuple.as_ref()) {
Ok(color) => Ok(Some(color)),
Err(err) => {
context.warning(self, err)?;
Ok(None)
}
},
_ => {
context.warning(
self,
AttributeError::InvalidCommand(Identifier::no_ref("color")),
)?;
Ok(None)
}
}
}
_ => todo!(),
}
}
}
impl Eval<Option<ResolutionAttribute>> for ir::AttributeCommand {
fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<ResolutionAttribute>> {
match self {
ir::AttributeCommand::Assignment { value, .. } => {
let value: Value = value.eval(context)?;
match value {
Value::Quantity(qty) => match qty.quantity_type {
QuantityType::Scalar => Ok(Some(ResolutionAttribute::Relative(qty.value))),
QuantityType::Length => Ok(Some(ResolutionAttribute::Absolute(qty.value))),
_ => unimplemented!(),
},
_ => todo!("Error handling"),
}
}
_ => {
context.warning(
self,
AttributeError::InvalidCommand(Identifier::no_ref("resolution")),
)?;
Ok(None)
}
}
}
}
impl Eval<Option<Size2>> for ir::AttributeCommand {
fn eval(&self, _: &mut EvalContext) -> EvalResult<Option<Size2>> {
todo!("Get Size2, e.g. `size = (width = 10mm, height = 10mm) from AttributeCommand")
}
}
macro_rules! eval_to_attribute {
($id:ident: $ty:ty) => {
impl Eval<Option<$ty>> for ir::Attribute {
fn eval(&self, context: &mut EvalContext) -> EvalResult<Option<$ty>> {
let command = self.commands.first().expect("empty attribute");
assert_eq!(command.id_ref().to_string(), stringify!($id));
match self.single_command() {
Some(command) => Ok(command.eval(context)?),
None => {
context.warning(self, AttributeError::InvalidCommand(command.id()))?;
Ok(None)
}
}
}
}
};
}
eval_to_attribute!(color: Color);
eval_to_attribute!(resolution: ResolutionAttribute);
eval_to_attribute!(size: Size2);
impl Eval<Vec<crate::model::Attribute>> for ir::Attribute {
fn eval(&self, context: &mut EvalContext) -> EvalResult<Vec<crate::model::Attribute>> {
use crate::model::Attribute as Attr;
self.commands
.iter()
.map(|command| {
Ok(match command.id_as_str() {
"color" => match self.eval(context)? {
Some(color) => vec![Attr::Color(color)],
None => Default::default(),
},
"resolution" => match self.eval(context)? {
Some(resolution) => vec![Attr::Resolution(resolution)],
None => Default::default(),
},
"size" => match self.eval(context)? {
Some(size) => vec![Attr::Size(size)],
None => Default::default(),
},
"export" => {
let exports: Vec<ExportCommand> = self.eval(context)?;
exports.iter().cloned().map(Attr::Export).collect()
}
"measure" => {
let measures: Vec<MeasureCommand> = self.eval(context)?;
measures.iter().cloned().map(Attr::Measure).collect()
}
_ => {
let commands: Vec<CustomCommand> = self.eval(context)?;
commands.iter().cloned().map(Attr::Custom).collect()
}
})
})
.flat_map(|res| match res {
Ok(res) => res.into_iter().map(Ok).collect(),
Err(err) => vec![Err(err)],
})
.collect()
}
}
impl Eval<crate::model::Attributes> for ir::AttributeList {
fn eval(&self, context: &mut EvalContext) -> EvalResult<crate::model::Attributes> {
Ok(Attributes(self.iter().try_fold(
Vec::new(),
|mut attributes, attribute| -> EvalResult<_> {
attributes.append(&mut attribute.eval(context)?);
Ok(attributes)
},
)?))
}
}