basic-lang 0.2.0

The BASIC programming language as it was in 1978.
Documentation
use super::{Address, Op, Program, Stack, Val, Var};
use crate::error;
use crate::lang::{Column, Error, Line, LineNumber, MaxValue};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::ops::Range;
use std::sync::Arc;

type Result<T> = std::result::Result<T, Error>;

const INTRO: &str = "64K BASIC";
const READY: &str = "READY.";

/// ## Virtual machine

pub struct Runtime {
    source: BTreeMap<LineNumber, Line>,
    dirty: bool,
    program: Program,
    pc: Address,
    entry_address: Address,
    indirect_errors: Arc<Vec<Error>>,
    direct_errors: Arc<Vec<Error>>,
    stack: Stack<Val>,
    vars: Var,
    state: Status,
    print_col: usize,
}

/// ## Events for the user interface

pub enum Event {
    Errors(Arc<Vec<Error>>),
    Print(String),
    List((String, Vec<Range<usize>>)),
    Stopped,
    Running,
}

#[derive(Debug, PartialEq)]
enum Status {
    Intro,
    Stopped,
    Listing(Range<LineNumber>),
    Running,
    Interrupt,
}

impl Default for Runtime {
    fn default() -> Self {
        Runtime {
            source: BTreeMap::new(),
            dirty: false,
            program: Program::new(),
            pc: 0,
            entry_address: 1,
            indirect_errors: Arc::new(vec![]),
            direct_errors: Arc::new(vec![]),
            stack: Stack::new("STACK OVERFLOW"),
            vars: Var::new(),
            state: Status::Intro,
            print_col: 0,
        }
    }
}

impl Runtime {
    pub fn new() -> Runtime {
        Runtime::default()
    }

    /// Enters a line of BASIC.
    /// Returns true if it was a non-blank direct line.
    /// Return value is useful for command history.
    pub fn enter(&mut self, s: &str) -> bool {
        if s.trim().is_empty() {
            return false;
        }
        let line = Line::new(s);
        if line.is_direct() {
            if self.dirty {
                self.program.clear();
                self.program
                    .compile(self.source.iter().map(|(_, line)| line));
                self.dirty = false;
            }
            self.program.compile(&line);
            let (pc, indirect_errors, direct_errors) = self.program.link();
            self.pc = pc;
            self.entry_address = pc;
            self.indirect_errors = indirect_errors;
            self.direct_errors = direct_errors;
            self.state = Status::Running;
            true
        } else {
            if line.is_empty() {
                self.dirty = self.source.remove(&line.number()).is_some();
            } else {
                self.source.insert(line.number(), line);
                self.dirty = true;
            }
            false
        }
    }

    pub fn interrupt(&mut self) {
        self.state = Status::Interrupt;
    }

    pub fn line(&self, num: usize) -> Option<(String, Vec<Range<usize>>)> {
        if num > LineNumber::max_value() as usize {
            return None;
        }
        let mut range = Some(num as u16)..Some(num as u16);
        self.list_line(&mut range)
    }

    fn list_line(&self, range: &mut Range<LineNumber>) -> Option<(String, Vec<Range<usize>>)> {
        let mut source_range = self.source.range(range.start..=range.end);
        if let Some((line_number, line)) = source_range.next() {
            if *line_number < range.end {
                if let Some(num) = line_number {
                    range.start = Some(num + 1);
                }
            } else {
                range.start = Some(0);
                range.end = Some(0);
            }
            let columns: Vec<Column> = self
                .indirect_errors
                .iter()
                .filter_map(|e| {
                    if e.line_number() == *line_number {
                        Some(e.column())
                    } else {
                        None
                    }
                })
                .collect();
            return Some((line.to_string(), columns));
        }
        None
    }

    fn ready(&mut self) -> Option<Event> {
        if self.entry_address != 0 {
            self.entry_address = 0;
            let mut s = String::new();
            if self.print_col > 0 {
                s.push('\n');
                self.print_col = 0;
            }
            s.push_str(READY);
            s.push('\n');
            return Some(Event::Print(s));
        };
        None
    }

