Typechecking for assertions

This commit is contained in:
elvis
2025-07-30 03:58:34 +02:00
parent c28a8c9685
commit fde3c45a12
2 changed files with 458 additions and 12 deletions

View File

@ -20,16 +20,7 @@ pub enum Tree {
For(Variable, Range, Box<Tree>), For(Variable, Range, Box<Tree>),
} }
#[derive(Debug, Clone)] pub type Variable = String;
pub struct Variable {
data: String,
}
impl Variable {
pub fn from(data: String) -> Self {
Self { data }
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum AssignmentVariable { pub enum AssignmentVariable {
@ -37,6 +28,15 @@ pub enum AssignmentVariable {
QualifiedVar(Variable, QualifierRestricted) QualifiedVar(Variable, QualifierRestricted)
} }
impl fmt::Display for AssignmentVariable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Var(v) => write!(f, "{v}"),
Self::QualifiedVar(v, q) => write!(f, "{v}.{q}"),
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Expression { pub enum Expression {
True, True,
@ -66,7 +66,36 @@ pub enum Unary {
Empty, Empty,
Length, Length,
ToStr, ToStr,
Qualifier(Qualifier) Qualifier(Qualifier),
}
impl Unary {
fn associated_types_unary(&self) -> Vec<AssertionTypes> {
match self {
Unary::Not => vec![AssertionTypes::Boolean],
Unary::Rand => vec![AssertionTypes::Integer],
Unary::Empty => vec![AssertionTypes::Set],
Unary::Length => vec![AssertionTypes::String,
AssertionTypes::Set],
Unary::ToStr => vec![AssertionTypes::Boolean,
AssertionTypes::Integer,
AssertionTypes::Element],
Unary::Qualifier(_) => vec![AssertionTypes::Set]
}
}
}
impl fmt::Display for Unary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Unary::Not => write!(f, "not"),
Unary::Rand => write!(f, "rand"),
Unary::Empty => write!(f, ".empty"),
Unary::Length => write!(f, ".length"),
Unary::ToStr => write!(f, ".tostr"),
Unary::Qualifier(q) => write!(f, ".{q}"),
}
}
} }
@ -81,6 +110,27 @@ pub enum QualifierRestricted {
Products, Products,
} }
impl fmt::Display for QualifierRestricted {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
QualifierRestricted::Entities =>
write!(f, "Entities"),
QualifierRestricted::Context =>
write!(f, "Context"),
QualifierRestricted::Reactants =>
write!(f, "Reactants"),
QualifierRestricted::ReactantsAbsent =>
write!(f, "ReactantsAbsent"),
QualifierRestricted::Inhibitors =>
write!(f, "Inhibitors"),
QualifierRestricted::InhibitorsPresent =>
write!(f, "InhibitorsPresent"),
QualifierRestricted::Products =>
write!(f, "Products"),
}
}
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Qualifier { pub enum Qualifier {
AvailableEntities, AvailableEntities,
@ -89,6 +139,18 @@ pub enum Qualifier {
Restricted(QualifierRestricted), Restricted(QualifierRestricted),
} }
impl fmt::Display for Qualifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AvailableEntities => write!(f, "AvailableEntities"),
Self::AllReactants => write!(f, "AllReactants"),
Self::AllInhibitors => write!(f, "AllInhibitors"),
Self::Restricted(q) => write!(f, "{q}"),
}
}
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Binary { pub enum Binary {
And, And,
@ -113,3 +175,387 @@ pub enum Binary {
Max, Max,
CommonSubStr, CommonSubStr,
} }
impl Binary {
fn associate(
&self,
t1: &AssertionTypes,
t2: &AssertionTypes
) -> Result<AssertionTypes, String> {
//
match self {
Self::And |
Self::Or => {
if let (AssertionTypes::Boolean, AssertionTypes::Boolean) =
(t1, t2)
{
return Ok(AssertionTypes::Boolean)
}
},
Self::Xor => {
match (t1, t2) {
(AssertionTypes::Boolean, AssertionTypes::Boolean) =>
return Ok(AssertionTypes::Boolean),
(AssertionTypes::Set, AssertionTypes::Set) =>
return Ok(AssertionTypes::Set),
_ => {}
}
}
Self::Less |
Self::LessEq |
Self::More |
Self::MoreEq =>
match (t1, t2) {
(AssertionTypes::Integer, AssertionTypes::Integer) |
(AssertionTypes::Set, AssertionTypes::Set ) =>
return Ok(AssertionTypes::Boolean),
_ => {}
}
Self::Eq |
Self::NotEq => {
match (t1, t2) {
(AssertionTypes::Integer, AssertionTypes::Integer) |
(AssertionTypes::Boolean, AssertionTypes::Boolean) |
(AssertionTypes::Element, AssertionTypes::Element) |
(AssertionTypes::Label, AssertionTypes::Label) |
(AssertionTypes::String, AssertionTypes::String) |
(AssertionTypes::Set, AssertionTypes::Set ) =>
return Ok(AssertionTypes::Boolean),
_ => {}
}
},
Self::Plus |
Self::Minus |
Self::Times => {
match (t1, t2) {
(AssertionTypes::Integer, AssertionTypes::Integer) =>
return Ok(AssertionTypes::Integer),
(AssertionTypes::Set, AssertionTypes::Set ) =>
return Ok(AssertionTypes::Set),
_ => {}
}
},
Self::Exponential |
Self::Quotient |
Self::Reminder => {
if let (AssertionTypes::Integer, AssertionTypes::Integer) =
(t1, t2) {
return Ok(AssertionTypes::Integer)
}
},
Self::Concat => {
if let (AssertionTypes::String, AssertionTypes::String) =
(t1, t2) {
return Ok(AssertionTypes::String)
}
},
Self::SubStr => {
if let (AssertionTypes::String, AssertionTypes::String) =
(t1, t2) {
return Ok(AssertionTypes::Boolean)
}
},
Self::Min |
Self::Max => {
if let (AssertionTypes::Integer, AssertionTypes::Integer) =
(t1, t2) {
return Ok(AssertionTypes::Integer)
}
},
Self::CommonSubStr => {
if let (AssertionTypes::String, AssertionTypes::String) =
(t1, t2) {
return Ok(AssertionTypes::String)
}
},
}
Err(format!("Expressions have incompatible types: {t1} and {t2} with \
operation {self}."))
}
}
impl fmt::Display for Binary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::And => write!(f, "&&"),
Self::Or => write!(f, "||"),
Self::Xor => write!(f, "^^"),
Self::Less => write!(f, "<"),
Self::LessEq => write!(f, "<="),
Self::More => write!(f, ">"),
Self::MoreEq => write!(f, ">="),
Self::Eq => write!(f, "=="),
Self::NotEq => write!(f, "!="),
Self::Plus => write!(f, "+"),
Self::Minus => write!(f, "-"),
Self::Times => write!(f, "*"),
Self::Exponential => write!(f, "^"),
Self::Quotient => write!(f, "/"),
Self::Reminder => write!(f, "%"),
Self::Concat => write!(f, "::"),
Self::SubStr => write!(f, "substr"),
Self::Min => write!(f, "min"),
Self::Max => write!(f, "max"),
Self::CommonSubStr => write!(f, "commonsubstr"),
}
}
}
use core::fmt;
// -----------------------------------------------------------------------------
use std::collections::HashMap;
struct Context {
data: HashMap<Variable, AssertionTypes>,
ty_return: Option<AssertionTypes>
}
impl Context {
pub fn new() -> Self {
Context {
data: HashMap::new(),
ty_return: None
}
}
pub fn assign(
&mut self,
v: &AssignmentVariable,
ty: AssertionTypes
) -> Result<(), String> {
match v {
AssignmentVariable::Var(v) => {
self.data.insert(v.clone(), ty);
Ok(())
},
AssignmentVariable::QualifiedVar(v, q) => {
if let Some(AssertionTypes::Label) = self.data.get(v) {
Ok(())
} else {
Err(format!("Variable {v} does not have type label while \
trying to assign to qualification {q}"))
}
}
}
}
pub fn return_type(
&mut self,
ty: AssertionTypes
) -> Result<(), String> {
if let Some(ty_return) = self.ty_return {
if ty_return == ty {
Ok(())
} else {
Err(format!("Return statements don't agree: {ty_return} and \
{ty} found."))
}
} else {
self.ty_return = Some(ty);
Ok(())
}
}
pub fn assign_range(
&mut self,
v: &Variable,
ty: AssertionTypes
) -> Result<(), String> {
match ty {
AssertionTypes::RangeSet => {
self.data.insert(v.clone(), AssertionTypes::Element);
Ok(())
},
AssertionTypes::RangeInteger => {
self.data.insert(v.clone(), AssertionTypes::Integer);
Ok(())
},
_ => {
Err(format!("Range has incorrect type {ty}."))
}
}
}
pub fn get(
&self,
v: &AssignmentVariable,
) -> Result<AssertionTypes, String> {
match v {
AssignmentVariable::Var(v) => {
if let Some(ty) = self.data.get(v) {
Ok(*ty)
} else {
Err(format!("Could not find variable {v}."))
}
},
AssignmentVariable::QualifiedVar(var, _) => {
if let Some(ty) = self.data.get(var) {
if *ty == AssertionTypes::Label {
Ok(AssertionTypes::Set)
} else {
Err(format!("Variable {var} is not a label."))
}
} else {
Err(format!("Could not find variable {v}."))
}
}
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum AssertionTypes {
Boolean,
Integer,
String,
Label,
Set,
Element,
NoType,
RangeInteger,
RangeSet,
}
impl fmt::Display for AssertionTypes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AssertionTypes::Boolean => write!(f, "boolean"),
AssertionTypes::Integer => write!(f, "integer"),
AssertionTypes::String => write!(f, "string"),
AssertionTypes::Label => write!(f, "label"),
AssertionTypes::Set => write!(f, "set"),
AssertionTypes::Element => write!(f, "element"),
AssertionTypes::NoType => write!(f, "no type"),
AssertionTypes::RangeInteger => write!(f, "range of integers"),
AssertionTypes::RangeSet => write!(f, "range of set"),
}
}
}
fn typecheck(tree: &Tree, c: &mut Context) -> Result<AssertionTypes, String> {
match tree {
Tree::Concat(t1, t2) => {
typecheck(t1, c)?;
typecheck(t2, c)
},
Tree::If(exp, t) => {
match typecheck_expression(exp, c)? {
AssertionTypes::Boolean => {},
_ => {return Err("Expression in if statement doesn't return a \
boolean.".to_string())}
};
typecheck(t, c)
},
Tree::IfElse(exp, t1, t2) => {
match typecheck_expression(exp, c)? {
AssertionTypes::Boolean => {},
_ => {return Err("Expression in if statement doesn't return a \
boolean.".into())}
};
let type_t1 = typecheck(t1, c)?;
let type_t2 = typecheck(t2, c)?;
if type_t1 == type_t2 {
Ok(type_t1)
} else {
Err("Branches of if statement do not match.".into())
}
},
Tree::Assignment(assignvar, exp) => {
let type_exp = typecheck_expression(exp, c)?;
c.assign(assignvar, type_exp)?;
Ok(AssertionTypes::NoType)
},
Tree::Return(exp) => {
let type_exp = typecheck_expression(exp, c)?;
c.return_type(type_exp)?;
Ok(AssertionTypes::NoType)
},
Tree::For(var, range, t) => {
let type_range = typecheck_range(range, c)?;
c.assign_range(var, type_range)?;
typecheck(t, c)
},
}
}
fn typecheck_expression(
exp: &Expression,
c: &Context
) -> Result<AssertionTypes, String> {
match exp {
Expression::True |
Expression::False => Ok(AssertionTypes::Boolean),
Expression::Integer(_) => Ok(AssertionTypes::Integer),
Expression::Label(_) => Ok(AssertionTypes::Label),
Expression::Set(_) => Ok(AssertionTypes::Set),
Expression::Element(_) => Ok(AssertionTypes::Element),
Expression::String(_) => Ok(AssertionTypes::String),
Expression::Var(v) => c.get(v),
Expression::Unary(u, exp) => {
if let Unary::Qualifier(_) = u {
let type_exp = typecheck_expression(exp, c)?;
if type_exp == AssertionTypes::Label {
Ok(AssertionTypes::Set)
} else {
Err("Trying to get the field of a label, but value is not \
a label.".into())
}
} else {
let type_exp = typecheck_expression(exp, c)?;
let possible_types = u.associated_types_unary();
if possible_types.contains(&type_exp) {
Ok(type_exp)
} else {
Err(format!("Cannot use operation {u} on type {type_exp}"))
}
}
},
Expression::Binary(b, exp1, exp2) => {
let type_exp1 = typecheck_expression(exp1, c)?;
let type_exp2 = typecheck_expression(exp2, c)?;
b.associate(&type_exp1, &type_exp2)
}
}
}
fn typecheck_range(
range: &Range,
c: &mut Context
) -> Result<AssertionTypes, String> {
match range {
Range::IterateInRange(exp1, exp2) => {
let type_exp1 = typecheck_expression(exp1, c)?;
let type_exp2 = typecheck_expression(exp2, c)?;
if let (AssertionTypes::Integer, AssertionTypes::Integer) =
(type_exp1, type_exp2)
{
Ok(AssertionTypes::RangeInteger)
} else {
Err(format!("Expressions in range are not integers, but are: \
{type_exp1} and {type_exp2}."))
}
},
Range::IterateOverSet(exp) => {
let type_exp = typecheck_expression(exp, c)?;
if let AssertionTypes::Set = type_exp {
Ok(AssertionTypes::RangeSet)
} else {
Err(format!("Expressions in range is not a set, but is: \
{type_exp}."))
}
}
}
}
impl RSassert {
pub fn typecheck(&self) -> Result<(), String> {
let mut context = Context::new();
typecheck(&self.tree, &mut context)?;
Ok(())
}
}

View File

@ -211,7 +211,7 @@ AssertAssignmentVar: assert::AssignmentVariable = {
} }
AssertVariable: assert::Variable = { AssertVariable: assert::Variable = {
<v: Literal> => assert::Variable::from(v) <v: Literal> => v
} }
AssertExpression: assert::Expression = { AssertExpression: assert::Expression = {