Skip to content

Commit

Permalink
renum statement
Browse files Browse the repository at this point in the history
  • Loading branch information
AE9RB committed Apr 14, 2020
1 parent a7ebdcf commit ab5c01d
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 26 deletions.
4 changes: 4 additions & 0 deletions src/doc/chapter_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ pub mod READ;
#[allow(non_snake_case)]
pub mod REM;

#[path = "statements/renum.rs"]
#[allow(non_snake_case)]
pub mod RENUM;

#[path = "statements/restore.rs"]
#[allow(non_snake_case)]
pub mod RESTORE;
Expand Down
20 changes: 20 additions & 0 deletions src/doc/statements/renum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*!
# `RENUM [<new number>][,<old number>][,<increment>]`
## Purpose
Renumber a program.
## Remarks
New number defaults to 10. If old number is specified, lines less than
that number are not renumbered. Increment defaults to 10.
All statements containing line numbers are updated.
You can not change the order of lines.
Failures do not modify the program in memory.
## Example
```text
RENUM 1000
RENUM 100,,100
```
*/
88 changes: 86 additions & 2 deletions src/lang/line.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{ast, lex, parse, token, Error, LineNumber};
use super::{ast::*, lex, parse, token, Column, Error, LineNumber, MaxValue};
use std::collections::HashMap;

#[derive(Debug)]
pub struct Line {
Expand All @@ -24,9 +25,92 @@ impl Line {
self.tokens.is_empty()
}

pub fn ast(&self) -> Result<Vec<ast::Statement>, Error> {
pub fn ast(&self) -> Result<Vec<Statement>, Error> {
parse(self.number, &self.tokens)
}

pub fn renum(&self, changes: &HashMap<u16, u16>) -> Self {
let number = if let Some(line_number) = self.number {
changes.get(&line_number).cloned().or(self.number)
} else {
None
};
let ast = match parse(self.number, &self.tokens) {
Ok(ast) => ast,
Err(_) => {
return Line {
number: self.number,
tokens: self.tokens.clone(),
}
}
};
let mut visitor = RenumVisitor::new(changes);
for statement in ast {
statement.accept(&mut visitor);
}
if visitor.replace.is_empty() {
return Line {
number,
tokens: self.tokens.clone(),
};
}
let mut s: String = self.tokens.iter().map(|s| s.to_string()).collect();
while let Some((col, num)) = visitor.replace.pop() {
s.replace_range(col, &format!("{}", num));
}
let (_, tokens) = lex(&s);
Line { number, tokens }
}
}

#[derive(Debug)]
struct RenumVisitor<'a> {
changes: &'a HashMap<u16, u16>,
replace: Vec<(Column, u16)>,
}

impl<'a> RenumVisitor<'a> {
fn new(changes: &HashMap<u16, u16>) -> RenumVisitor {
RenumVisitor {
changes,
replace: vec![],
}
}
fn line(&mut self, expr: &Expression) {
use Expression::*;
let (col, n) = match expr {
Single(col, n) => (col, *n as f64),
Double(col, n) => (col, *n),
Integer(col, n) => (col, *n as f64),
_ => return,
};
if n > LineNumber::max_value() as f64 {
return;
}
let n = n as u16;
if let Some(new_num) = self.changes.get(&n) {
self.replace.push((col.clone(), *new_num));
}
}
}

impl<'a> Visitor for RenumVisitor<'a> {
fn visit_statement(&mut self, stmt: &Statement) {
use Statement::*;
match stmt {
Goto(_, ln) | Gosub(_, ln) | Restore(_, ln) | Run(_, ln) => self.line(ln),
Delete(_, ln1, ln2) | List(_, ln1, ln2) => {
self.line(ln1);
self.line(ln2);
}
OnGoto(_, _, ve) => {
for ln in ve {
self.line(ln);
}
}
_ => {}
}
}
}

impl std::fmt::Display for Line {
Expand Down
36 changes: 35 additions & 1 deletion src/mach/listing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::MAX_LINE_LEN;
use crate::error;
use crate::lang::{Column, Error, Line, LineNumber, MaxValue};
use std::collections::{btree_map::Values, BTreeMap};
use std::collections::{btree_map::Values, BTreeMap, HashMap};
use std::ops::{Range, RangeInclusive};
use std::sync::Arc;

Expand Down Expand Up @@ -110,4 +110,38 @@ impl Listing {
}
None
}

pub fn renum(&mut self, new_start: u16, old_start: u16, step: u16) -> Result<(), Error> {
let mut changes: HashMap<u16, u16> = HashMap::default();
let mut old_end: u16 = LineNumber::max_value() + 1;
let mut new_num = new_start;
for (&ln, _) in self.source.iter() {
let ln = match ln {
Some(ln) => ln,
None => return Err(error!(InternalError)),
};
if ln >= old_start {
if old_end <= LineNumber::max_value() && old_end >= new_start {
return Err(error!(IllegalFunctionCall));
}
if new_num > LineNumber::max_value() {
return Err(error!(Overflow));
}
changes.insert(ln, new_num);
new_num = match new_num.checked_add(step) {
Some(num) => num,
None => return Err(error!(Overflow)),
};
} else {
old_end = ln;
}
}
let mut new_source: BTreeMap<LineNumber, Line> = BTreeMap::default();
for line in self.lines() {
let line = line.renum(&changes);
new_source.insert(line.number(), line);
}
self.source = Arc::from(new_source);
Ok(())
}
}
53 changes: 30 additions & 23 deletions src/mach/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const PROMPT: &str = "READY.";

