use crate::{reader::LineCol, syms::SymbolKey};
use std::fmt;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BooleanSpan {
pub value: bool,
pub pos: LineCol,
}
#[derive(Clone, Debug, PartialEq)]
pub struct DoubleSpan {
pub value: f64,
pub pos: LineCol,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IntegerSpan {
pub value: i32,
pub pos: LineCol,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TextSpan {
pub value: String,
pub pos: LineCol,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SymbolSpan {
pub vref: VarRef,
pub pos: LineCol,
}
#[derive(Clone, Debug, PartialEq)]
pub struct UnaryOpSpan {
pub expr: Expr,
pub pos: LineCol,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BinaryOpSpan {
pub lhs: Expr,
pub rhs: Expr,
pub pos: LineCol,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Expr {
Boolean(BooleanSpan),
Double(DoubleSpan),
Integer(IntegerSpan),
Text(TextSpan),
Symbol(SymbolSpan),
Add(Box<BinaryOpSpan>),
Subtract(Box<BinaryOpSpan>),
Multiply(Box<BinaryOpSpan>),
Divide(Box<BinaryOpSpan>),
Modulo(Box<BinaryOpSpan>),
Power(Box<BinaryOpSpan>),
Negate(Box<UnaryOpSpan>),
Equal(Box<BinaryOpSpan>),
NotEqual(Box<BinaryOpSpan>),
Less(Box<BinaryOpSpan>),
LessEqual(Box<BinaryOpSpan>),
Greater(Box<BinaryOpSpan>),
GreaterEqual(Box<BinaryOpSpan>),
And(Box<BinaryOpSpan>),
Not(Box<UnaryOpSpan>),
Or(Box<BinaryOpSpan>),
Xor(Box<BinaryOpSpan>),
ShiftLeft(Box<BinaryOpSpan>),
ShiftRight(Box<BinaryOpSpan>),
Call(CallSpan),
}
impl Expr {
pub fn start_pos(&self) -> LineCol {
match self {
Expr::Boolean(span) => span.pos,
Expr::Double(span) => span.pos,
Expr::Integer(span) => span.pos,
Expr::Text(span) => span.pos,
Expr::Symbol(span) => span.pos,
Expr::And(span) => span.lhs.start_pos(),
Expr::Or(span) => span.lhs.start_pos(),
Expr::Xor(span) => span.lhs.start_pos(),
Expr::Not(span) => span.pos,
Expr::ShiftLeft(span) => span.lhs.start_pos(),
Expr::ShiftRight(span) => span.lhs.start_pos(),
Expr::Equal(span) => span.lhs.start_pos(),
Expr::NotEqual(span) => span.lhs.start_pos(),
Expr::Less(span) => span.lhs.start_pos(),
Expr::LessEqual(span) => span.lhs.start_pos(),
Expr::Greater(span) => span.lhs.start_pos(),
Expr::GreaterEqual(span) => span.lhs.start_pos(),
Expr::Add(span) => span.lhs.start_pos(),
Expr::Subtract(span) => span.lhs.start_pos(),
Expr::Multiply(span) => span.lhs.start_pos(),
Expr::Divide(span) => span.lhs.start_pos(),
Expr::Modulo(span) => span.lhs.start_pos(),
Expr::Power(span) => span.lhs.start_pos(),
Expr::Negate(span) => span.pos,
Expr::Call(span) => span.vref_pos,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExprType {
Boolean,
Double,
Integer,
Text,
}
impl ExprType {
pub(crate) fn is_numerical(self) -> bool {
self == Self::Double || self == Self::Integer
}
pub fn annotation(&self) -> char {
match self {
ExprType::Boolean => '?',
ExprType::Double => '#',
ExprType::Integer => '%',
ExprType::Text => '$',
}
}
pub(crate) fn default_value(&self) -> Value {
match self {
ExprType::Boolean => Value::Boolean(false),
ExprType::Double => Value::Double(0.0),
ExprType::Integer => Value::Integer(0),
ExprType::Text => Value::Text("".to_owned()),
}
}
}
impl fmt::Display for ExprType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExprType::Boolean => write!(f, "BOOLEAN"),
ExprType::Double => write!(f, "DOUBLE"),
ExprType::Integer => write!(f, "INTEGER"),
ExprType::Text => write!(f, "STRING"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VarRef {
name: String,
ref_type: Option<ExprType>,
}
impl VarRef {
pub fn new<T: Into<String>>(name: T, ref_type: Option<ExprType>) -> Self {
Self { name: name.into(), ref_type }
}
pub fn name(&self) -> &str {
&self.name
}
pub(crate) fn take_name(self) -> String {
self.name
}
pub fn ref_type(&self) -> Option<ExprType> {
self.ref_type
}
pub fn accepts(&self, other: ExprType) -> bool {
match self.ref_type {
None => true,
Some(vtype) => vtype == other,
}
}
pub fn accepts_callable(&self, other: Option<ExprType>) -> bool {
match self.ref_type {
None => true,
Some(vtype) => match other {
Some(other) => vtype == other,
None => false,
},
}
}
pub(crate) fn as_symbol_key(&self) -> SymbolKey {
SymbolKey::from(&self.name)
}
}
impl fmt::Display for VarRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.ref_type {
None => self.name.fmt(f),
Some(vtype) => write!(f, "{}{}", self.name, vtype.annotation()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Boolean(bool),
Double(f64),
Integer(i32),
Text(String),
VarRef(SymbolKey, ExprType),
}
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 fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Boolean(true) => write!(f, "TRUE"),
Value::Boolean(false) => write!(f, "FALSE"),
Value::Double(d) => {
let mut s = format!("{}", d);
if d.is_finite() && !d.is_nan() && !s.contains('.') {
s += ".0";
}
write!(f, "{}", s)
}
Value::Integer(i) => write!(f, "{}", i),
Value::Text(s) => write!(f, "\"{}\"", s),
Value::VarRef(key, etype) => write!(f, "{}{}", key, etype),
}
}
}
impl Value {
pub fn as_exprtype(&self) -> ExprType {
match self {
Value::Boolean(_) => ExprType::Boolean,
Value::Double(_) => ExprType::Double,
Value::Integer(_) => ExprType::Integer,
Value::Text(_) => ExprType::Text,
Value::VarRef(_key, etype) => *etype,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ArgSep {
End = 0,
Short = 1,
Long = 2,
As = 3,
}
impl fmt::Display for ArgSep {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ArgSep::End => write!(f, "<END OF STATEMENT>"),
ArgSep::Short => write!(f, ";"),
ArgSep::Long => write!(f, ","),
ArgSep::As => write!(f, "AS"),
}
}
}
impl ArgSep {
pub(crate) fn describe(&self) -> (&str, bool) {
match self {
ArgSep::End => ("", false),
ArgSep::Short => (";", false),
ArgSep::Long => (",", false),
ArgSep::As => ("AS", true),
}
}
}
#[derive(Debug, PartialEq)]
#[cfg_attr(test, derive(Clone))]
pub struct ArrayAssignmentSpan {
pub vref: VarRef,
pub vref_pos: LineCol,
pub subscripts: Vec<Expr>,
pub expr: Expr,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(test, derive(Clone))]
pub struct AssignmentSpan {
pub vref: VarRef,
pub vref_pos: LineCol,
pub expr: Expr,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ArgSpan {
pub expr: Option<Expr>,
pub sep: ArgSep,
pub sep_pos: LineCol,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CallSpan {
pub vref: VarRef,
pub vref_pos: LineCol,
pub args: Vec<ArgSpan>,
}
#[derive(Debug, PartialEq)]
pub struct CallableSpan {
pub name: VarRef,
pub name_pos: LineCol,
pub params: Vec<VarRef>,
pub body: Vec<Statement>,
pub end_pos: LineCol,
}
#[derive(Debug, PartialEq)]
pub struct DataSpan {
pub values: Vec<Option<Value>>,
}
#[derive(Debug, Eq, PartialEq)]
#[cfg_attr(test, derive(Clone))]
pub struct DimSpan {
pub name: String,
pub name_pos: LineCol,
pub shared: bool,
pub vtype: ExprType,
pub vtype_pos: LineCol,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(test, derive(Clone))]
pub struct DimArraySpan {
pub name: String,
pub name_pos: LineCol,
pub shared: bool,
pub dimensions: Vec<Expr>,
pub subtype: ExprType,
pub subtype_pos: LineCol,
}
#[derive(Debug, PartialEq)]
pub enum DoGuard {
Infinite,
PreUntil(Expr),
PreWhile(Expr),
PostUntil(Expr),
PostWhile(Expr),
}
#[derive(Debug, PartialEq)]
pub struct DoSpan {
pub guard: DoGuard,
pub body: Vec<Statement>,
}
#[derive(Debug, PartialEq)]
pub struct EndSpan {
pub code: Option<Expr>,
}
#[derive(Debug, Eq, PartialEq)]
pub struct ExitDoSpan {
pub pos: LineCol,
}
#[derive(Debug, PartialEq)]
pub struct IfBranchSpan {
pub guard: Expr,
pub body: Vec<Statement>,
}
#[derive(Debug, PartialEq)]
pub struct IfSpan {
pub branches: Vec<IfBranchSpan>,
}
#[derive(Debug, PartialEq)]
pub struct ForSpan {
pub iter: VarRef,
pub iter_pos: LineCol,
pub iter_double: bool,
pub start: Expr,
pub end: Expr,
pub next: Expr,
pub body: Vec<Statement>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GotoSpan {
pub target: String,
pub target_pos: LineCol,
}
#[derive(Debug, Eq, PartialEq)]
pub struct LabelSpan {
pub name: String,
pub name_pos: LineCol,
}
#[derive(Debug, Eq, PartialEq)]
pub enum OnErrorSpan {
Goto(GotoSpan),
Reset,
ResumeNext,
}
#[derive(Debug, Eq, PartialEq)]
pub struct ReturnSpan {
pub pos: LineCol,
}
#[derive(Debug, Eq, PartialEq)]
pub enum CaseRelOp {
Equal,
NotEqual,
Less,
LessEqual,
Greater,
GreaterEqual,
}
#[derive(Debug, PartialEq)]
pub enum CaseGuardSpan {
Is(CaseRelOp, Expr),
To(Expr, Expr),
}
#[derive(Debug, PartialEq)]
pub struct CaseSpan {
pub guards: Vec<CaseGuardSpan>,
pub body: Vec<Statement>,
}
#[derive(Debug, PartialEq)]
pub struct SelectSpan {
pub expr: Expr,
pub cases: Vec<CaseSpan>,
pub end_pos: LineCol,
}
#[derive(Debug, PartialEq)]
pub struct WhileSpan {
pub expr: Expr,
pub body: Vec<Statement>,
}
#[derive(Debug, PartialEq)]
pub enum Statement {
ArrayAssignment(ArrayAssignmentSpan),
Assignment(AssignmentSpan),
Call(CallSpan),
Callable(CallableSpan),
Data(DataSpan),
Dim(DimSpan),
DimArray(DimArraySpan),
Do(DoSpan),
End(EndSpan),
ExitDo(ExitDoSpan),
For(ForSpan),
Gosub(GotoSpan),
Goto(GotoSpan),
If(IfSpan),
Label(LabelSpan),
OnError(OnErrorSpan),
Return(ReturnSpan),
Select(SelectSpan),
While(WhileSpan),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_varref_display() {
assert_eq!("name", format!("{}", VarRef::new("name", None)));
assert_eq!("abc?", format!("{}", VarRef::new("abc", Some(ExprType::Boolean))));
assert_eq!("cba#", format!("{}", VarRef::new("cba", Some(ExprType::Double))));
assert_eq!("def%", format!("{}", VarRef::new("def", Some(ExprType::Integer))));
assert_eq!("ghi$", format!("{}", VarRef::new("ghi", Some(ExprType::Text))));
}
#[test]
fn test_varref_accepts() {
assert!(VarRef::new("a", None).accepts(ExprType::Boolean));
assert!(VarRef::new("a", None).accepts(ExprType::Double));
assert!(VarRef::new("a", None).accepts(ExprType::Integer));
assert!(VarRef::new("a", None).accepts(ExprType::Text));
assert!(VarRef::new("a", Some(ExprType::Boolean)).accepts(ExprType::Boolean));
assert!(!VarRef::new("a", Some(ExprType::Boolean)).accepts(ExprType::Double));
assert!(!VarRef::new("a", Some(ExprType::Boolean)).accepts(ExprType::Integer));
assert!(!VarRef::new("a", Some(ExprType::Boolean)).accepts(ExprType::Text));
assert!(!VarRef::new("a", Some(ExprType::Double)).accepts(ExprType::Boolean));
assert!(VarRef::new("a", Some(ExprType::Double)).accepts(ExprType::Double));
assert!(!VarRef::new("a", Some(ExprType::Double)).accepts(ExprType::Integer));
assert!(!VarRef::new("a", Some(ExprType::Double)).accepts(ExprType::Text));
assert!(!VarRef::new("a", Some(ExprType::Integer)).accepts(ExprType::Boolean));
assert!(!VarRef::new("a", Some(ExprType::Integer)).accepts(ExprType::Double));
assert!(VarRef::new("a", Some(ExprType::Integer)).accepts(ExprType::Integer));
assert!(!VarRef::new("a", Some(ExprType::Integer)).accepts(ExprType::Text));
assert!(!VarRef::new("a", Some(ExprType::Text)).accepts(ExprType::Boolean));
assert!(!VarRef::new("a", Some(ExprType::Text)).accepts(ExprType::Double));
assert!(!VarRef::new("a", Some(ExprType::Text)).accepts(ExprType::Integer));
assert!(VarRef::new("a", Some(ExprType::Text)).accepts(ExprType::Text));
}
#[test]
fn test_value_display() {
assert_eq!("TRUE", format!("{}", Value::Boolean(true)));
assert_eq!("FALSE", format!("{}", Value::Boolean(false)));
assert_eq!("3.0", format!("{}", Value::Double(3.0)));
assert_eq!("3.1", format!("{}", Value::Double(3.1)));
assert_eq!("0.51", format!("{}", Value::Double(0.51)));
assert_eq!("inf", format!("{}", Value::Double(f64::INFINITY)));
assert_eq!("-inf", format!("{}", Value::Double(f64::NEG_INFINITY)));
assert_eq!("NaN", format!("{}", Value::Double(f64::NAN)));
assert_eq!("NaN", format!("{}", Value::Double(-f64::NAN)));
assert_eq!("-56", format!("{}", Value::Integer(-56)));
assert_eq!("\"some words\"", format!("{}", Value::Text("some words".to_owned())));
}
}