    pub fn execute(&mut self, iterations: usize) -> Event {
        fn prev_pc(pc: Address) -> Address {
            if pc > 0 {
                pc - 1
            } else {
                pc
            }
        }
        match &self.state {
            Status::Intro => {
                self.state = Status::Stopped;
                let mut s = INTRO.to_string();
                if let Some(version) = option_env!("CARGO_PKG_VERSION") {
                    s.push(' ');
                    s.push_str(version);
                }
                #[cfg(debug_assertions)]
                s.push_str("+debug");
                s.push('\n');
                return Event::Print(s);
            }
            Status::Stopped => match self.ready() {
                Some(e) => return e,
                None => return Event::Stopped,
            },
            Status::Interrupt => {
                self.state = Status::Stopped;
                let line_number = self.program.line_number_for(prev_pc(self.pc));
                return Event::Errors(Arc::new(vec![error!(Break, line_number)]));
            }
            Status::Listing(range) => {
                let mut range = range.clone();
                if let Some((string, columns)) = self.list_line(&mut range) {
                    self.state = Status::Listing(range);
                    return Event::List((string, columns));
                } else {
                    self.state = Status::Running;
                }
            }
            Status::Running => {
                if !self.direct_errors.is_empty() {
                    self.state = Status::Stopped;
                    return Event::Errors(Arc::clone(&self.direct_errors));
                }
            }
        }
        debug_assert_eq!(self.state, Status::Running);
        match self.execute_loop(iterations) {
            Ok(event) => {
                if self.state == Status::Stopped {
                    match event {
                        Event::Stopped => match self.ready() {
                            Some(e) => e,
                            None => event,
                        },
                        _ => event,
                    }
                } else {
                    event
                }
            }
            Err(error) => {
                self.stack.clear();
                self.print_col = 0;
                self.state = Status::Stopped;
                let line_number = self.program.line_number_for(prev_pc(self.pc));
                Event::Errors(Arc::new(vec![error.in_line_number(line_number)]))
            }
        }
    }

    #[allow(clippy::cognitive_complexity)]
    fn execute_loop(&mut self, iterations: usize) -> Result<Event> {
        let has_indirect_errors = !self.indirect_errors.is_empty();
        for _ in 0..iterations {
            let op = match self.program.get(self.pc) {
                Some(v) => v,
                None => return Err(error!(InternalError; "INVALID PC ADDRESS")),
            };
            self.pc += 1;
            match op {
                Op::Literal(val) => self.stack.push(val.clone())?,
                Op::Pop(var_name) => self.vars.store(var_name, self.stack.pop()?)?,
                Op::Push(var_name) => self.stack.push(self.vars.fetch(var_name))?,
                Op::For(addr) => {
                    let addr = *addr;
                    self.r#for(addr)?;
                }
                Op::If(_) => return Err(error!(InternalError; "'IF' NOT YET IMPLEMENTED; PANIC")),
                Op::Jump(addr) => {
                    self.pc = *addr;
                    if has_indirect_errors && self.pc < self.entry_address {
                        self.state = Status::Stopped;
                        return Ok(Event::Errors(Arc::clone(&self.indirect_errors)));
                    }
                }
                Op::Return => {
                    return Err(error!(InternalError; "'RETURN' NOT YET IMPLEMENTED; PANIC"))
                }
                Op::Clear => {
                    self.stack.clear();
                    self.vars.clear();
                }
                Op::List => return self.r#list(),
                Op::End => return self.r#end(),
                Op::Print => return self.r#print(),

                Op::Neg => self.r#negation()?,
                Op::Exp => self.pop_2_op(&Val::unimplemented)?,
                Op::Mul => self.pop_2_op(&Val::multiply)?,
                Op::Div => self.pop_2_op(&Val::divide)?,
                Op::DivInt => self.pop_2_op(&Val::unimplemented)?,
                Op::Mod => self.pop_2_op(&Val::unimplemented)?,
                Op::Add => self.pop_2_op(&Val::sum)?,
                Op::Sub => self.pop_2_op(&Val::subtract)?,
                Op::Eq => self.pop_2_op(&Val::unimplemented)?,
                Op::NotEq => self.pop_2_op(&Val::unimplemented)?,
                Op::Lt => self.pop_2_op(&Val::less)?,
                Op::LtEq => self.pop_2_op(&Val::unimplemented)?,
                Op::Gt => self.pop_2_op(&Val::greater)?,
                Op::GtEq => self.pop_2_op(&Val::unimplemented)?,
                Op::Not => self.pop_2_op(&Val::unimplemented)?,
                Op::And => self.pop_2_op(&Val::unimplemented)?,
                Op::Or => self.pop_2_op(&Val::unimplemented)?,
                Op::Xor => self.pop_2_op(&Val::unimplemented)?,
                Op::Imp => self.pop_2_op(&Val::unimplemented)?,
                Op::Eqv => self.pop_2_op(&Val::unimplemented)?,
            }
        }
        Ok(Event::Running)
    }

