Support for assigning variables to other variables, printing variables, and commenting

This commit is contained in:
Lewis Dale 2023-01-20 16:32:00 +00:00
parent 0e6ebd621f
commit b2e2db96db
4 changed files with 128 additions and 30 deletions

View File

@ -0,0 +1,8 @@
10 REM Assign vars
20 LET a$="Hello"
30 LET b$="World"
40 LET c$=a$
50 REM Print values
60 PRINT a$
70 PRINT b$
80 PRINT c$

View File

@ -1,3 +1,4 @@
10 PRINT "Hello World"
20 LET apple=10
30 LET b$="Hello"
40 LET cat=apple

View File

@ -2,15 +2,15 @@ use std::collections::HashMap;
use nom::{
branch::alt,
bytes::complete::{escaped_transform, tag},
bytes::complete::{escaped_transform, tag, take_until, take_while, take_till},
character::{
complete::{alphanumeric1, digit1, i64 as cci64},
complete::{anychar, u64 as ccu64},
streaming::none_of,
complete::{anychar, u64 as ccu64, line_ending},
streaming::none_of, is_newline,
},
combinator::{map, not, value, verify},
combinator::{map, not, value, verify, rest},
multi::separated_list0,
sequence::{delimited, terminated},
sequence::{delimited, terminated, preceded},
IResult,
};
@ -18,9 +18,10 @@ pub type Line = (usize, Command);
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Command {
Print(String),
Print(PrintOutput),
GoTo(usize),
Var((String, Primitive)),
Comment,
None,
}
@ -28,6 +29,13 @@ pub enum Command {
pub enum Primitive {
Int(i64),
String(String),
Assignment(String)
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum PrintOutput {
Value(String),
Variable(String)
}
#[derive(Debug, PartialEq, Eq, Clone)]
@ -96,16 +104,20 @@ impl Program {
while let Some(node) = iter.next() {
if let Node::Link { item, next: _ } = node {
match item.1 {
Command::Print(line) => println!("{}", line),
Command::Print(PrintOutput::Value(line)) => println!("{}", line),
Command::Print(PrintOutput::Variable(variable)) => println!("{:?}", self.vars.get(&variable).unwrap()),
Command::GoTo(line) => iter.jump_to_line(line),
Command::Var((id, Primitive::Assignment(variable))) => {
self.vars.insert(id, self.vars.get(&variable).unwrap().clone());
},
Command::Var((id, var)) => {
self.vars.insert(id, var);
}
},
Command::Comment => (),
_ => panic!("Unrecognised command"),
}
};
}
println!("{:?}", self.vars);
}
}
@ -131,6 +143,11 @@ impl Iterator for Program {
}
}
// Take everything until it hits a newline, if it does
fn consume_line(i: &str) -> IResult<&str, &str> {
take_while(|c| c != '\n')(i)
}
fn read_string(i: &str) -> IResult<&str, String> {
delimited(
tag("\""),
@ -144,29 +161,62 @@ fn read_string(i: &str) -> IResult<&str, String> {
}
fn match_command(i: &str) -> IResult<&str, &str> {
alt((tag("PRINT"), tag("GO TO"), tag("LET")))(i)
alt((tag("PRINT"), tag("GO TO"), tag("LET"), tag("REM")))(i)
}
fn parse_int_variable_name(i: &str) -> IResult<&str, String> {
map(
preceded(not(digit1), alphanumeric1),
String::from
)(i)
}
fn parse_int(i: &str) -> IResult<&str, (String, Primitive)> {
let (i, _) = not(digit1)(i)?;
let (i, id) = alphanumeric1(i)?;
let (i, id) = parse_int_variable_name(i)?;
let (i, _) = tag("=")(i)?;
let (i, var) = map(cci64, Primitive::Int)(i)?;
Ok((i, (id.to_string(), var)))
Ok((i, (id, var)))
}
fn parse_str_variable_name(i: &str) -> IResult<&str, String> {
let (i, id) = terminated(
verify(anychar, |c| c.is_alphabetic()),
tag("$")
)(i)?;
let id = format!("{}$", id);
Ok((i, id))
}
fn parse_str(i: &str) -> IResult<&str, (String, Primitive)> {
let (i, id) = verify(anychar, |c| c.is_alphabetic())(i)?;
let (i, _) = tag("$")(i)?;
let (i, id) = parse_str_variable_name(i)?;
let (i, _) = tag("=")(i)?;
let (i, var) = map(read_string, Primitive::String)(i)?;
let var_name = format!("{}$", id);
Ok((i, (var_name, var)))
Ok((i, (id, var)))
}
fn parse_assignment(i: &str) -> IResult<&str, (String, Primitive)> {
let (i, id) = alt((
parse_str_variable_name,
parse_int_variable_name
))(i)?;
let (i, _) = tag("=")(i)?;
let (i, assigned_variable) = consume_line(i)?;
Ok((i, (id.to_string(), Primitive::Assignment(assigned_variable.to_string()))))
}
fn parse_var(i: &str) -> IResult<&str, (String, Primitive)> {
alt((parse_int, parse_str))(i)
alt((parse_int, parse_str, parse_assignment))(i)
}
fn parse_print_command(i: &str) -> IResult<&str, PrintOutput> {
alt((
map(alt((
parse_str_variable_name,
parse_int_variable_name
)), PrintOutput::Variable),
map(read_string, PrintOutput::Value)
))(i)
}
fn parse_command(i: &str) -> IResult<&str, Command> {
@ -174,9 +224,13 @@ fn parse_command(i: &str) -> IResult<&str, Command> {
let (i, _) = tag(" ")(i)?;
let (i, cmd) = match command {
"PRINT" => map(read_string, Command::Print)(i)?,
"PRINT" => map(parse_print_command, Command::Print)(i)?,
"GO TO" => map(ccu64, |line| Command::GoTo(line as usize))(i)?,
"LET" => map(parse_var, Command::Var)(i)?,
"REM" => {
let (i, _) = consume_line(i)?;
(i, Command::Comment)
},
_ => (i, Command::None),
};
@ -202,12 +256,14 @@ pub fn read_program(i: &str) -> IResult<&str, Program> {
#[cfg(test)]
mod tests {
use crate::basic::PrintOutput;
use super::{parse_line, read_program, read_string, Command, Line, Node, Primitive};
#[test]
fn it_parses_a_print_command() {
let input = "10 PRINT \"Hello, world\"";
let expected = (10, Command::Print(String::from("Hello, world")));
let expected = (10, Command::Print(PrintOutput::Value(String::from("Hello, world"))));
let (_, result) = parse_line(input).unwrap();
assert_eq!(expected, result);
@ -223,7 +279,7 @@ mod tests {
#[test]
fn it_parses_a_print_command_with_escaped_quotes() {
let input = r#"10 PRINT "Hello, \"world\"""#;
let expected = (10, Command::Print(String::from(r#"Hello, "world""#)));
let expected = (10, Command::Print(PrintOutput::Value(String::from(r#"Hello, "world""#))));
let (_, result) = parse_line(input).unwrap();
assert_eq!(expected, result);
@ -240,13 +296,13 @@ mod tests {
#[test]
fn it_can_create_a_linked_list_for_a_program() {
let mut node = Node::Link {
item: (10, Command::Print(String::from("Hello world"))),
item: (10, Command::Print(PrintOutput::Value(String::from("Hello world")))),
next: Box::new(Node::None),
};
node.push((20, Command::GoTo(10)));
let expected = Node::Link {
item: (10, Command::Print(String::from("Hello world"))),
item: (10, Command::Print(PrintOutput::Value(String::from("Hello world")))),
next: Box::new(Node::Link {
item: (20, Command::GoTo(10)),
next: Box::new(Node::None),
@ -258,15 +314,15 @@ mod tests {
#[test]
fn it_finds_a_node_by_line_number() {
let mut node = Node::Link {
item: (10, Command::Print(String::from("Hello world"))),
item: (10, Command::Print(PrintOutput::Value(String::from("Hello world")))),
next: Box::new(Node::None),
};
node.push((20, Command::Print(String::from("I'm a second line"))));
node.push((30, Command::Print(String::from("Still printing..."))));
node.push((20, Command::Print(PrintOutput::Value(String::from("I'm a second line")))));
node.push((30, Command::Print(PrintOutput::Value(String::from("Still printing...")))));
node.push((40, Command::GoTo(10)));
let expected: Option<Node> = Some(Node::Link {
item: (30, Command::Print(String::from("Still printing..."))),
item: (30, Command::Print(PrintOutput::Value(String::from("Still printing...")))),
next: Box::new(Node::Link {
item: (40, Command::GoTo(10)),
next: Box::new(Node::None),
@ -280,7 +336,7 @@ mod tests {
fn it_reads_a_program() {
let lines = "10 PRINT \"Hello world\"\n20 GO TO 10";
let expected_node = Node::Link {
item: (10, Command::Print(String::from("Hello world"))),
item: (10, Command::Print(PrintOutput::Value(String::from("Hello world")))),
next: Box::new(Node::Link {
item: (20, Command::GoTo(10)),
next: Box::new(Node::None),
@ -341,4 +397,37 @@ mod tests {
let result = parse_line(line);
assert!(result.is_err());
}
#[test]
fn it_assigns_one_variable_to_another() {
let line = "10 LET a=b$";
let (_, result) = parse_line(line).unwrap();
let expected: Line = (
10,
Command::Var((
String::from("a"),
Primitive::Assignment(String::from("b$"))
))
);
assert_eq!(result, expected);
}
#[test]
fn it_parses_a_print_command_with_a_variable_name() {
let line = "10 PRINT a$";
let (_, result) = parse_line(line).unwrap();
let expected: Line = (
10,
Command::Print(PrintOutput::Variable(String::from("a$")))
);
assert_eq!(result, expected);
}
#[test]
fn it_parses_a_comment() {
let line = "10 REM This is an arbitrary comment";
let (_, result) = parse_line(line).unwrap();
let expected: Line = (10, Command::Comment);
assert_eq!(result, expected);
}
}

View File

@ -3,7 +3,7 @@ use std::fs;
mod basic;
fn main() {
let file = fs::read_to_string("./inputs/simple_program.bas").unwrap();
let file = fs::read_to_string("./inputs/printing_program.bas").unwrap();
let mut program = basic::Program::from(file.as_str());
program.execute();
}