use crate::reader::LineCol;
use std::convert::TryFrom;
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 {
let mut expr = self;
loop {
match expr {
Expr::Boolean(span) => return span.pos,
Expr::Double(span) => return span.pos,
Expr::Integer(span) => return span.pos,
Expr::Text(span) => return span.pos,
Expr::Symbol(span) => return span.pos,
Expr::Not(span) => return span.pos,
Expr::Negate(span) => return span.pos,
Expr::Call(span) => return span.vref_pos,
Expr::Add(span)
| Expr::And(span)
| Expr::Divide(span)
| Expr::Equal(span)
| Expr::Greater(span)
| Expr::GreaterEqual(span)
| Expr::Less(span)
| Expr::LessEqual(span)
| Expr::Modulo(span)
| Expr::Multiply(span)
| Expr::NotEqual(span)
| Expr::Or(span)
| Expr::Power(span)
| Expr::ShiftLeft(span)
| Expr::ShiftRight(span)
| Expr::Subtract(span)
| Expr::Xor(span) => expr = &span.lhs,
}
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum ExprType {
Boolean = 0,
Double = 1,
Integer = 2,
Text = 3,
}
impl TryFrom<u8> for ExprType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Boolean),
1 => Ok(Self::Double),
2 => Ok(Self::Integer),
3 => Ok(Self::Text),
_ => Err(()),
}
}
}
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 => '$',
}
}
}
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 {
pub name: String,
pub 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 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,
},
}
}
}
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, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum ArgSep {
End = 0,
Short = 1,
Long = 2,
As = 3,
}
impl TryFrom<u8> for ArgSep {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::End),
1 => Ok(Self::Short),
2 => Ok(Self::Long),
3 => Ok(Self::As),
_ => Err(()),
}
}
}
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<Expr>>,
}
#[derive(Debug, PartialEq)]
pub struct DeclareSpan {
pub name: VarRef,
pub name_pos: LineCol,
pub params: Vec<VarRef>,
}
#[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>,
pub pos: LineCol,
}
#[derive(Debug, Eq, PartialEq)]
pub struct ExitSpan {
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, LineCol),
Reset(LineCol),
ResumeNext(LineCol),
}
#[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),
Declare(DeclareSpan),
Dim(DimSpan),
DimArray(DimArraySpan),
Do(DoSpan),
End(EndSpan),
ExitDo(ExitSpan),
ExitFor(ExitSpan),
ExitFunction(ExitSpan),
ExitSub(ExitSpan),
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));
}
}