    fn pop_2_op<T: Fn(Val, Val) -> Result<Val>>(&mut self, func: &T) -> Result<()> {
        let (lhs, rhs) = self.stack.pop_2()?;
        self.stack.push(func(lhs, rhs)?)?;
        Ok(())
    }

    fn r#for(&mut self, addr: Address) -> Result<()> {
        loop {
            if self.stack.len() < 4 {
                break;
            }
            let (first_iter, next_name) = match self.stack.pop()? {
                Val::String(s) => (false, s),
                _ => (true, "".to_string()),
            };
            let var_name_val = self.stack.pop()?;
            let to_val = self.stack.pop()?;
            let step_val = self.stack.pop()?;
            if let Val::String(var_name) = var_name_val {
                if !next_name.is_empty() && var_name != next_name {
                    self.stack.push(Val::String(next_name))?;
                    continue;
                }
                let mut current = self.vars.fetch(&var_name);
                if !first_iter {
                    current = Val::sum(current, step_val.clone())?;
                    self.vars.store(&var_name, current.clone())?;
                }
                if let Ok(step) = f64::try_from(step_val.clone()) {
                    let done = Val::Integer(-1)
                        == if step < 0.0 {
                            Val::less(current, to_val.clone())?
                        } else {
                            Val::less(to_val.clone(), current)?
                        };
                    if done {
                        self.pc = addr;
                    } else {
                        self.stack.push(step_val)?;
                        self.stack.push(to_val)?;
                        self.stack.push(Val::String(var_name))?;
                    }
                    return Ok(());
                }
            }
            break;
        }
        Err(error!(NextWithoutFor; "MISSING STACK FRAME"))
    }

    fn r#negation(&mut self) -> Result<()> {
        let val = self.stack.pop()?;
        self.stack.push(Val::negate(val)?)?;
        Ok(())
    }

    fn r#list(&mut self) -> Result<Event> {
        let (from, to) = self.stack.pop_2()?;
        let from = LineNumber::try_from(from)?;
        let to = LineNumber::try_from(to)?;
        if to < from {
            return Err(error!(UndefinedLine; "INVALID RANGE"));
        }
        self.state = Status::Listing(from..to);
        Ok(Event::Running)
    }

    fn r#end(&mut self) -> Result<Event> {
        self.pc -= 1;
        self.state = Status::Stopped;
        Ok(Event::Stopped)
    }

    fn r#print(&mut self) -> Result<Event> {
        match self.stack.pop()? {
            Val::Integer(len) => {
                let mut s = String::new();
                for item in self.stack.pop_n(len as usize)? {
                    match item {
                        Val::Char('\n') => {
                            s.push('\n');
                            self.print_col = 0;
                        }
                        Val::Char('\t') => {
                            let len = 14 - (self.print_col % 14);
                            s.push_str(&" ".repeat(len));
                            self.print_col += len;
                        }
                        _ => {
                            let val_str = format!("{}", item);
                            for ch in val_str.chars() {
                                s.push(ch);
                                match ch {
                                    '\n' => self.print_col = 0,
                                    _ => self.print_col += 1,
                                }
                            }
                        }
                    }
                }
                Ok(Event::Print(s))
            }
            _ => Err(error!(InternalError; "EXPECTED VECTOR ON STACK")),
        }
    }
}