use crate::ast::{ExprType, Value, VarRef};
use crate::compiler::{self, CallableSyntax, RepeatedSyntax, SingularArgSyntax};
use crate::exec::{Machine, Scope};
use crate::reader::LineCol;
use crate::value::{Error, Result};
use async_trait::async_trait;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt;
use std::io;
use std::mem;
use std::rc::Rc;
use std::str::Lines;
#[derive(Debug)]
pub enum CallError {
ArgumentError(LineCol, String),
EvalError(LineCol, String),
InternalError(LineCol, String),
IoError(io::Error),
NestedError(String),
SyntaxError,
}
impl From<io::Error> for CallError {
fn from(e: io::Error) -> Self {
Self::IoError(e)
}
}
impl From<compiler::Error> for CallError {
fn from(value: compiler::Error) -> Self {
Self::EvalError(value.pos, value.message)
}
}
pub type CallResult = std::result::Result<(), CallError>;
#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub struct SymbolKey(String);
impl<R: AsRef<str>> From<R> for SymbolKey {
fn from(value: R) -> Self {
Self(value.as_ref().to_ascii_uppercase())
}
}
impl fmt::Display for SymbolKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Array {
subtype: ExprType,
dimensions: Vec<usize>,
values: Vec<Value>,
}
impl Array {
pub fn new(subtype: ExprType, dimensions: Vec<usize>) -> Self {
assert!(!dimensions.is_empty());
let mut n = 1;
for dim in &dimensions {
assert!(n > 0);
n *= dim;
}
let mut values = Vec::with_capacity(n);
let value = subtype.default_value();
for _ in 0..n {
values.push(value.clone());
}
Self { subtype, dimensions, values }
}
pub fn dimensions(&self) -> &[usize] {
&self.dimensions
}
pub fn subtype(&self) -> ExprType {
self.subtype
}
fn validate_subscript(i: i32, max: usize) -> Result<usize> {
if i < 0 {
Err(Error::new(format!("Subscript {} cannot be negative", i)))
} else if (i as usize) >= max {
Err(Error::new(format!("Subscript {} exceeds limit of {}", i, max)))
} else {
Ok(i as usize)
}
}
fn native_index(dimensions: &[usize], subscripts: &[i32]) -> Result<usize> {
debug_assert_eq!(
subscripts.len(),
dimensions.len(),
"Invalid number of subscripts; guaranteed valid by the compiler"
);
let mut offset = 0;
let mut multiplier = 1;
let mut k = dimensions.len() - 1;
while k > 0 {
offset += Array::validate_subscript(subscripts[k], dimensions[k])? * multiplier;
debug_assert!(dimensions[k] > 0);
multiplier *= dimensions[k];
k -= 1;
}
offset += Array::validate_subscript(subscripts[k], dimensions[k])? * multiplier;
Ok(offset)
}
pub fn assign(&mut self, subscripts: &[i32], value: Value) -> Result<()> {
debug_assert_eq!(
subscripts.len(),
self.dimensions.len(),
"Invalid number of subscripts; guaranteed valid by the compiler"
);
debug_assert_eq!(
value.as_exprtype(),
self.subtype,
"Invalid types in assignment; guaranteed valid by the compiler"
);
let i = Array::native_index(&self.dimensions, subscripts)?;
self.values[i] = value;
Ok(())
}
pub fn index(&self, subscripts: &[i32]) -> Result<&Value> {
let i = Array::native_index(&self.dimensions, subscripts)?;
let value = &self.values[i];
debug_assert!(value.as_exprtype() == self.subtype);
Ok(value)
}
}
pub enum Symbol {
Array(Array),
Callable(Rc<dyn Callable>),
Variable(Value),
}
impl Symbol {
fn eval_type(&self) -> Option<ExprType> {
match self {
Symbol::Array(array) => Some(array.subtype()),
Symbol::Callable(callable) => callable.metadata().return_type(),
Symbol::Variable(value) => Some(value.as_exprtype()),
}
}
pub fn metadata(&self) -> Option<&CallableMetadata> {
match self {
Symbol::Array(_) => None,
Symbol::Callable(callable) => Some(callable.metadata()),
Symbol::Variable(_) => None,
}
}
fn user_defined(&self) -> bool {
match self {
Symbol::Array(_) => true,
Symbol::Callable(_) => false,
Symbol::Variable(_) => true,
}
}
}
impl fmt::Debug for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Symbol::Array(array) => write!(f, "Array({:?})", array),
Symbol::Callable(callable) => write!(f, "Callable({:?})", callable.metadata()),
Symbol::Variable(value) => write!(f, "Variable({:?})", value),
}
}
}
pub struct Symbols {
globals: HashMap<SymbolKey, Symbol>,
scopes: Vec<HashMap<SymbolKey, Symbol>>,
}
impl Default for Symbols {
fn default() -> Self {
Self { globals: HashMap::default(), scopes: vec![HashMap::default()] }
}
}
impl Symbols {
#[cfg(test)]
pub(crate) fn from(
globals: HashMap<SymbolKey, Symbol>,
scope: HashMap<SymbolKey, Symbol>,
) -> Self {
Self { globals, scopes: vec![scope] }
}
pub(crate) fn enter_scope(&mut self) {
self.scopes.push(HashMap::default());
}
pub(crate) fn leave_scope(&mut self) {
let last = self.scopes.pop();
assert!(last.is_some(), "Must have at least one scope to pop");
assert!(!self.scopes.is_empty(), "Cannot pop the global scope");
}
pub fn add_callable(&mut self, callable: Rc<dyn Callable>) {
let key = SymbolKey::from(callable.metadata().name());
assert!(!self.globals.contains_key(&key));
self.globals.insert(key.to_owned(), Symbol::Callable(callable));
}
pub fn callables(&self) -> HashMap<&SymbolKey, Rc<dyn Callable>> {
let mut callables = HashMap::with_capacity(self.globals.len());
for (key, symbol) in &self.globals {
if let Symbol::Callable(c) = symbol {
callables.insert(key, c.clone());
}
}
callables
}
pub fn locals(&self) -> &HashMap<SymbolKey, Symbol> {
self.scopes.last().unwrap()
}
pub fn clear(&mut self) {
fn filter(key: &SymbolKey, symbol: &mut Symbol) -> bool {
let is_internal = key.0.starts_with(|c: char| c.is_ascii_digit());
let is_gpio_hack = key.0.starts_with("__");
is_internal || is_gpio_hack || !symbol.user_defined()
}
self.globals.retain(filter);
self.scopes.last_mut().unwrap().retain(filter);
}
pub fn dim(&mut self, key: SymbolKey, etype: ExprType) {
debug_assert!(
!self.globals.contains_key(&key) && !self.scopes.last_mut().unwrap().contains_key(&key),
"Pre-existence of variables is checked at compilation time"
);
self.scopes.last_mut().unwrap().insert(key, Symbol::Variable(etype.default_value()));
}
pub fn dim_shared(&mut self, key: SymbolKey, etype: ExprType) {
debug_assert!(
!self.globals.contains_key(&key) && !self.scopes.last_mut().unwrap().contains_key(&key),
"Pre-existence of variables is checked at compilation time"
);
self.globals.insert(key, Symbol::Variable(etype.default_value()));
}
pub fn dim_array(&mut self, key: SymbolKey, subtype: ExprType, dimensions: Vec<usize>) {
debug_assert!(
!self.globals.contains_key(&key) && !self.scopes.last_mut().unwrap().contains_key(&key),
"Pre-existence of variables is checked at compilation time"
);
self.scopes.last_mut().unwrap().insert(key, Symbol::Array(Array::new(subtype, dimensions)));
}
pub fn dim_shared_array(&mut self, key: SymbolKey, subtype: ExprType, dimensions: Vec<usize>) {
debug_assert!(
!self.globals.contains_key(&key) && !self.scopes.last_mut().unwrap().contains_key(&key),
"Pre-existence of variables is checked at compilation time"
);
self.globals.insert(key, Symbol::Array(Array::new(subtype, dimensions)));
}
pub(crate) fn load(&self, key: &SymbolKey) -> Option<&Symbol> {
let local = self.scopes.last().unwrap().get(key);
if local.is_some() {
return local;
}
self.globals.get(key)
}
pub(crate) fn load_mut(&mut self, key: &SymbolKey) -> Option<&mut Symbol> {
let local = self.scopes.last_mut().unwrap().get_mut(key);
if local.is_some() {
return local;
}
self.globals.get_mut(key)
}
pub fn get(&self, vref: &VarRef) -> Result<Option<&Symbol>> {
let key = SymbolKey::from(vref.name());
let symbol = self.load(&key);
if let Some(symbol) = symbol {
let stype = symbol.eval_type();
if !vref.accepts_callable(stype) {
return Err(Error::new(format!("Incompatible types in {} reference", vref)));
}
}
Ok(symbol)
}
pub fn get_auto(&self, var: &str) -> Option<&Symbol> {
let key = SymbolKey::from(var);
self.load(&key)
}
pub fn get_mut(&mut self, vref: &VarRef) -> Result<Option<&mut Symbol>> {
match self.load_mut(&vref.as_symbol_key()) {
Some(symbol) => {
let stype = symbol.eval_type();
if !vref.accepts_callable(stype) {
return Err(Error::new(format!("Incompatible types in {} reference", vref)));
}
Ok(Some(symbol))
}
None => Ok(None),
}
}
pub(crate) fn get_var(&self, vref: &VarRef) -> Result<&Value> {
match self.get(vref)? {
Some(Symbol::Variable(v)) => Ok(v),
Some(_) => Err(Error::new(format!("{} is not a variable", vref.name()))),
None => Err(Error::new(format!("Undefined variable {}", vref.name()))),
}
}
pub(crate) fn assign(&mut self, key: &SymbolKey, value: Value) {
let old_value = match self.globals.get_mut(key) {
Some(value) => Some(value),
None => self.scopes.last_mut().unwrap().get_mut(key),
};
match old_value {
Some(Symbol::Variable(old_value)) => {
debug_assert_eq!(
mem::discriminant(old_value),
mem::discriminant(&value),
"Type consistency is validated at compilation time"
);
*old_value = value;
}
Some(_) => unreachable!("Type consistency is validated at compilation time"),
None => {
self.scopes.last_mut().unwrap().insert(key.clone(), Symbol::Variable(value));
}
}
}
pub fn set_var(&mut self, vref: &VarRef, value: Value) -> Result<()> {
let key = vref.as_symbol_key();
let value = value.maybe_cast(vref.ref_type())?;
match self.get_mut(vref)? {
Some(Symbol::Variable(old_value)) => {
let value = value.maybe_cast(Some(old_value.as_exprtype()))?;
if mem::discriminant(&value) != mem::discriminant(old_value) {
return Err(Error::new(format!(
"Cannot assign value of type {} to variable of type {}",
value.as_exprtype(),
old_value.as_exprtype(),
)));
}
self.assign(&key, value);
Ok(())
}
Some(_) => Err(Error::new(format!("Cannot redefine {} as a variable", vref))),
None => {
if let Some(ref_type) = vref.ref_type() {
if !vref.accepts(value.as_exprtype()) {
return Err(Error::new(format!(
"Cannot assign value of type {} to variable of type {}",
value.as_exprtype(),
ref_type,
)));
}
}
self.scopes.last_mut().unwrap().insert(key, Symbol::Variable(value));
Ok(())
}
}
}
pub(crate) fn unset(&mut self, key: &SymbolKey) -> Result<()> {
match self.scopes.last_mut().unwrap().remove(key) {
Some(_) => Ok(()),
None => Err(Error::new(format!("{} is not defined", key))),
}
}
}
pub struct CallableMetadataBuilder {
name: Cow<'static, str>,
return_type: Option<ExprType>,
category: Option<&'static str>,
syntax: Option<String>,
syntaxes: Vec<CallableSyntax>,
description: Option<&'static str>,
}
impl CallableMetadataBuilder {
pub fn new(name: &'static str) -> Self {
assert!(name == name.to_ascii_uppercase(), "Callable name must be in uppercase");
Self {
name: Cow::Borrowed(name),
return_type: None,
syntax: None,
syntaxes: vec![],
category: None,
description: None,
}
}
pub fn new_dynamic(name: String) -> Self {
Self {
name: Cow::Owned(name.to_ascii_uppercase()),
return_type: None,
syntax: None,
syntaxes: vec![],
category: None,
description: None,
}
}
pub fn with_return_type(mut self, return_type: ExprType) -> Self {
self.return_type = Some(return_type);
self
}
pub fn with_syntax(
mut self,
syntaxes: &'static [(&'static [SingularArgSyntax], Option<&'static RepeatedSyntax>)],
) -> Self {
let syntaxes = syntaxes
.iter()
.map(|s| CallableSyntax::new_static(s.0, s.1))
.collect::<Vec<CallableSyntax>>();
if syntaxes.is_empty() {
self.syntax = Some("no arguments".to_owned());
} else if syntaxes.len() == 1 {
self.syntax = Some(syntaxes.first().unwrap().describe());
} else {
self.syntax = Some(
syntaxes
.iter()
.map(|syn| format!("<{}>", syn.describe()))
.collect::<Vec<String>>()
.join(" | "),
);
};
self.syntaxes = syntaxes;
self
}
pub fn with_dynamic_syntax(
mut self,
syntaxes: Vec<(Vec<SingularArgSyntax>, Option<RepeatedSyntax>)>,
) -> Self {
let syntaxes = syntaxes
.into_iter()
.map(|s| CallableSyntax::new_dynamic(s.0, s.1))
.collect::<Vec<CallableSyntax>>();
if syntaxes.is_empty() {
self.syntax = Some("no arguments".to_owned());
} else if syntaxes.len() == 1 {
self.syntax = Some(syntaxes.first().unwrap().describe());
} else {
self.syntax = Some(
syntaxes
.iter()
.map(|syn| format!("<{}>", syn.describe()))
.collect::<Vec<String>>()
.join(" | "),
);
};
self.syntaxes = syntaxes;
self
}
pub fn with_category(mut self, category: &'static str) -> Self {
self.category = Some(category);
self
}
pub fn with_description(mut self, description: &'static str) -> Self {
for l in description.lines() {
assert!(!l.is_empty(), "Description cannot contain empty lines");
}
self.description = Some(description);
self
}
pub fn build(self) -> CallableMetadata {
assert!(!self.syntaxes.is_empty(), "All callables must specify a syntax");
CallableMetadata {
name: self.name,
return_type: self.return_type,
syntax: self.syntax.expect("All callables must specify a syntax"),
syntaxes: self.syntaxes,
category: self.category.expect("All callables must specify a category"),
description: self.description.expect("All callables must specify a description"),
}
}
pub fn test_build(mut self) -> CallableMetadata {
if self.syntaxes.is_empty() {
self.syntaxes.push(CallableSyntax::new_static(&[], None));
}
CallableMetadata {
name: self.name,
return_type: self.return_type,
syntax: self.syntax.unwrap_or("".to_owned()),
syntaxes: self.syntaxes,
category: self.category.unwrap_or(""),
description: self.description.unwrap_or(""),
}
}
}
#[derive(Clone, Debug)]
pub struct CallableMetadata {
name: Cow<'static, str>,
return_type: Option<ExprType>,
syntax: String,
syntaxes: Vec<CallableSyntax>,
category: &'static str,
description: &'static str,
}
impl CallableMetadata {
pub fn name(&self) -> &str {
&self.name
}
pub fn return_type(&self) -> Option<ExprType> {
self.return_type
}
pub fn syntax(&self) -> &str {
&self.syntax
}
pub(crate) fn syntaxes(&self) -> &[CallableSyntax] {
&self.syntaxes
}
pub fn category(&self) -> &'static str {
self.category
}
pub fn description(&self) -> Lines<'static> {
self.description.lines()
}
pub fn is_argless(&self) -> bool {
self.syntax.is_empty()
}
pub fn is_function(&self) -> bool {
self.return_type.is_some()
}
}
#[async_trait(?Send)]
pub trait Callable {
fn metadata(&self) -> &CallableMetadata;
async fn exec(&self, scope: Scope<'_>, machine: &mut Machine) -> CallResult;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ExprType, VarRef};
use crate::testutils::*;
use std::cell::RefCell;
#[test]
fn test_array_unidimensional_ok() {
let mut array = Array::new(ExprType::Integer, vec![5]);
assert_eq!(ExprType::Integer, array.subtype());
array.assign(&[1], 5.into()).unwrap();
array.assign(&[4], 8.into()).unwrap();
assert_eq!(&Value::Integer(0), array.index(&[0]).unwrap());
assert_eq!(&Value::Integer(5), array.index(&[1]).unwrap());
assert_eq!(&Value::Integer(0), array.index(&[2]).unwrap());
assert_eq!(&Value::Integer(0), array.index(&[3]).unwrap());
assert_eq!(&Value::Integer(8), array.index(&[4]).unwrap());
}
#[test]
fn test_array_unidimensional_errors() {
let mut array = Array::new(ExprType::Integer, vec![5]);
assert_eq!(
"Subscript -1 cannot be negative",
format!("{}", array.assign(&[-1], Value::Integer(1)).unwrap_err())
);
assert_eq!(
"Subscript -1 cannot be negative",
format!("{}", array.index(&[-1]).unwrap_err())
);
assert_eq!(
"Subscript 6 exceeds limit of 5",
format!("{}", array.assign(&[6], Value::Integer(1)).unwrap_err())
);
assert_eq!("Subscript 6 exceeds limit of 5", format!("{}", array.index(&[6]).unwrap_err()));
}
#[test]
fn test_array_bidimensional_ok() {
let mut array = Array::new(ExprType::Double, vec![2, 3]);
assert_eq!(ExprType::Double, array.subtype());
array.assign(&[0, 1], 9.1.into()).unwrap();
array.assign(&[1, 0], 8.1.into()).unwrap();
array.assign(&[1, 2], 7.1.into()).unwrap();
assert_eq!(&Value::Double(0.0), array.index(&[0, 0]).unwrap());
assert_eq!(&Value::Double(9.1), array.index(&[0, 1]).unwrap());
assert_eq!(&Value::Double(0.0), array.index(&[0, 2]).unwrap());
assert_eq!(&Value::Double(8.1), array.index(&[1, 0]).unwrap());
assert_eq!(&Value::Double(0.0), array.index(&[1, 1]).unwrap());
assert_eq!(&Value::Double(7.1), array.index(&[1, 2]).unwrap());
}
#[test]
fn test_array_bidimensional_errors() {
let mut array = Array::new(ExprType::Integer, vec![5, 2]);
assert_eq!(
"Subscript -1 cannot be negative",
format!("{}", array.assign(&[-1, 1], Value::Integer(1)).unwrap_err())
);
assert_eq!(
"Subscript -1 cannot be negative",
format!("{}", array.index(&[-1, 1]).unwrap_err())
);
assert_eq!(
"Subscript -1 cannot be negative",
format!("{}", array.assign(&[1, -1], Value::Integer(1)).unwrap_err())
);
assert_eq!(
"Subscript -1 cannot be negative",
format!("{}", array.index(&[1, -1]).unwrap_err())
);
assert_eq!(
"Subscript 2 exceeds limit of 2",
format!("{}", array.assign(&[-1, 2], Value::Integer(1)).unwrap_err())
);
assert_eq!(
"Subscript 2 exceeds limit of 2",
format!("{}", array.index(&[-1, 2]).unwrap_err())
);
}
#[test]
fn test_array_multidimensional() {
let mut array = Array::new(ExprType::Integer, vec![2, 4, 3, 5]);
assert_eq!(ExprType::Integer, array.subtype());
let mut n = 0;
for i in 0..2 {
for j in 0..4 {
for k in 0..3 {
for l in 0..5 {
array.assign(&[i, j, k, l], n.into()).unwrap();
assert_eq!(&Value::Integer(n), array.index(&[i, j, k, l]).unwrap());
n += 1;
}
}
}
}
let mut n = 0;
for i in 0..2 {
for j in 0..4 {
for k in 0..3 {
for l in 0..5 {
assert_eq!(&Value::Integer(n), array.index(&[i, j, k, l]).unwrap());
n += 1;
}
}
}
}
}
#[test]
fn test_symbols_clear() {
let mut syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", ExprType::Integer)
.add_callable(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.add_callable(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.add_var("__SYSTEM_VAR", Value::Integer(42))
.add_global_var("GLOBAL_VAR", Value::Integer(43))
.add_global_var("__GLOBAL_SYSTEM_VAR", Value::Integer(44))
.build();
assert!(syms.get(&VarRef::new("SOMEARRAY", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("OUT", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SUM", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SOMEVAR", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("__SYSTEM_VAR", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("GLOBAL_VAR", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("__GLOBAL_SYSTEM_VAR", None)).unwrap().is_some());
syms.clear();
assert!(syms.get(&VarRef::new("SOMEARRAY", None)).unwrap().is_none());
assert!(syms.get(&VarRef::new("OUT", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SUM", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SOMEVAR", None)).unwrap().is_none());
assert!(syms.get(&VarRef::new("__SYSTEM_VAR", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("GLOBAL_VAR", None)).unwrap().is_none());
assert!(syms.get(&VarRef::new("__GLOBAL_SYSTEM_VAR", None)).unwrap().is_some());
}
#[test]
fn test_symbols_dim_ok() {
let mut syms = Symbols::default();
syms.dim(SymbolKey::from("A_Boolean"), ExprType::Boolean);
match syms.get(&VarRef::new("a_boolean", None)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Boolean(false), value),
_ => panic!("Got something that is not the variable we asked for"),
}
}
#[test]
fn test_symbols_dim_shared_ok() {
let mut syms = Symbols::default();
syms.dim_shared(SymbolKey::from("A_Boolean"), ExprType::Boolean);
match syms.get(&VarRef::new("a_boolean", None)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Boolean(false), value),
_ => panic!("Got something that is not the variable we asked for"),
}
}
#[test]
fn test_symbols_scopes_dim() {
let mut syms = Symbols::default();
syms.dim(SymbolKey::from("Local_Var"), ExprType::Boolean);
syms.dim(SymbolKey::from("Other_Var"), ExprType::Double);
syms.enter_scope();
syms.dim(SymbolKey::from("Local_Var"), ExprType::Integer);
match syms.get(&VarRef::new("local_var", None)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Integer(0), value),
_ => panic!("Got something that is not the variable we asked for"),
}
assert!(syms.get(&VarRef::new("other_var", None)).unwrap().is_none());
syms.leave_scope();
match syms.get(&VarRef::new("local_var", None)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Boolean(false), value),
_ => panic!("Got something that is not the variable we asked for"),
}
match syms.get(&VarRef::new("other_var", None)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Double(0.0), value),
_ => panic!("Got something that is not the variable we asked for"),
}
}
#[test]
fn test_symbols_scopes_dim_shared() {
let mut syms = Symbols::default();
syms.dim_shared(SymbolKey::from("Global_Var"), ExprType::Boolean);
syms.enter_scope();
match syms.get(&VarRef::new("global_var", None)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Boolean(false), value),
_ => panic!("Got something that is not the variable we asked for"),
}
syms.leave_scope();
match syms.get(&VarRef::new("global_var", None)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Boolean(false), value),
_ => panic!("Got something that is not the variable we asked for"),
}
}
fn assert_same_array_shape(exp_subtype: ExprType, exp_dims: &[usize], symbol: &Symbol) {
match symbol {
Symbol::Array(array) => {
assert_eq!(exp_subtype, array.subtype());
assert_eq!(exp_dims, array.dimensions());
}
_ => panic!("Expected array symbol type, got {:?}", symbol),
}
}
#[test]
fn test_symbols_dim_array_ok() {
let mut syms = Symbols::default();
syms.dim_array(SymbolKey::from("A_Boolean"), ExprType::Boolean, vec![1]);
assert_same_array_shape(
ExprType::Boolean,
&[1],
syms.get(&VarRef::new("a_boolean", None)).unwrap().unwrap(),
);
}
#[test]
fn test_symbols_get_check_types() {
let syms = SymbolsBuilder::default()
.add_array("BOOL_ARRAY", ExprType::Boolean)
.add_callable(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.add_callable(SumFunction::new())
.add_var("STRING_VAR", Value::Text("".to_owned()))
.build();
for ref_type in &[None, Some(ExprType::Boolean)] {
match syms.get(&VarRef::new("bool_array", *ref_type)).unwrap().unwrap() {
Symbol::Array(array) => assert_eq!(ExprType::Boolean, array.subtype()),
_ => panic!("Got something that is not the array we asked for"),
}
}
assert_eq!(
"Incompatible types in bool_array$ reference",
format!("{}", syms.get(&VarRef::new("bool_array", Some(ExprType::Text))).unwrap_err())
);
match syms.get(&VarRef::new("out", None)).unwrap().unwrap() {
Symbol::Callable(c) => assert_eq!(None, c.metadata().return_type()),
_ => panic!("Got something that is not the command we asked for"),
}
assert_eq!(
"Incompatible types in out# reference",
format!("{}", syms.get(&VarRef::new("out", Some(ExprType::Double))).unwrap_err())
);
for ref_type in &[None, Some(ExprType::Integer)] {
match syms.get(&VarRef::new("sum", *ref_type)).unwrap().unwrap() {
Symbol::Callable(f) => {
assert_eq!(Some(ExprType::Integer), f.metadata().return_type())
}
_ => panic!("Got something that is not the function we asked for"),
}
}
assert_eq!(
"Incompatible types in sum# reference",
format!("{}", syms.get(&VarRef::new("sum", Some(ExprType::Double))).unwrap_err())
);
for ref_type in &[None, Some(ExprType::Text)] {
match syms.get(&VarRef::new("string_var", *ref_type)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(ExprType::Text, value.as_exprtype()),
_ => panic!("Got something that is not the variable we asked for"),
}
}
assert_eq!(
"Incompatible types in string_var% reference",
format!(
"{}",
syms.get(&VarRef::new("string_var", Some(ExprType::Integer))).unwrap_err()
)
);
}
#[test]
fn test_symbols_get_case_insensitivity() {
let syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", ExprType::Integer)
.add_callable(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.add_callable(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert!(syms.get(&VarRef::new("somearray", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SomeArray", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("out", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("Out", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("sum", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("Sum", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("somevar", None)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SomeVar", None)).unwrap().is_some());
}
#[test]
fn test_symbols_get_undefined() {
let syms = SymbolsBuilder::default().add_var("SOMETHING", Value::Integer(3)).build();
assert!(syms.get(&VarRef::new("SOME_THIN", Some(ExprType::Integer))).unwrap().is_none());
}
#[test]
fn test_symbols_get_mut_check_types() {
let mut syms = SymbolsBuilder::default()
.add_array("BOOL_ARRAY", ExprType::Boolean)
.add_callable(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.add_callable(SumFunction::new())
.add_var("STRING_VAR", Value::Text("".to_owned()))
.build();
for ref_type in &[None, Some(ExprType::Boolean)] {
match syms.get_mut(&VarRef::new("bool_array", *ref_type)).unwrap().unwrap() {
Symbol::Array(array) => assert_eq!(ExprType::Boolean, array.subtype()),
_ => panic!("Got something that is not the array we asked for"),
}
}
assert_eq!(
"Incompatible types in bool_array$ reference",
format!(
"{}",
syms.get_mut(&VarRef::new("bool_array", Some(ExprType::Text))).unwrap_err()
)
);
match syms.get_mut(&VarRef::new("out", None)).unwrap().unwrap() {
Symbol::Callable(c) => assert_eq!(None, c.metadata().return_type()),
_ => panic!("Got something that is not the command we asked for"),
}
assert_eq!(
"Incompatible types in out# reference",
format!("{}", syms.get_mut(&VarRef::new("out", Some(ExprType::Double))).unwrap_err())
);
for ref_type in &[None, Some(ExprType::Integer)] {
match syms.get_mut(&VarRef::new("sum", *ref_type)).unwrap().unwrap() {
Symbol::Callable(f) => {
assert_eq!(Some(ExprType::Integer), f.metadata().return_type())
}
_ => panic!("Got something that is not the function we asked for"),
}
}
assert_eq!(
"Incompatible types in sum# reference",
format!("{}", syms.get_mut(&VarRef::new("sum", Some(ExprType::Double))).unwrap_err())
);
for ref_type in &[None, Some(ExprType::Text)] {
match syms.get_mut(&VarRef::new("string_var", *ref_type)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(ExprType::Text, value.as_exprtype()),
_ => panic!("Got something that is not the variable we asked for"),
}
}
assert_eq!(
"Incompatible types in string_var% reference",
format!(
"{}",
syms.get_mut(&VarRef::new("string_var", Some(ExprType::Integer))).unwrap_err()
)
);
}
#[test]
fn test_symbols_get_mut_case_insensitivity() {
let mut syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", ExprType::Integer)
.add_callable(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.add_callable(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert!(syms.get_mut(&VarRef::new("somearray", None)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("SomeArray", None)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("out", None)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("Out", None)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("sum", None)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("Sum", None)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("somevar", None)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("SomeVar", None)).unwrap().is_some());
}
#[test]
fn test_symbols_get_mut_undefined() {
let mut syms = SymbolsBuilder::default().add_var("SOMETHING", Value::Integer(3)).build();
assert!(syms
.get_mut(&VarRef::new("SOME_THIN", Some(ExprType::Integer)))
.unwrap()
.is_none());
}
#[test]
fn test_symbols_get_auto() {
let syms = SymbolsBuilder::default()
.add_array("BOOL_ARRAY", ExprType::Boolean)
.add_callable(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.add_callable(SumFunction::new())
.add_var("STRING_VAR", Value::Text("".to_owned()))
.build();
match syms.get_auto("bool_array").unwrap() {
Symbol::Array(array) => assert_eq!(ExprType::Boolean, array.subtype()),
_ => panic!("Got something that is not the array we asked for"),
}
match syms.get_auto("out").unwrap() {
Symbol::Callable(c) => assert_eq!(None, c.metadata().return_type()),
_ => panic!("Got something that is not the command we asked for"),
}
match syms.get_auto("sum").unwrap() {
Symbol::Callable(f) => assert_eq!(Some(ExprType::Integer), f.metadata().return_type()),
_ => panic!("Got something that is not the function we asked for"),
}
match syms.get_auto("string_var").unwrap() {
Symbol::Variable(value) => assert_eq!(ExprType::Text, value.as_exprtype()),
_ => panic!("Got something that is not the variable we asked for"),
}
}
#[test]
fn test_symbols_get_auto_case_insensitivity() {
let syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", ExprType::Integer)
.add_callable(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.add_callable(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert!(syms.get_auto("somearray").is_some());
assert!(syms.get_auto("SomeArray").is_some());
assert!(syms.get_auto("out").is_some());
assert!(syms.get_auto("Out").is_some());
assert!(syms.get_auto("sum").is_some());
assert!(syms.get_auto("Sum").is_some());
assert!(syms.get_auto("somevar").is_some());
assert!(syms.get_auto("SomeVar").is_some());
}
#[test]
fn test_symbols_get_auto_undefined() {
let syms = SymbolsBuilder::default().add_var("SOMETHING", Value::Integer(3)).build();
assert!(syms.get_auto("SOME_THIN").is_none());
}
fn check_var(syms: &Symbols, name: &str, exp_value: Value) {
match syms.get(&VarRef::new(name, None)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(exp_value, *value),
_ => panic!("Got something that is not the variable we asked for"),
}
}
#[test]
fn test_symbols_set_var_new_check_types_ok() {
for value in [
Value::Boolean(true),
Value::Double(3.4),
Value::Integer(5),
Value::Text("a".to_owned()),
] {
let mut syms = Symbols::default();
syms.set_var(&VarRef::new("a", None), value.clone()).unwrap();
check_var(&syms, "a", value.clone());
syms.set_var(&VarRef::new("v", Some(value.as_exprtype())), value.clone()).unwrap();
check_var(&syms, "v", value);
}
}
#[test]
fn test_symbols_apply_new() {
let mut syms = Symbols::default();
syms.assign(&SymbolKey::from("a"), Value::Integer(5));
check_var(&syms, "a", Value::Integer(5));
}
#[test]
fn test_symbols_apply_existing() {
let mut syms = SymbolsBuilder::default().add_var("A", Value::Double(1.0)).build();
syms.assign(&SymbolKey::from("a"), Value::Double(2.0));
check_var(&syms, "a", Value::Double(2.0));
}
#[test]
fn test_symbols_get_var_apply_case_insensitivity() {
let mut syms = Symbols::default();
syms.assign(&SymbolKey::from("SomeName"), Value::Integer(6));
assert_eq!(Value::Integer(6), *syms.get_var(&VarRef::new("somename", None)).unwrap());
}
#[test]
fn test_symbols_get_var_apply_replace_value() {
let mut syms = Symbols::default();
syms.assign(&SymbolKey::from("the_var"), Value::Integer(100));
assert_eq!(Value::Integer(100), *syms.get_var(&VarRef::new("the_var", None)).unwrap());
syms.assign(&SymbolKey::from("the_var"), Value::Integer(200));
assert_eq!(Value::Integer(200), *syms.get_var(&VarRef::new("the_var", None)).unwrap());
}
#[test]
fn test_symbols_set_var_new_check_types_incompatible() {
let mut syms = Symbols::default();
assert_eq!(
"Cannot assign value of type BOOLEAN to variable of type INTEGER",
format!(
"{}",
syms.set_var(&VarRef::new("v", Some(ExprType::Integer)), Value::Boolean(false))
.unwrap_err()
)
);
}
#[test]
fn test_symbols_set_var_new_integer_to_double() {
let mut syms = Symbols::default();
syms.set_var(&VarRef::new("v", Some(ExprType::Double)), Value::Integer(3)).unwrap();
check_var(&syms, "v", Value::Double(3.0));
}
#[test]
fn test_symbols_set_var_new_double_to_integer() {
for (expected, actual) in [(4, 4.4), (5, 4.5), (5, 4.6)] {
let mut syms = Symbols::default();
syms.set_var(&VarRef::new("v", Some(ExprType::Integer)), Value::Double(actual))
.unwrap();
check_var(&syms, "v", Value::Integer(expected));
}
}
#[test]
fn test_symbols_set_var_existing_check_types_ok() {
for value in [
Value::Boolean(true),
Value::Double(3.4),
Value::Integer(5),
Value::Text("a".to_owned()),
] {
let mut syms = SymbolsBuilder::default()
.add_var("A", value.clone())
.add_var("V", value.clone())
.build();
syms.set_var(&VarRef::new("a", None), value.clone()).unwrap();
check_var(&syms, "a", value.clone());
syms.set_var(&VarRef::new("v", Some(value.as_exprtype())), value.clone()).unwrap();
check_var(&syms, "v", value);
}
}
#[test]
fn test_symbols_set_var_existing_check_types_incompatible() {
let mut syms = SymbolsBuilder::default().add_var("V", Value::Double(10.0)).build();
assert_eq!(
"Cannot assign value of type BOOLEAN to variable of type DOUBLE",
format!("{}", syms.set_var(&VarRef::new("v", None), Value::Boolean(true)).unwrap_err())
);
}
#[test]
fn test_symbols_set_existing_integer_to_double() {
let mut syms = SymbolsBuilder::default()
.add_var("A", Value::Double(1.0))
.add_var("V", Value::Double(1.0))
.build();
syms.set_var(&VarRef::new("a", None), Value::Integer(3)).unwrap();
syms.set_var(&VarRef::new("v", Some(ExprType::Double)), Value::Integer(3)).unwrap();
check_var(&syms, "a", Value::Double(3.0));
check_var(&syms, "v", Value::Double(3.0));
}
#[test]
fn test_symbols_set_existing_double_to_integer() {
for (expected, actual) in [(4, 4.4), (5, 4.5), (5, 4.6)] {
let mut syms = SymbolsBuilder::default()
.add_var("A", Value::Integer(1))
.add_var("V", Value::Integer(1))
.build();
syms.set_var(&VarRef::new("a", None), Value::Double(actual)).unwrap();
syms.set_var(&VarRef::new("v", Some(ExprType::Integer)), Value::Double(actual))
.unwrap();
check_var(&syms, "a", Value::Integer(expected));
check_var(&syms, "v", Value::Integer(expected));
}
}
#[test]
fn test_symbols_set_var_name_overlap() {
let mut syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", ExprType::Integer)
.add_callable(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.add_callable(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert_eq!(
"Cannot redefine Out as a variable",
format!("{}", syms.set_var(&VarRef::new("Out", None), Value::Integer(1)).unwrap_err())
);
assert_eq!(
"Cannot redefine Sum% as a variable",
format!(
"{}",
syms.set_var(&VarRef::new("Sum", Some(ExprType::Integer)), Value::Integer(1))
.unwrap_err()
)
);
assert_eq!(
"Cannot redefine SomeArray% as a variable",
format!(
"{}",
syms.set_var(&VarRef::new("SomeArray", Some(ExprType::Integer)), Value::Integer(1))
.unwrap_err()
)
);
assert_eq!(
"Incompatible types in SomeArray$ reference",
format!(
"{}",
syms.set_var(&VarRef::new("SomeArray", Some(ExprType::Text)), Value::Integer(1))
.unwrap_err()
)
);
}
#[test]
fn test_symbols_set_var_global() {
let mut syms = Symbols::default();
syms.dim_shared(SymbolKey::from("global"), ExprType::Integer);
syms.set_var(&VarRef::new("global", None), Value::Integer(5)).unwrap();
check_var(&syms, "global", Value::Integer(5));
syms.enter_scope();
syms.set_var(&VarRef::new("global", None), Value::Integer(7)).unwrap();
check_var(&syms, "global", Value::Integer(7));
syms.leave_scope();
check_var(&syms, "global", Value::Integer(7));
}
#[test]
fn test_symbols_get_var_set_var_case_insensitivity() {
let mut syms = Symbols::default();
syms.set_var(&VarRef::new("SomeName", None), Value::Integer(6)).unwrap();
assert_eq!(Value::Integer(6), *syms.get_var(&VarRef::new("somename", None)).unwrap());
}
#[test]
fn test_symbols_get_var_set_var_replace_value() {
let mut syms = Symbols::default();
syms.set_var(&VarRef::new("the_var", None), Value::Integer(100)).unwrap();
assert_eq!(Value::Integer(100), *syms.get_var(&VarRef::new("the_var", None)).unwrap());
syms.set_var(&VarRef::new("the_var", None), Value::Integer(200)).unwrap();
assert_eq!(Value::Integer(200), *syms.get_var(&VarRef::new("the_var", None)).unwrap());
}
#[test]
fn test_symbols_get_var_undefined_error() {
let syms = SymbolsBuilder::default().add_var("SOMETHING", Value::Integer(3)).build();
assert_eq!(
"Undefined variable SOME_THIN",
format!(
"{}",
syms.get_var(&VarRef::new("SOME_THIN", Some(ExprType::Integer))).unwrap_err()
)
);
}
#[test]
fn test_symbols_unset_ok() {
let mut syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", ExprType::Integer)
.add_callable(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.add_callable(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
let mut count = 2;
for name in ["SomeArray", "SomeVar"] {
syms.unset(&SymbolKey::from(name)).unwrap();
count -= 1;
assert_eq!(count, syms.locals().len());
}
assert_eq!(0, count);
syms.unset(&SymbolKey::from("Out")).unwrap_err();
syms.unset(&SymbolKey::from("Sum")).unwrap_err();
assert_eq!(2, syms.callables().len());
}
#[test]
fn test_symbols_unset_undefined() {
let mut syms = SymbolsBuilder::default().add_var("SOMETHING", Value::Integer(3)).build();
syms.unset(&SymbolKey::from("FOO")).unwrap_err();
assert_eq!(1, syms.locals().len());
}
}