use crate::ast::{ArgSep, Expr, Value, VarRef, VarType};
use crate::eval::{Error, Result};
use crate::exec::Machine;
use async_trait::async_trait;
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(String),
EvalError(Error),
InternalError(String),
IoError(io::Error),
SyntaxError,
}
impl From<Error> for CallError {
fn from(e: Error) -> Self {
Self::EvalError(e)
}
}
impl From<io::Error> for CallError {
fn from(e: io::Error) -> Self {
Self::IoError(e)
}
}
pub type CommandResult = std::result::Result<(), CallError>;
pub type FunctionResult = std::result::Result<Value, CallError>;
#[derive(Clone, Debug, PartialEq)]
pub struct Array {
subtype: VarType,
dimensions: Vec<usize>,
values: Vec<Value>,
}
impl Array {
pub fn new(subtype: VarType, 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) -> VarType {
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> {
if subscripts.len() != dimensions.len() {
return Err(Error::new(format!(
"Cannot index array with {} subscripts; need {}",
subscripts.len(),
dimensions.len()
)));
}
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<()> {
if value.as_vartype() != self.subtype {
return Err(Error::new(format!(
"Cannot assign value of type {:?} to array of type {:?}",
value.as_vartype(),
self.subtype
)));
}
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_vartype() == self.subtype);
Ok(value)
}
}
pub enum Symbol {
Array(Array),
Command(Rc<dyn Command>),
Function(Rc<dyn Function>),
Variable(Value),
}
impl Symbol {
fn eval_type(&self) -> VarType {
match self {
Symbol::Array(array) => array.subtype(),
Symbol::Command(command) => command.metadata().return_type(),
Symbol::Function(function) => function.metadata().return_type(),
Symbol::Variable(value) => value.as_vartype(),
}
}
pub fn metadata(&self) -> Option<&CallableMetadata> {
match self {
Symbol::Array(_) => None,
Symbol::Command(command) => Some(command.metadata()),
Symbol::Function(function) => Some(function.metadata()),
Symbol::Variable(_) => None,
}
}
fn user_defined(&self) -> bool {
match self {
Symbol::Array(_) => true,
Symbol::Command(_) => false,
Symbol::Function(_) => 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::Command(command) => write!(f, "Command({:?})", command.metadata()),
Symbol::Function(function) => write!(f, "Function({:?})", function.metadata()),
Symbol::Variable(value) => write!(f, "Variable({:?})", value),
}
}
}
#[derive(Default)]
pub struct Symbols {
by_name: HashMap<String, Symbol>,
}
impl Symbols {
#[cfg(test)]
pub(crate) fn from(by_name: HashMap<String, Symbol>) -> Self {
Self { by_name }
}
pub fn add_command(&mut self, command: Rc<dyn Command>) {
let key = command.metadata().name();
debug_assert!(key == key.to_ascii_uppercase());
assert!(!self.by_name.contains_key(key));
self.by_name.insert(key.to_owned(), Symbol::Command(command));
}
pub fn add_function(&mut self, function: Rc<dyn Function>) {
let key = function.metadata().name();
debug_assert!(key == key.to_ascii_uppercase());
assert!(!self.by_name.contains_key(key));
self.by_name.insert(key.to_owned(), Symbol::Function(function));
}
pub fn as_hashmap(&self) -> &HashMap<String, Symbol> {
&self.by_name
}
pub fn clear(&mut self) {
self.by_name.retain(|_, symbol| !symbol.user_defined());
}
pub fn dim(&mut self, name: &str, vartype: VarType) -> Result<()> {
let key = name.to_ascii_uppercase();
if self.by_name.contains_key(&key) {
return Err(Error::new(format!("Cannot DIM already-defined symbol {}", name)));
}
self.by_name.insert(key, Symbol::Variable(vartype.default_value()));
Ok(())
}
pub fn dim_array(
&mut self,
name: &str,
subtype: VarType,
dimensions: Vec<usize>,
) -> Result<()> {
let key = name.to_ascii_uppercase();
if self.by_name.contains_key(&key) {
return Err(Error::new(format!("Cannot DIM already-defined symbol {}", name)));
}
self.by_name.insert(key, Symbol::Array(Array::new(subtype, dimensions)));
Ok(())
}
pub fn get(&self, vref: &VarRef) -> Result<Option<&Symbol>> {
let key = &vref.name().to_ascii_uppercase();
let symbol = self.by_name.get(key);
if let Some(symbol) = symbol {
let stype = symbol.eval_type();
if !vref.accepts(stype) {
return Err(Error::new(format!("Incompatible types in {} reference", vref)));
}
}
Ok(symbol)
}
pub fn get_mut(&mut self, vref: &VarRef) -> Result<Option<&mut Symbol>> {
let key = &vref.name().to_ascii_uppercase();
match self.by_name.get_mut(key) {
Some(symbol) => {
let stype = symbol.eval_type();
if !vref.accepts(stype) {
return Err(Error::new(format!("Incompatible types in {} reference", vref)));
}
Ok(Some(symbol))
}
None => Ok(None),
}
}
pub 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 fn qualify_varref(&self, vref: &VarRef) -> Result<VarRef> {
let key = vref.name().to_ascii_uppercase();
match self.by_name.get(&key) {
Some(symbol) => match vref.ref_type() {
VarType::Auto => Ok(vref.clone().qualify(symbol.eval_type())),
_ => {
if !vref.accepts(symbol.eval_type()) {
return Err(Error::new(format!(
"Incompatible types in {} reference",
vref
)));
}
Ok(vref.clone())
}
},
None => Ok(vref.clone()),
}
}
pub fn set_var(&mut self, vref: &VarRef, value: Value) -> Result<()> {
match self.get_mut(vref)? {
Some(Symbol::Variable(old_value)) => {
if mem::discriminant(&value) != mem::discriminant(old_value) {
return Err(Error::new(format!("Incompatible types in {} assignment", vref)));
}
*old_value = value;
Ok(())
}
Some(_) => Err(Error::new(format!("Cannot redefine {} as a variable", vref))),
None => {
if !vref.accepts(value.as_vartype()) {
return Err(Error::new(format!("Incompatible types in {} assignment", vref)));
}
self.by_name.insert(vref.name().to_ascii_uppercase(), Symbol::Variable(value));
Ok(())
}
}
}
}
pub struct CallableMetadataBuilder {
name: &'static str,
return_type: VarType,
category: Option<&'static str>,
syntax: Option<&'static str>,
description: Option<&'static str>,
}
impl CallableMetadataBuilder {
pub fn new(name: &'static str, return_type: VarType) -> Self {
assert!(name == name.to_ascii_uppercase(), "Callable name must be in uppercase");
Self { name, return_type, syntax: None, category: None, description: None }
}
pub fn with_syntax(mut self, syntax: &'static str) -> Self {
self.syntax = Some(syntax);
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 {
CallableMetadata {
name: self.name,
return_type: self.return_type,
syntax: self.syntax.expect("All callables must specify a syntax"),
category: self.category.expect("All callables must specify a category"),
description: self.description.expect("All callables must specify a description"),
}
}
pub fn test_build(self) -> CallableMetadata {
CallableMetadata {
name: self.name,
return_type: self.return_type,
syntax: self.syntax.unwrap_or(""),
category: self.category.unwrap_or(""),
description: self.description.unwrap_or(""),
}
}
}
#[derive(Debug)]
pub struct CallableMetadata {
name: &'static str,
return_type: VarType,
syntax: &'static str,
category: &'static str,
description: &'static str,
}
impl CallableMetadata {
pub fn name(&self) -> &'static str {
self.name
}
pub fn return_type(&self) -> VarType {
self.return_type
}
pub fn syntax(&self) -> &'static str {
self.syntax
}
pub fn category(&self) -> &'static str {
self.category
}
pub fn description(&self) -> Lines<'static> {
self.description.lines()
}
}
pub trait Function {
fn metadata(&self) -> &CallableMetadata;
fn exec(&self, args: Vec<Value>, symbols: &mut Symbols) -> FunctionResult;
}
#[async_trait(?Send)]
pub trait Command {
fn metadata(&self) -> &CallableMetadata;
async fn exec(&self, args: &[(Option<Expr>, ArgSep)], machine: &mut Machine) -> CommandResult;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::VarRef;
use crate::testutils::*;
#[test]
fn test_array_unidimensional_ok() {
let mut array = Array::new(VarType::Integer, vec![5]);
assert_eq!(VarType::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(VarType::Integer, vec![5]);
assert_eq!(
"Cannot index array with 0 subscripts; need 1",
format!("{}", array.assign(&[], Value::Integer(1)).unwrap_err())
);
assert_eq!(
"Cannot index array with 0 subscripts; need 1",
format!("{}", array.index(&[]).unwrap_err())
);
assert_eq!(
"Cannot index array with 2 subscripts; need 1",
format!("{}", array.assign(&[1, 2], Value::Integer(1)).unwrap_err())
);
assert_eq!(
"Cannot index array with 2 subscripts; need 1",
format!("{}", array.index(&[1, 2]).unwrap_err())
);
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()));
assert_eq!(
"Cannot assign value of type Text to array of type Integer",
format!("{}", array.assign(&[0], Value::Text("a".to_owned())).unwrap_err())
);
}
#[test]
fn test_array_bidimensional_ok() {
let mut array = Array::new(VarType::Double, vec![2, 3]);
assert_eq!(VarType::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(VarType::Integer, vec![5, 2]);
assert_eq!(
"Cannot index array with 0 subscripts; need 2",
format!("{}", array.assign(&[], Value::Integer(1)).unwrap_err())
);
assert_eq!(
"Cannot index array with 0 subscripts; need 2",
format!("{}", array.index(&[]).unwrap_err())
);
assert_eq!(
"Cannot index array with 1 subscripts; need 2",
format!("{}", array.assign(&[1], Value::Integer(1)).unwrap_err())
);
assert_eq!(
"Cannot index array with 1 subscripts; need 2",
format!("{}", array.index(&[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 -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())
);
assert_eq!(
"Cannot assign value of type Text to array of type Integer",
format!("{}", array.assign(&[0, 0], Value::Text("a".to_owned())).unwrap_err())
);
}
#[test]
fn test_array_multidimensional() {
let mut array = Array::new(VarType::Integer, vec![2, 4, 3, 5]);
assert_eq!(VarType::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", VarType::Integer)
.add_command(ExitCommand::new())
.add_function(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert!(syms.get(&VarRef::new("SOMEARRAY", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("EXIT", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SUM", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SOMEVAR", VarType::Auto)).unwrap().is_some());
syms.clear();
assert!(!syms.get(&VarRef::new("SOMEARRAY", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("EXIT", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SUM", VarType::Auto)).unwrap().is_some());
assert!(!syms.get(&VarRef::new("SOMEVAR", VarType::Auto)).unwrap().is_some());
}
#[test]
fn test_symbols_dim_ok() {
let mut syms = Symbols::default();
syms.dim("a_boolean", VarType::Boolean).unwrap();
match syms.get(&VarRef::new("a_boolean", VarType::Auto)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Boolean(false), value),
_ => panic!("Got something that is not the variable we asked for"),
}
syms.dim("a_double", VarType::Double).unwrap();
match syms.get(&VarRef::new("a_double", VarType::Auto)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Double(0.0), value),
_ => panic!("Got something that is not the variable we asked for"),
}
syms.dim("an_integer", VarType::Integer).unwrap();
match syms.get(&VarRef::new("an_integer", VarType::Auto)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Integer(0), value),
_ => panic!("Got something that is not the variable we asked for"),
}
syms.dim("a_string", VarType::Text).unwrap();
match syms.get(&VarRef::new("a_string", VarType::Auto)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Text("".to_owned()), value),
_ => panic!("Got something that is not the variable we asked for"),
}
}
#[test]
fn test_symbols_dim_name_overlap() {
let mut syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", VarType::Integer)
.add_command(ExitCommand::new())
.add_function(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert_eq!(
"Cannot DIM already-defined symbol Exit",
format!("{}", syms.dim("Exit", VarType::Integer).unwrap_err())
);
assert_eq!(
"Cannot DIM already-defined symbol Sum",
format!("{}", syms.dim("Sum", VarType::Integer).unwrap_err())
);
assert_eq!(
"Cannot DIM already-defined symbol SomeVar",
format!("{}", syms.dim("SomeVar", VarType::Integer).unwrap_err())
);
assert_eq!(
"Cannot DIM already-defined symbol SomeArray",
format!("{}", syms.dim("SomeArray", VarType::Integer).unwrap_err())
);
}
fn assert_same_array_shape(exp_subtype: VarType, 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("a_boolean", VarType::Boolean, vec![1]).unwrap();
assert_same_array_shape(
VarType::Boolean,
&[1],
syms.get(&VarRef::new("a_boolean", VarType::Auto)).unwrap().unwrap(),
);
syms.dim_array("a_double", VarType::Double, vec![5, 10]).unwrap();
assert_same_array_shape(
VarType::Double,
&[5, 10],
syms.get(&VarRef::new("a_double", VarType::Auto)).unwrap().unwrap(),
);
syms.dim_array("an_integer", VarType::Integer, vec![100]).unwrap();
assert_same_array_shape(
VarType::Integer,
&[100],
syms.get(&VarRef::new("an_integer", VarType::Auto)).unwrap().unwrap(),
);
syms.dim_array("a_string", VarType::Text, vec![1, 1]).unwrap();
assert_same_array_shape(
VarType::Text,
&[1, 1],
syms.get(&VarRef::new("a_string", VarType::Auto)).unwrap().unwrap(),
);
}
#[test]
fn test_symbols_dim_array_name_overlap() {
let mut syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", VarType::Integer)
.add_command(ExitCommand::new())
.add_function(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert_eq!(
"Cannot DIM already-defined symbol Exit",
format!("{}", syms.dim_array("Exit", VarType::Integer, vec![5]).unwrap_err())
);
assert_eq!(
"Cannot DIM already-defined symbol Sum",
format!("{}", syms.dim_array("Sum", VarType::Integer, vec![5]).unwrap_err())
);
assert_eq!(
"Cannot DIM already-defined symbol SomeVar",
format!("{}", syms.dim_array("SomeVar", VarType::Integer, vec![5]).unwrap_err())
);
assert_eq!(
"Cannot DIM already-defined symbol SomeArray",
format!("{}", syms.dim_array("SomeArray", VarType::Integer, vec![5]).unwrap_err())
);
}
#[test]
fn test_symbols_get_check_types() {
let syms = SymbolsBuilder::default()
.add_array("BOOL_ARRAY", VarType::Boolean)
.add_command(ExitCommand::new())
.add_function(SumFunction::new())
.add_var("STRING_VAR", Value::Text("".to_owned()))
.build();
for ref_type in &[VarType::Auto, VarType::Boolean] {
match syms.get(&VarRef::new("bool_array", *ref_type)).unwrap().unwrap() {
Symbol::Array(array) => assert_eq!(VarType::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", VarType::Text)).unwrap_err())
);
match syms.get(&VarRef::new("exit", VarType::Auto)).unwrap().unwrap() {
Symbol::Command(c) => assert_eq!(VarType::Void, c.metadata().return_type()),
_ => panic!("Got something that is not the command we asked for"),
}
assert_eq!(
"Incompatible types in exit# reference",
format!("{}", syms.get(&VarRef::new("exit", VarType::Double)).unwrap_err())
);
for ref_type in &[VarType::Auto, VarType::Integer] {
match syms.get(&VarRef::new("sum", *ref_type)).unwrap().unwrap() {
Symbol::Function(f) => assert_eq!(VarType::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", VarType::Double)).unwrap_err())
);
for ref_type in &[VarType::Auto, VarType::Text] {
match syms.get(&VarRef::new("string_var", *ref_type)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(VarType::Text, value.as_vartype()),
_ => 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", VarType::Integer)).unwrap_err())
);
}
#[test]
fn test_symbols_get_case_insensitivity() {
let syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", VarType::Integer)
.add_command(ExitCommand::new())
.add_function(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert!(syms.get(&VarRef::new("somearray", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SomeArray", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("exit", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("Exit", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("sum", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("Sum", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("somevar", VarType::Auto)).unwrap().is_some());
assert!(syms.get(&VarRef::new("SomeVar", VarType::Auto)).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", VarType::Integer)).unwrap().is_none());
}
#[test]
fn test_symbols_get_mut_check_types() {
let mut syms = SymbolsBuilder::default()
.add_array("BOOL_ARRAY", VarType::Boolean)
.add_command(ExitCommand::new())
.add_function(SumFunction::new())
.add_var("STRING_VAR", Value::Text("".to_owned()))
.build();
for ref_type in &[VarType::Auto, VarType::Boolean] {
match syms.get_mut(&VarRef::new("bool_array", *ref_type)).unwrap().unwrap() {
Symbol::Array(array) => assert_eq!(VarType::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", VarType::Text)).unwrap_err())
);
match syms.get_mut(&VarRef::new("exit", VarType::Auto)).unwrap().unwrap() {
Symbol::Command(c) => assert_eq!(VarType::Void, c.metadata().return_type()),
_ => panic!("Got something that is not the command we asked for"),
}
assert_eq!(
"Incompatible types in exit# reference",
format!("{}", syms.get_mut(&VarRef::new("exit", VarType::Double)).unwrap_err())
);
for ref_type in &[VarType::Auto, VarType::Integer] {
match syms.get_mut(&VarRef::new("sum", *ref_type)).unwrap().unwrap() {
Symbol::Function(f) => assert_eq!(VarType::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", VarType::Double)).unwrap_err())
);
for ref_type in &[VarType::Auto, VarType::Text] {
match syms.get_mut(&VarRef::new("string_var", *ref_type)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(VarType::Text, value.as_vartype()),
_ => 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", VarType::Integer)).unwrap_err())
);
}
#[test]
fn test_symbols_get_mut_case_insensitivity() {
let mut syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", VarType::Integer)
.add_command(ExitCommand::new())
.add_function(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert!(syms.get_mut(&VarRef::new("somearray", VarType::Auto)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("SomeArray", VarType::Auto)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("exit", VarType::Auto)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("Exit", VarType::Auto)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("sum", VarType::Auto)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("Sum", VarType::Auto)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("somevar", VarType::Auto)).unwrap().is_some());
assert!(syms.get_mut(&VarRef::new("SomeVar", VarType::Auto)).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", VarType::Integer)).unwrap().is_none());
}
#[test]
fn test_symbols_qualify_varref() {
let syms = SymbolsBuilder::default().add_array("V", VarType::Boolean).build();
assert_eq!(
VarRef::new("v", VarType::Boolean),
syms.qualify_varref(&VarRef::new("v", VarType::Auto)).unwrap(),
);
assert_eq!(
VarRef::new("v", VarType::Boolean),
syms.qualify_varref(&VarRef::new("v", VarType::Boolean)).unwrap(),
);
assert_eq!(
"Incompatible types in v% reference",
format!("{}", syms.qualify_varref(&VarRef::new("v", VarType::Integer)).unwrap_err()),
);
assert_eq!(
VarRef::new("undefined", VarType::Auto),
syms.qualify_varref(&VarRef::new("undefined", VarType::Auto)).unwrap(),
);
assert_eq!(
VarRef::new("undefined", VarType::Text),
syms.qualify_varref(&VarRef::new("undefined", VarType::Text)).unwrap(),
);
}
#[test]
fn test_symbols_set_var_new_check_types() {
for ref_type in &[VarType::Auto, VarType::Text] {
let mut syms = Symbols::default();
syms.set_var(&VarRef::new("v", *ref_type), Value::Text("a".to_owned())).unwrap();
match syms.get(&VarRef::new("v", VarType::Auto)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Text("a".to_owned()), value),
_ => panic!("Got something that is not the variable we asked for"),
}
}
let mut syms = Symbols::default();
assert_eq!(
"Incompatible types in v% assignment",
format!(
"{}",
syms.set_var(&VarRef::new("v", VarType::Integer), Value::Double(3.0)).unwrap_err()
)
);
}
#[test]
fn test_symbols_set_var_existing_check_types() {
for ref_type in &[VarType::Auto, VarType::Integer] {
let mut syms = SymbolsBuilder::default().add_var("V", Value::Integer(10)).build();
syms.set_var(&VarRef::new("v", *ref_type), Value::Integer(3)).unwrap();
match syms.get(&VarRef::new("v", VarType::Auto)).unwrap().unwrap() {
Symbol::Variable(value) => assert_eq!(&Value::Integer(3), value),
_ => panic!("Got something that is not the variable we asked for"),
}
}
let mut syms = SymbolsBuilder::default().add_var("V", Value::Integer(10)).build();
assert_eq!(
"Incompatible types in v assignment",
format!(
"{}",
syms.set_var(&VarRef::new("v", VarType::Auto), Value::Double(3.0)).unwrap_err()
)
);
}
#[test]
fn test_symbols_set_var_name_overlap() {
let mut syms = SymbolsBuilder::default()
.add_array("SOMEARRAY", VarType::Integer)
.add_command(ExitCommand::new())
.add_function(SumFunction::new())
.add_var("SOMEVAR", Value::Boolean(true))
.build();
assert_eq!(
"Cannot redefine Exit as a variable",
format!(
"{}",
syms.set_var(&VarRef::new("Exit", VarType::Auto), Value::Integer(1)).unwrap_err()
)
);
assert_eq!(
"Cannot redefine Sum% as a variable",
format!(
"{}",
syms.set_var(&VarRef::new("Sum", VarType::Integer), Value::Integer(1)).unwrap_err()
)
);
assert_eq!(
"Cannot redefine SomeArray% as a variable",
format!(
"{}",
syms.set_var(&VarRef::new("SomeArray", VarType::Integer), Value::Integer(1))
.unwrap_err()
)
);
assert_eq!(
"Incompatible types in SomeArray$ reference",
format!(
"{}",
syms.set_var(&VarRef::new("SomeArray", VarType::Text), Value::Integer(1))
.unwrap_err()
)
);
}
#[test]
fn test_symbols_get_var_set_var_case_insensitivity() {
let mut syms = Symbols::default();
syms.set_var(&VarRef::new("SomeName", VarType::Auto), Value::Integer(6)).unwrap();
assert_eq!(
Value::Integer(6),
*syms.get_var(&VarRef::new("somename", VarType::Auto)).unwrap()
);
}
#[test]
fn test_symbols_get_var_set_var_replace_value() {
let mut syms = Symbols::default();
syms.set_var(&VarRef::new("the_var", VarType::Auto), Value::Integer(100)).unwrap();
assert_eq!(
Value::Integer(100),
*syms.get_var(&VarRef::new("the_var", VarType::Auto)).unwrap()
);
syms.set_var(&VarRef::new("the_var", VarType::Auto), Value::Integer(200)).unwrap();
assert_eq!(
Value::Integer(200),
*syms.get_var(&VarRef::new("the_var", VarType::Auto)).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", VarType::Integer)).unwrap_err())
);
}
}