Start refactoring parsers into separate files inside a parsers module

This commit is contained in:
Lewis Dale 2023-01-20 16:46:32 +00:00
parent b2e2db96db
commit 4da9e9d781
5 changed files with 151 additions and 96 deletions

View File

@ -2,18 +2,21 @@ use std::collections::HashMap;
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::{escaped_transform, tag, take_until, take_while, take_till}, bytes::complete::{escaped_transform, tag, take_till, take_until, take_while},
character::{ character::{
complete::{alphanumeric1, digit1, i64 as cci64}, complete::{alphanumeric1, digit1, i64 as cci64},
complete::{anychar, u64 as ccu64, line_ending}, complete::{anychar, line_ending, u64 as ccu64},
streaming::none_of, is_newline, is_newline,
streaming::none_of,
}, },
combinator::{map, not, value, verify, rest}, combinator::{map, not, rest, value, verify},
multi::separated_list0, multi::separated_list0,
sequence::{delimited, terminated, preceded}, sequence::{delimited, preceded, terminated},
IResult, IResult,
}; };
use crate::parsers::{generic, variables};
pub type Line = (usize, Command); pub type Line = (usize, Command);
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
@ -29,13 +32,13 @@ pub enum Command {
pub enum Primitive { pub enum Primitive {
Int(i64), Int(i64),
String(String), String(String),
Assignment(String) Assignment(String),
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum PrintOutput { pub enum PrintOutput {
Value(String), Value(String),
Variable(String) Variable(String),
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
@ -105,14 +108,17 @@ impl Program {
if let Node::Link { item, next: _ } = node { if let Node::Link { item, next: _ } = node {
match item.1 { match item.1 {
Command::Print(PrintOutput::Value(line)) => println!("{}", line), Command::Print(PrintOutput::Value(line)) => println!("{}", line),
Command::Print(PrintOutput::Variable(variable)) => println!("{:?}", self.vars.get(&variable).unwrap()), Command::Print(PrintOutput::Variable(variable)) => {
println!("{:?}", self.vars.get(&variable).unwrap())
}
Command::GoTo(line) => iter.jump_to_line(line), Command::GoTo(line) => iter.jump_to_line(line),
Command::Var((id, Primitive::Assignment(variable))) => { Command::Var((id, Primitive::Assignment(variable))) => {
self.vars.insert(id, self.vars.get(&variable).unwrap().clone()); self.vars
}, .insert(id, self.vars.get(&variable).unwrap().clone());
}
Command::Var((id, var)) => { Command::Var((id, var)) => {
self.vars.insert(id, var); self.vars.insert(id, var);
}, }
Command::Comment => (), Command::Comment => (),
_ => panic!("Unrecognised command"), _ => panic!("Unrecognised command"),
} }
@ -143,79 +149,20 @@ 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("\""),
escaped_transform(
none_of("\\\""),
'\\',
alt((value("\\", tag("\\")), value("\"", tag("\"")))),
),
tag("\""),
)(i)
}
fn match_command(i: &str) -> IResult<&str, &str> { fn match_command(i: &str) -> IResult<&str, &str> {
alt((tag("PRINT"), tag("GO TO"), tag("LET"), tag("REM")))(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, id) = parse_int_variable_name(i)?;
let (i, _) = tag("=")(i)?;
let (i, var) = map(cci64, Primitive::Int)(i)?;
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) = parse_str_variable_name(i)?;
let (i, _) = tag("=")(i)?;
let (i, var) = map(read_string, Primitive::String)(i)?;
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, parse_assignment))(i)
}
fn parse_print_command(i: &str) -> IResult<&str, PrintOutput> { fn parse_print_command(i: &str) -> IResult<&str, PrintOutput> {
alt(( alt((
map(alt(( map(
parse_str_variable_name, alt((
parse_int_variable_name variables::parse_str_variable_name,
)), PrintOutput::Variable), variables::parse_int_variable_name,
map(read_string, PrintOutput::Value) )),
PrintOutput::Variable,
),
map(generic::read_string, PrintOutput::Value),
))(i) ))(i)
} }
@ -226,11 +173,11 @@ fn parse_command(i: &str) -> IResult<&str, Command> {
let (i, cmd) = match command { let (i, cmd) = match command {
"PRINT" => map(parse_print_command, Command::Print)(i)?, "PRINT" => map(parse_print_command, Command::Print)(i)?,
"GO TO" => map(ccu64, |line| Command::GoTo(line as usize))(i)?, "GO TO" => map(ccu64, |line| Command::GoTo(line as usize))(i)?,
"LET" => map(parse_var, Command::Var)(i)?, "LET" => map(variables::parse_var, Command::Var)(i)?,
"REM" => { "REM" => {
let (i, _) = consume_line(i)?; let (i, _) = generic::consume_line(i)?;
(i, Command::Comment) (i, Command::Comment)
}, }
_ => (i, Command::None), _ => (i, Command::None),
}; };
@ -258,12 +205,17 @@ pub fn read_program(i: &str) -> IResult<&str, Program> {
mod tests { mod tests {
use crate::basic::PrintOutput; use crate::basic::PrintOutput;
use super::{parse_line, read_program, read_string, Command, Line, Node, Primitive}; use super::{parse_line, read_program, Command, Line, Node, Primitive};
use crate::parsers::generic::read_string;
#[test] #[test]
fn it_parses_a_print_command() { fn it_parses_a_print_command() {
let input = "10 PRINT \"Hello, world\""; let input = "10 PRINT \"Hello, world\"";
let expected = (10, Command::Print(PrintOutput::Value(String::from("Hello, world")))); let expected = (
10,
Command::Print(PrintOutput::Value(String::from("Hello, world"))),
);
let (_, result) = parse_line(input).unwrap(); let (_, result) = parse_line(input).unwrap();
assert_eq!(expected, result); assert_eq!(expected, result);
@ -279,7 +231,10 @@ mod tests {
#[test] #[test]
fn it_parses_a_print_command_with_escaped_quotes() { fn it_parses_a_print_command_with_escaped_quotes() {
let input = r#"10 PRINT "Hello, \"world\"""#; let input = r#"10 PRINT "Hello, \"world\"""#;
let expected = (10, Command::Print(PrintOutput::Value(String::from(r#"Hello, "world""#)))); let expected = (
10,
Command::Print(PrintOutput::Value(String::from(r#"Hello, "world""#))),
);
let (_, result) = parse_line(input).unwrap(); let (_, result) = parse_line(input).unwrap();
assert_eq!(expected, result); assert_eq!(expected, result);
@ -296,13 +251,19 @@ mod tests {
#[test] #[test]
fn it_can_create_a_linked_list_for_a_program() { fn it_can_create_a_linked_list_for_a_program() {
let mut node = Node::Link { let mut node = Node::Link {
item: (10, Command::Print(PrintOutput::Value(String::from("Hello world")))), item: (
10,
Command::Print(PrintOutput::Value(String::from("Hello world"))),
),
next: Box::new(Node::None), next: Box::new(Node::None),
}; };
node.push((20, Command::GoTo(10))); node.push((20, Command::GoTo(10)));
let expected = Node::Link { let expected = Node::Link {
item: (10, Command::Print(PrintOutput::Value(String::from("Hello world")))), item: (
10,
Command::Print(PrintOutput::Value(String::from("Hello world"))),
),
next: Box::new(Node::Link { next: Box::new(Node::Link {
item: (20, Command::GoTo(10)), item: (20, Command::GoTo(10)),
next: Box::new(Node::None), next: Box::new(Node::None),
@ -314,15 +275,27 @@ mod tests {
#[test] #[test]
fn it_finds_a_node_by_line_number() { fn it_finds_a_node_by_line_number() {
let mut node = Node::Link { let mut node = Node::Link {
item: (10, Command::Print(PrintOutput::Value(String::from("Hello world")))), item: (
10,
Command::Print(PrintOutput::Value(String::from("Hello world"))),
),
next: Box::new(Node::None), next: Box::new(Node::None),
}; };
node.push((20, Command::Print(PrintOutput::Value(String::from("I'm a second line"))))); node.push((
node.push((30, Command::Print(PrintOutput::Value(String::from("Still printing..."))))); 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))); node.push((40, Command::GoTo(10)));
let expected: Option<Node> = Some(Node::Link { let expected: Option<Node> = Some(Node::Link {
item: (30, Command::Print(PrintOutput::Value(String::from("Still printing...")))), item: (
30,
Command::Print(PrintOutput::Value(String::from("Still printing..."))),
),
next: Box::new(Node::Link { next: Box::new(Node::Link {
item: (40, Command::GoTo(10)), item: (40, Command::GoTo(10)),
next: Box::new(Node::None), next: Box::new(Node::None),
@ -336,7 +309,10 @@ mod tests {
fn it_reads_a_program() { fn it_reads_a_program() {
let lines = "10 PRINT \"Hello world\"\n20 GO TO 10"; let lines = "10 PRINT \"Hello world\"\n20 GO TO 10";
let expected_node = Node::Link { let expected_node = Node::Link {
item: (10, Command::Print(PrintOutput::Value(String::from("Hello world")))), item: (
10,
Command::Print(PrintOutput::Value(String::from("Hello world"))),
),
next: Box::new(Node::Link { next: Box::new(Node::Link {
item: (20, Command::GoTo(10)), item: (20, Command::GoTo(10)),
next: Box::new(Node::None), next: Box::new(Node::None),
@ -404,10 +380,7 @@ mod tests {
let (_, result) = parse_line(line).unwrap(); let (_, result) = parse_line(line).unwrap();
let expected: Line = ( let expected: Line = (
10, 10,
Command::Var(( Command::Var((String::from("a"), Primitive::Assignment(String::from("b$")))),
String::from("a"),
Primitive::Assignment(String::from("b$"))
))
); );
assert_eq!(result, expected); assert_eq!(result, expected);
} }
@ -418,7 +391,7 @@ mod tests {
let (_, result) = parse_line(line).unwrap(); let (_, result) = parse_line(line).unwrap();
let expected: Line = ( let expected: Line = (
10, 10,
Command::Print(PrintOutput::Variable(String::from("a$"))) Command::Print(PrintOutput::Variable(String::from("a$"))),
); );
assert_eq!(result, expected); assert_eq!(result, expected);
} }

View File

@ -1,6 +1,7 @@
use std::fs; use std::fs;
mod basic; mod basic;
mod parsers;
fn main() { fn main() {
let file = fs::read_to_string("./inputs/printing_program.bas").unwrap(); let file = fs::read_to_string("./inputs/printing_program.bas").unwrap();

2
src/parsers.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod generic;
pub mod variables;

25
src/parsers/generic.rs Normal file
View File

@ -0,0 +1,25 @@
use nom::{
branch::alt,
bytes::complete::{escaped_transform, tag, take_while},
character::complete::none_of,
combinator::value,
sequence::delimited,
IResult,
};
// Take everything until it hits a newline, if it does
pub fn consume_line(i: &str) -> IResult<&str, &str> {
take_while(|c| c != '\n')(i)
}
pub fn read_string(i: &str) -> IResult<&str, String> {
delimited(
tag("\""),
escaped_transform(
none_of("\\\""),
'\\',
alt((value("\\", tag("\\")), value("\"", tag("\"")))),
),
tag("\""),
)(i)
}

54
src/parsers/variables.rs Normal file
View File

@ -0,0 +1,54 @@
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{alphanumeric1, anychar, digit1, i64 as cci64},
combinator::{map, not, verify},
sequence::{preceded, terminated},
IResult,
};
use crate::basic::Primitive;
use super::generic::{consume_line, read_string};
pub fn parse_int_variable_name(i: &str) -> IResult<&str, String> {
map(preceded(not(digit1), alphanumeric1), String::from)(i)
}
pub fn parse_int(i: &str) -> IResult<&str, (String, Primitive)> {
let (i, id) = parse_int_variable_name(i)?;
let (i, _) = tag("=")(i)?;
let (i, var) = map(cci64, Primitive::Int)(i)?;
Ok((i, (id, var)))
}
pub 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))
}
pub fn parse_str(i: &str) -> IResult<&str, (String, Primitive)> {
let (i, id) = parse_str_variable_name(i)?;
let (i, _) = tag("=")(i)?;
let (i, var) = map(read_string, Primitive::String)(i)?;
Ok((i, (id, var)))
}
pub 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()),
),
))
}
pub fn parse_var(i: &str) -> IResult<&str, (String, Primitive)> {
alt((parse_int, parse_str, parse_assignment))(i)
}