use log::trace;
use std::convert::{TryFrom, TryInto};
use super::errors::{CFGError, CFGResult};
use super::parameters::Parameters;
use crate::ast::{Access, Expression, Meta, Statement, LogArgument};
use crate::environment::VarEnvironment;
use crate::report::{Report, ReportCollection};
use crate::file_definition::{FileID, FileLocation};
type Version = usize;
struct Declaration {
file_id: Option<FileID>,
file_location: FileLocation,
}
impl Declaration {
fn new(file_id: Option<FileID>, file_location: FileLocation) -> Declaration {
Declaration { file_id, file_location }
}
fn file_id(&self) -> Option<FileID> {
self.file_id
}
fn file_location(&self) -> FileLocation {
self.file_location.clone()
}
}
struct DeclarationEnvironment {
declarations: VarEnvironment<Declaration>,
scoped_versions: VarEnvironment<Version>,
global_versions: VarEnvironment<Option<Version>>,
}
impl DeclarationEnvironment {
pub fn new() -> DeclarationEnvironment {
DeclarationEnvironment {
declarations: VarEnvironment::new(),
scoped_versions: VarEnvironment::new(),
global_versions: VarEnvironment::new(),
}
}
pub fn get_declaration(&self, name: &str) -> Option<&Declaration> {
self.declarations.get_variable(name)
}
pub fn add_declaration(
&mut self,
name: &str,
file_id: Option<FileID>,
file_location: FileLocation,
) -> Option<Version> {
self.declarations.add_variable(name, Declaration::new(file_id, file_location));
self.get_next_version(name)
}
pub fn get_current_version(&self, name: &str) -> Option<&Version> {
self.scoped_versions.get_variable(name)
}
fn get_next_version(&mut self, name: &str) -> Option<Version> {
let version = match self.global_versions.get_variable(name) {
None => None,
Some(None) => Some(0),
Some(Some(version)) => Some(version + 1),
};
self.global_versions.add_variable(name, version);
match version {
None => None,
Some(version) => {
self.scoped_versions.add_variable(name, version);
Some(version)
}
}
}
pub fn add_variable_block(&mut self) {
self.declarations.add_variable_block();
self.scoped_versions.add_variable_block();
}
pub fn remove_variable_block(&mut self) {
self.declarations.remove_variable_block();
self.scoped_versions.remove_variable_block();
}
}
impl TryFrom<&Parameters> for DeclarationEnvironment {
type Error = CFGError;
fn try_from(params: &Parameters) -> CFGResult<Self> {
let mut env = DeclarationEnvironment::new();
for name in params.iter() {
let file_id = *params.file_id();
let file_location = params.file_location().clone();
if env.add_declaration(&name.to_string(), file_id, file_location).is_some() {
return Err(CFGError::ParameterNameCollisionError {
name: name.to_string(),
file_id: *params.file_id(),
file_location: params.file_location().clone(),
});
}
}
Ok(env)
}
}
pub fn ensure_unique_variables(
stmt: &mut Statement,
param_data: &Parameters,
reports: &mut ReportCollection,
) -> CFGResult<()> {
assert!(matches!(stmt, Statement::Block { .. }));
let mut env = param_data.try_into()?;
visit_statement(stmt, &mut env, reports);
Ok(())
}
fn visit_statement(
stmt: &mut Statement,
env: &mut DeclarationEnvironment,
reports: &mut ReportCollection,
) {
use Statement::*;
match stmt {
Declaration { meta, name, dimensions, .. } => {
trace!("visiting declared variable `{name}`");
for size in dimensions {
visit_expression(size, env);
}
if let Some(declaration) = env.get_declaration(name) {
reports.push(build_report(name, meta, declaration));
}
match env.add_declaration(name, meta.file_id, meta.file_location()) {
None => {}
Some(version) => {
trace!("renaming declared variable `{name}` to `{name}.{version}`");
*name = format!("{name}.{version}");
}
}
}
Substitution { var, rhe, access, .. } => {
trace!("visiting assigned variable '{var}'");
*var = match env.get_current_version(var) {
Some(version) => {
trace!("renaming assigned shadowing variable `{var}` to `{var}.{version}`");
format!("{var}.{version}")
}
None => var.to_string(),
};
for access in access {
if let Access::ArrayAccess(index) = access {
visit_expression(index, env);
}
}
visit_expression(rhe, env);
}
MultiSubstitution { lhe, rhe, .. } => {
visit_expression(lhe, env);
visit_expression(rhe, env);
}
LogCall { args, .. } => {
use LogArgument::*;
for arg in args {
if let LogExp(value) = arg {
visit_expression(value, env);
}
}
}
Return { value, .. } => {
visit_expression(value, env);
}
ConstraintEquality { lhe, rhe, .. } => {
visit_expression(lhe, env);
visit_expression(rhe, env);
}
Assert { arg, .. } => {
visit_expression(arg, env);
}
InitializationBlock { initializations, .. } => {
for init in initializations {
visit_statement(init, env, reports);
}
}
While { cond, stmt, .. } => {
visit_expression(cond, env);
visit_statement(stmt, env, reports);
}
Block { stmts, .. } => {
env.add_variable_block();
for stmt in stmts {
visit_statement(stmt, env, reports);
}
env.remove_variable_block();
}
IfThenElse { cond, if_case, else_case, .. } => {
visit_expression(cond, env);
visit_statement(if_case, env, reports);
if let Some(else_case) = else_case {
visit_statement(else_case, env, reports);
}
}
}
}
fn visit_expression(expr: &mut Expression, env: &DeclarationEnvironment) {
use Access::*;
use Expression::*;
match expr {
Variable { name, access, .. } => {
trace!("visiting variable '{name}'");
*name = match env.get_current_version(name) {
Some(version) => {
trace!("renaming occurrence of variable `{name}` to `{name}.{version}`");
format!("{name}.{version}")
}
None => name.clone(),
};
for access in access {
if let ArrayAccess(index) = access {
visit_expression(index, env);
}
}
}
InfixOp { lhe, rhe, .. } => {
visit_expression(lhe, env);
visit_expression(rhe, env);
}
PrefixOp { rhe, .. } => {
visit_expression(rhe, env);
}
InlineSwitchOp { cond, if_true, if_false, .. } => {
visit_expression(cond, env);
visit_expression(if_true, env);
visit_expression(if_false, env);
}
Number(_, _) => {}
Call { args, .. } => {
for arg in args {
visit_expression(arg, env);
}
}
Tuple { values, .. } | ArrayInLine { values, .. } => {
for value in values {
visit_expression(value, env);
}
}
ParallelOp { rhe, .. } => {
visit_expression(rhe, env);
}
AnonymousComponent { params, signals, names, .. } => {
for param in params {
visit_expression(param, env)
}
for signal in signals {
visit_expression(signal, env)
}
if let Some(names) = names {
for (_, name) in names {
trace!("visiting variable '{name}'");
*name = match env.get_current_version(name) {
Some(version) => {
trace!(
"renaming occurrence of variable `{name}` to `{name}.{version}`"
);
format!("{name}.{version}")
}
None => name.clone(),
};
}
}
}
}
}
fn build_report(name: &str, primary_meta: &Meta, secondary_decl: &Declaration) -> Report {
CFGError::ShadowingVariableWarning {
name: name.to_string(),
primary_file_id: primary_meta.file_id,
primary_location: primary_meta.file_location(),
secondary_file_id: secondary_decl.file_id(),
secondary_location: secondary_decl.file_location(),
}
.into()
}