Created
January 23, 2022 11:53
-
-
Save linuskmr/bb20194205080a3f692497f982011b1b to your computer and use it in GitHub Desktop.
Demonstration of a checker for the forty two languages. Its goal is to verify the usage of variables, i.e. creating, accessing and dropping values, for example on the stack via RAII.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! This is a demonstration of how a checker for the forty two languages could look like. Its goal is to verify the | |
//! usage of variables, i.e. creating, accessing and dropping values, for example on the stack via RAII. | |
use std::collections::{HashSet}; | |
use thiserror::Error; | |
/// Instructions that a checker has to validate, i.e. creating, accessing and dropping variables. | |
enum Instruction { | |
/// Create a new variable | |
Create(String), | |
/// Access a variable for getting or setting | |
Access(String), | |
/// Removes the variable | |
Drop(String), | |
} | |
/// This checker validates source code or an AST by verifying that all variable creations and accesses are valid. | |
/// | |
/// This means that no two variables with the same name can exist and that a not declared variable may not be | |
/// accessed. This concept might be expanded to checking if functions with the specified names and types exists. | |
#[derive(Debug, Eq, PartialEq)] | |
struct Checker<Instructions: Iterator<Item=Instruction>> { | |
instructions: Instructions, | |
/// All currently defined variables | |
variables: HashSet<String>, | |
} | |
/// Indicates that by the checking the code, an error occurred. | |
/// | |
/// This it either because a variable with the same name is already defined ([CheckError::AlreadyDefined]) or a | |
/// unknown variables is accessed ([CheckError::UnknownVariable]). | |
#[derive(Error, Debug, Eq, PartialEq)] | |
enum CheckError { | |
/// Another variable with the same name exists. | |
#[error("Another variable with the name `{0}` exists")] | |
AlreadyDefined(String), | |
/// Access to variable that is not declared yet. | |
#[error("Access to variable `{0}` which is not declared")] | |
UnknownVariable(String), | |
} | |
impl<Instructions: Iterator<Item=Instruction>> Iterator for Checker<Instructions> { | |
type Item = Result<(), CheckError>; | |
fn next(&mut self) -> Option<Self::Item> { | |
// Get the next instruction from the code. If the code is drained, nothing has to be done and we will return | |
// immediately | |
let instruction: Instruction = self.instructions.next()?; | |
// Depending on what the next instruction is, either add a variable to the set or remove one or check if a | |
// variable is in the set. | |
Some(match instruction { | |
Instruction::Create(new_var) => { | |
match self.variables.insert(new_var.clone()) { | |
// Variable not already in set | |
true => Ok(()), | |
// Variable already in set | |
false => Err(CheckError::AlreadyDefined(new_var)), | |
} | |
}, | |
Instruction::Access(get_var) => { | |
match self.variables.contains(&get_var) { | |
true => Ok(()), | |
// No variable with this name | |
false => Err(CheckError::UnknownVariable(get_var)) | |
} | |
}, | |
Instruction::Drop(drop_var) => { | |
match self.variables.remove(&drop_var) { | |
// Variable was present in set | |
true => Ok(()), | |
// Variable not present in set | |
false => Err(CheckError::UnknownVariable(drop_var)), | |
} | |
} | |
}) | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
/// This test verifies that a basic flow of instructions work do not lead to any error. | |
#[test] | |
fn basic_ok() { | |
let checker = Checker { | |
instructions: vec![ | |
Instruction::Create("name".into()), | |
Instruction::Create("age".into()), | |
Instruction::Access("name".into()), | |
Instruction::Drop("age".into()), | |
].into_iter(), | |
variables: HashSet::new(), | |
}; | |
let check_result: Result<(), CheckError> = checker.collect(); | |
assert!(check_result.is_ok()); | |
} | |
/// An already dropped variable will be accessed, which is expected to lead to an [CheckError::UnknownVariable]. | |
#[test] | |
fn already_dropped_var() { | |
let checker = Checker { | |
instructions: vec![ | |
Instruction::Create("name".into()), | |
Instruction::Create("age".into()), | |
Instruction::Access("name".into()), | |
Instruction::Drop("age".into()), | |
Instruction::Access("age".into()), | |
].into_iter(), | |
variables: HashSet::new(), | |
}; | |
let check_result: Result<(), CheckError> = checker.collect(); | |
assert!(matches!(check_result, Err(CheckError::UnknownVariable(_)))); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment