use crate::parser::{Error, Result};
use std::fmt;
#[derive(Clone, Debug, PartialEq)]
pub enum Expr {
Boolean(bool),
Double(f64),
Integer(i32),
Symbol(VarRef),
Text(String),
Add(Box<Expr>, Box<Expr>),
Subtract(Box<Expr>, Box<Expr>),
Multiply(Box<Expr>, Box<Expr>),
Divide(Box<Expr>, Box<Expr>),
Modulo(Box<Expr>, Box<Expr>),
Negate(Box<Expr>),
Equal(Box<Expr>, Box<Expr>),
NotEqual(Box<Expr>, Box<Expr>),
Less(Box<Expr>, Box<Expr>),
LessEqual(Box<Expr>, Box<Expr>),
Greater(Box<Expr>, Box<Expr>),
GreaterEqual(Box<Expr>, Box<Expr>),
And(Box<Expr>, Box<Expr>),
Not(Box<Expr>),
Or(Box<Expr>, Box<Expr>),
Xor(Box<Expr>, Box<Expr>),
Call(VarRef, Vec<Expr>),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VarType {
Auto,
Boolean,
Double,
Integer,
Text,
Void,
}
impl VarType {
pub fn annotation(&self) -> &'static str {
match self {
VarType::Auto => "",
VarType::Boolean => "?",
VarType::Double => "#",
VarType::Integer => "%",
VarType::Text => "$",
VarType::Void => "",
}
}
pub fn default_value(&self) -> Value {
match self {
VarType::Auto => Value::Integer(0),
VarType::Boolean => Value::Boolean(false),
VarType::Double => Value::Double(0.0),
VarType::Integer => Value::Integer(0),
VarType::Text => Value::Text("".to_owned()),
VarType::Void => panic!("Cannot represent a default value for void"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VarRef {
name: String,
ref_type: VarType,
}
impl VarRef {
#[allow(clippy::redundant_field_names)]
pub fn new<T: Into<String>>(name: T, ref_type: VarType) -> Self {
Self { name: name.into(), ref_type: ref_type }
}
pub fn into_unannotated_string(self) -> Result<String> {
if self.ref_type != VarType::Auto {
return Err(Error::Bad(format!("Type annotation not allowed in {}", self)));
}
Ok(self.name)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn qualify(self, ref_type: VarType) -> Self {
assert!(ref_type != VarType::Auto, "Cannot qualify with auto");
assert!(self.ref_type == VarType::Auto, "Reference already qualified");
Self { name: self.name, ref_type }
}
pub fn ref_type(&self) -> VarType {
self.ref_type
}
pub fn accepts(&self, other: VarType) -> bool {
self.ref_type == VarType::Auto || self.ref_type == other
}
}
impl fmt::Display for VarRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.name, self.ref_type().annotation())
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Boolean(bool),
Double(f64),
Integer(i32),
Text(String), }
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Boolean(b)
}
}
impl From<f64> for Value {
fn from(d: f64) -> Self {
Value::Double(d)
}
}
impl From<i32> for Value {
fn from(i: i32) -> Self {
Value::Integer(i)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::Text(s.to_owned())
}
}
impl Value {
pub fn as_vartype(&self) -> VarType {
match self {
Value::Boolean(_) => VarType::Boolean,
Value::Double(_) => VarType::Double,
Value::Integer(_) => VarType::Integer,
Value::Text(_) => VarType::Text,
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum ArgSep {
End,
Short,
Long,
}
#[derive(Debug, PartialEq)]
pub enum Statement {
ArrayAssignment(VarRef, Vec<Expr>, Expr),
Assignment(VarRef, Expr),
BuiltinCall(String, Vec<(Option<Expr>, ArgSep)>),
Dim(String, VarType),
DimArray(String, Vec<Expr>, VarType),
If(Vec<(Expr, Vec<Statement>)>),
For(VarRef, Expr, Expr, Expr, Vec<Statement>),
While(Expr, Vec<Statement>),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_varref_display() {
assert_eq!("name", format!("{}", VarRef::new("name", VarType::Auto)));
assert_eq!("abc?", format!("{}", VarRef::new("abc", VarType::Boolean)));
assert_eq!("cba#", format!("{}", VarRef::new("cba", VarType::Double)));
assert_eq!("def%", format!("{}", VarRef::new("def", VarType::Integer)));
assert_eq!("ghi$", format!("{}", VarRef::new("ghi", VarType::Text)));
}
#[test]
fn test_varref_into_unannotated_string() {
assert_eq!(
"print",
&VarRef::new("print", VarType::Auto).into_unannotated_string().unwrap()
);
assert_eq!(
"Type annotation not allowed in print$",
format!(
"{}",
&VarRef::new("print", VarType::Text).into_unannotated_string().unwrap_err()
)
);
}
#[test]
fn test_varref_accepts() {
assert!(VarRef::new("a", VarType::Auto).accepts(VarType::Boolean));
assert!(VarRef::new("a", VarType::Auto).accepts(VarType::Double));
assert!(VarRef::new("a", VarType::Auto).accepts(VarType::Integer));
assert!(VarRef::new("a", VarType::Auto).accepts(VarType::Text));
assert!(VarRef::new("a", VarType::Boolean).accepts(VarType::Boolean));
assert!(!VarRef::new("a", VarType::Boolean).accepts(VarType::Double));
assert!(!VarRef::new("a", VarType::Boolean).accepts(VarType::Integer));
assert!(!VarRef::new("a", VarType::Boolean).accepts(VarType::Text));
assert!(!VarRef::new("a", VarType::Double).accepts(VarType::Boolean));
assert!(VarRef::new("a", VarType::Double).accepts(VarType::Double));
assert!(!VarRef::new("a", VarType::Double).accepts(VarType::Integer));
assert!(!VarRef::new("a", VarType::Double).accepts(VarType::Text));
assert!(!VarRef::new("a", VarType::Integer).accepts(VarType::Boolean));
assert!(!VarRef::new("a", VarType::Integer).accepts(VarType::Double));
assert!(VarRef::new("a", VarType::Integer).accepts(VarType::Integer));
assert!(!VarRef::new("a", VarType::Integer).accepts(VarType::Text));
assert!(!VarRef::new("a", VarType::Text).accepts(VarType::Boolean));
assert!(!VarRef::new("a", VarType::Text).accepts(VarType::Double));
assert!(!VarRef::new("a", VarType::Text).accepts(VarType::Integer));
assert!(VarRef::new("a", VarType::Text).accepts(VarType::Text));
}
}