diff --git a/inputs/printing_program.bas b/inputs/printing_program.bas new file mode 100644 index 0000000..9c6030b --- /dev/null +++ b/inputs/printing_program.bas @@ -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$ \ No newline at end of file diff --git a/inputs/simple_program.bas b/inputs/simple_program.bas index 79a2d61..e8212c1 100644 --- a/inputs/simple_program.bas +++ b/inputs/simple_program.bas @@ -1,3 +1,4 @@ 10 PRINT "Hello World" 20 LET apple=10 -30 LET b$="Hello" \ No newline at end of file +30 LET b$="Hello" +40 LET cat=apple \ No newline at end of file diff --git a/src/basic.rs b/src/basic.rs index b115951..ca935ff 100644 --- a/src/basic.rs +++ b/src/basic.rs @@ -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 = 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); + } } diff --git a/src/main.rs b/src/main.rs index 4b2bd1f..cf64e72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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(); }