pub struct Runtime {
prompt: String,
source: Listing,
listing: Listing,
dirty: bool,
program: Program,
pc: Address,
Expand Down Expand Up @@ -69,7 +69,7 @@ impl Default for Runtime {
fn default() -> Self {
Runtime {
prompt: PROMPT.into(),
source: Listing::default(),
listing: Listing::default(),
dirty: false,
program: Program::default(),
pc: 0,
Expand Down Expand Up @@ -123,25 +123,25 @@ impl Runtime {
fn enter_direct(&mut self, line: Line) {
if self.dirty {
self.program.clear();
self.program.compile(self.source.lines());
self.program.compile(self.listing.lines());
self.dirty = false;
}
self.program.compile(&line);
let (pc, indirect_errors, direct_errors) = self.program.link();
self.pc = pc;
self.tr = None;
self.entry_address = pc;
self.source.indirect_errors = indirect_errors;
self.source.direct_errors = direct_errors;
self.listing.indirect_errors = indirect_errors;
self.listing.direct_errors = direct_errors;
self.state = State::Running;
}

fn enter_indirect(&mut self, line: Line) {
self.cont = State::Stopped;
if line.is_empty() {
self.dirty = self.source.remove(line.number()).is_some();
self.dirty = self.listing.remove(line.number()).is_some();
} else {
self.source.insert(line);
self.listing.insert(line);
self.dirty = true;
}
}
Expand Down Expand Up @@ -225,13 +225,13 @@ impl Runtime {

/// Obtain a thread-safe Listing for saving and line completion.
pub fn get_listing(&self) -> Listing {
self.source.clone()
self.listing.clone()
}

/// Set a new listing. Used to load a program.
pub fn set_listing(&mut self, listing: Listing, run: bool) {
self.r#new_();
self.source = listing;
self.listing = listing;
if run {
self.enter("RUN");
}
Expand Down Expand Up @@ -285,7 +285,7 @@ impl Runtime {
}
State::Listing(range) => {
let mut range = range.clone();
if let Some((string, columns)) = self.source.list_line(&mut range) {
if let Some((string, columns)) = self.listing.list_line(&mut range) {
self.state = State::Listing(range);
return Event::List((string, columns));
} else {
Expand All @@ -303,9 +303,9 @@ impl Runtime {
return Event::Errors(Arc::new(vec![error!(RedoFromStart)]));
}
State::InputRunning | State::Running => {
if !self.source.direct_errors.is_empty() {
if !self.listing.direct_errors.is_empty() {
self.state = State::Stopped;
return Event::Errors(Arc::clone(&self.source.direct_errors));
return Event::Errors(Arc::clone(&self.listing.direct_errors));
}
}
State::Inkey | State::RuntimeError(_) => {}
Expand Down Expand Up @@ -381,7 +381,7 @@ impl Runtime {

#[allow(clippy::cognitive_complexity)]
fn execute_loop(&mut self, iterations: usize) -> Result<Event> {
let has_indirect_errors = !self.source.indirect_errors.is_empty();
let has_indirect_errors = !self.listing.indirect_errors.is_empty();
for _ in 0..iterations {
if self.tron {
let tr = self.program.line_number_for(self.pc);
Expand All @@ -390,7 +390,7 @@ impl Runtime {
if let Some(num) = self.tr {
let num = format!("[{}]", num);
self.print_col += num.len();
return Ok(Event::Print(num.into()));
return Ok(Event::Print(num));
}
}
}
Expand Down Expand Up @@ -435,7 +435,7 @@ impl Runtime {
if has_indirect_errors && self.pc < self.entry_address {
self.state = State::Stopped;
self.cont = State::Stopped;
return Ok(Event::Errors(Arc::clone(&self.source.indirect_errors)));
return Ok(Event::Errors(Arc::clone(&self.listing.indirect_errors)));
}
}
Opcode::Clear => self.r#clear(),
Expand Down Expand Up @@ -626,7 +626,7 @@ impl Runtime {
if from == Some(0) && to == Some(LineNumber::max_value()) {
return Err(error!(IllegalFunctionCall));
}
if self.source.remove_range(from..=to) {
if self.listing.remove_range(from..=to) {
self.dirty = true;
self.state = State::Stopped;
}
Expand Down Expand Up @@ -752,7 +752,7 @@ impl Runtime {

fn r#new_(&mut self) -> Event {
self.r#clear();
self.source.clear();
self.listing.clear();
self.dirty = true;
self.state = State::Stopped;
self.tron = false;
Expand Down Expand Up @@ -830,12 +830,19 @@ impl Runtime {
}

fn r#renum(&mut self) -> Result<Event> {
let step = LineNumber::try_from(self.stack.pop()?)?;
let old_start = LineNumber::try_from(self.stack.pop()?)?;
let new_start = LineNumber::try_from(self.stack.pop()?)?;
Ok(Event::Print(
format!(">>WIP>>RENUM {:?},{:?},{:?}\n", new_start, old_start, step).into(),
))
if self.pc < self.entry_address {
return Err(error!(IllegalDirect));
}
if !self.listing.indirect_errors.is_empty() {
return Ok(Event::Errors(Arc::clone(&self.listing.indirect_errors)));
}
let step = u16::try_from(self.stack.pop()?)?;
let old_start = u16::try_from(self.stack.pop()?)?;
let new_start = u16::try_from(self.stack.pop()?)?;
self.listing.renum(new_start, old_start, step)?;

self.state = State::Stopped;
return Ok(self.r#end());
}

fn r#restore(&mut self, addr: Address) -> Result<()> {
Expand Down

0 comments on commit ab5c01d

Please sign in to comment.