Skip to content

Commit 4ac78d5

Browse files
authored
Merge pull request #2 from ic-it/dev
simple check, run
2 parents 7e3175c + 003ee0b commit 4ac78d5

11 files changed

+353
-26
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ edition = "2021"
77

88
[dependencies]
99
clap = { version = "4.2.1", features = ["derive"] }
10-
ram = { git = "https://github.com/AVO-cado-team/ramemu.git", tag = "0.1.2"}
10+
colored = "2.0.0"
11+
ramemu = { git = "https://github.com/AVO-cado-team/ramemu.git", tag = "0.1.3"}

src/cli.rs

+14-23
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
11
use clap::{Parser, Subcommand, ValueHint};
22
use std::path::PathBuf;
33

4-
#[derive(Parser, Debug)] // requires `derive` feature
5-
#[command(author, version, about, long_about = None)]
4+
#[derive(Parser, Debug)]
5+
#[command(author, about = "CLI for ramemu (Random Access Machine Emulator)", version, long_about = None)]
66
pub struct Cli {
7-
// Subcommand to check file
87
#[clap(subcommand)]
9-
subcommand: Subcommands,
8+
pub subcommand: Subcommands,
109
}
1110

1211
#[derive(Subcommand, Debug)]
1312
pub enum Subcommands {
1413
Check {
1514
#[clap(value_hint(ValueHint::DirPath))]
1615
file: PathBuf,
17-
18-
#[clap(value_hint(ValueHint::DirPath), short, long)]
19-
#[arg(value_parser = clap::value_parser!(PathBuf))]
20-
input: Option<PathBuf>,
21-
22-
#[clap(value_hint(ValueHint::DirPath), short, long)]
23-
#[arg(value_parser = clap::value_parser!(PathBuf))]
24-
output: Option<PathBuf>,
2516
},
2617
Run {
2718
#[clap(value_hint(ValueHint::DirPath))]
@@ -35,16 +26,16 @@ pub enum Subcommands {
3526
#[arg(value_parser = clap::value_parser!(PathBuf))]
3627
output: Option<PathBuf>,
3728
},
38-
Compile {
39-
#[clap(value_hint(ValueHint::DirPath))]
40-
file: PathBuf,
29+
// Compile {
30+
// #[clap(value_hint(ValueHint::DirPath))]
31+
// file: PathBuf,
4132

42-
#[arg(default_value_t = false)]
43-
#[clap(long)]
44-
turing_machine: bool,
45-
},
46-
Debug {
47-
#[clap(value_hint(ValueHint::DirPath))]
48-
file: PathBuf,
49-
},
33+
// #[arg(default_value_t = false)]
34+
// #[clap(long)]
35+
// turing_machine: bool,
36+
// },
37+
// Debug {
38+
// #[clap(value_hint(ValueHint::DirPath))]
39+
// file: PathBuf,
40+
// },
5041
}

src/compile.rs

Whitespace-only changes.

src/create_program.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use crate::display_error::display_parsing_error;
2+
use crate::errors::RamCliError;
3+
use colored::Colorize;
4+
use ramemu::parser;
5+
use ramemu::program::Program;
6+
use ramemu::stmt::Stmt;
7+
use std::path::PathBuf;
8+
9+
pub fn create_program(source: &str, file_path: PathBuf) -> Result<Program, RamCliError> {
10+
let stmts_result: Vec<_> = parser::parse(source).collect();
11+
let errors: Vec<_> = stmts_result
12+
.iter()
13+
.filter_map(|stmt| stmt.as_ref().err().cloned())
14+
.collect();
15+
errors
16+
.iter()
17+
.try_for_each(|error| display_parsing_error(source, error))?;
18+
19+
if errors.is_empty() {
20+
let stmts: Vec<Stmt> = stmts_result
21+
.into_iter()
22+
.filter_map(|stmt| stmt.ok())
23+
.collect();
24+
return Ok(Program::from(stmts));
25+
}
26+
27+
Err(RamCliError::Parse(format!(
28+
"Found {} {} in file: {}",
29+
errors.len().to_string().bright_yellow().bold(),
30+
if errors.len() == 1 { "error" } else { "errors" },
31+
file_path
32+
.to_str()
33+
.expect("This should never happen")
34+
.bright_yellow()
35+
.bold()
36+
)))
37+
}

src/display_error.rs

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use crate::errors::RamCliError;
2+
use colored::Colorize;
3+
use ramemu::errors::InterpretError;
4+
use ramemu::errors::InvalidArgument;
5+
use ramemu::errors::ParseError;
6+
7+
fn get_line_from_source(source: &str, line_index: usize) -> Result<&str, RamCliError> {
8+
source
9+
.lines()
10+
.nth(line_index - 1)
11+
.ok_or_else(|| RamCliError::Other(format!("Could not find line {} in source", line_index)))
12+
}
13+
14+
fn display_error_message(
15+
source: &str,
16+
line_index: usize,
17+
message: &str,
18+
) -> Result<(), RamCliError> {
19+
let line = get_line_from_source(source, line_index)?;
20+
let (command, comment) = line.split_once('#').unwrap_or((line, ""));
21+
22+
let comment = if !comment.is_empty() {
23+
format!("#{}", comment).as_str().truecolor(100, 100, 100)
24+
} else {
25+
"".normal()
26+
};
27+
28+
println!("{}: {}", "Error".red().bold(), message);
29+
println!(
30+
"{}\t{} {}{}\n",
31+
line_index.to_string().bright_blue().bold(),
32+
'|'.to_string().bright_blue().bold(),
33+
command,
34+
comment
35+
);
36+
Ok(())
37+
}
38+
39+
pub fn display_parsing_error(source: &str, error: &ParseError) -> Result<(), RamCliError> {
40+
let (line_index, message) = match error {
41+
ParseError::LabelIsNotValid(line_index) => (
42+
*line_index,
43+
format!(
44+
"Label is not valid. Use only labels from {}:",
45+
"[a-zA-Z0-9_]+".bright_blue().bold()
46+
),
47+
),
48+
ParseError::UnsupportedSyntax(line_index) => (
49+
*line_index,
50+
"Unsupported syntax. Maybe you passed on an extra argument".to_string(),
51+
),
52+
ParseError::UnsupportedOpcode(line_index, opcode) => (
53+
*line_index,
54+
format!(
55+
"Unsupported opcode: {}",
56+
opcode.to_string().bright_blue().bold()
57+
),
58+
),
59+
ParseError::ArgumentIsRequired(line_index) => {
60+
(*line_index, "Argument is required".to_string())
61+
}
62+
ParseError::ArgumentIsNotValid(line_index, argument) => (
63+
*line_index,
64+
format!(
65+
"Argument is not valid: {}",
66+
match argument {
67+
InvalidArgument::LabelIsNotValid => "Label is not valid",
68+
InvalidArgument::ArgumentIsRequired => "Argument is required",
69+
InvalidArgument::ArgumentValueMustBeNumberic =>
70+
"Argument value must be numeric",
71+
InvalidArgument::PureArgumentIsNotAllowed =>
72+
"Pure argument is not allowed in this context",
73+
InvalidArgument::ArgumentIsNotValid => "Argument is not valid",
74+
}
75+
),
76+
),
77+
ParseError::UnknownError(line_index) => (*line_index, "Unknown error".to_string()),
78+
};
79+
80+
display_error_message(source, line_index, &message)?;
81+
Ok(())
82+
}
83+
84+
pub fn display_runtime_error(source: &str, error: &InterpretError) -> Result<(), RamCliError> {
85+
let (line_index, message) = match error {
86+
InterpretError::SegmentationFault(line_index) => (
87+
*line_index,
88+
"Segmentation fault. Look for possible reasons in the documentation".to_string(),
89+
),
90+
InterpretError::UnknownLabel(line_index) => (
91+
*line_index,
92+
"Unknown label. Attempted to jump to unknown label".to_string(),
93+
),
94+
InterpretError::InvalidInput(line_index, input) => {
95+
(*line_index, format!("Invalid input: {}", input))
96+
}
97+
InterpretError::InvalidLiteral(line_index) => (
98+
*line_index,
99+
"Invalid literal. Attempted to use invalid literal value".to_string(),
100+
),
101+
InterpretError::DivisionByZero(line_index) => (
102+
*line_index,
103+
"Division by zero. Are you trying to divide by zero?".to_string(),
104+
),
105+
InterpretError::WriteError(line_index) => (
106+
*line_index,
107+
"Output stream error. Can I write in the specified output?".to_string(),
108+
),
109+
InterpretError::Halted(line_index) => (
110+
*line_index,
111+
"Halted! You have reached the end of the program. Don't try to continue execution!"
112+
.to_string(),
113+
),
114+
};
115+
display_error_message(source, line_index, &message)?;
116+
Ok(())
117+
}

src/errors.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use colored::Colorize;
2+
use std::error::Error;
3+
4+
#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
5+
pub enum RamCliError {
6+
Io(String),
7+
Parse(String),
8+
Runtime(String),
9+
Other(String),
10+
}
11+
12+
impl std::fmt::Display for RamCliError {
13+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
14+
let (kind, message) = match &self {
15+
RamCliError::Io(message) => ("IO error", message),
16+
RamCliError::Parse(message) => ("Parse error", message),
17+
RamCliError::Runtime(message) => ("Runtime error", message),
18+
RamCliError::Other(message) => ("Unknown error", message),
19+
};
20+
write!(f, "{}: {}", kind.red().bold(), message)
21+
}
22+
}
23+
24+
impl Error for RamCliError {}

src/io_manager.rs

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use colored::Colorize;
2+
use std::fs::{canonicalize, read_to_string, File};
3+
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Result as IoResult, Write};
4+
use std::path::PathBuf;
5+
6+
use crate::errors::RamCliError;
7+
8+
struct RamCliStdin;
9+
struct RamCliStdout;
10+
11+
impl Read for RamCliStdin {
12+
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
13+
print!("{}", ">>> ".cyan().bold());
14+
stdout().flush().unwrap();
15+
stdin().read(buf)
16+
}
17+
}
18+
19+
impl Write for RamCliStdout {
20+
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
21+
if buf == b"\n" || buf == b"\r" {
22+
println!();
23+
return Ok(buf.len());
24+
}
25+
print!("{} {}", "<<<".cyan().bold(), String::from_utf8_lossy(buf));
26+
Ok(buf.len())
27+
}
28+
29+
fn flush(&mut self) -> IoResult<()> {
30+
stdout().flush()
31+
}
32+
}
33+
34+
fn ramcli_stdin() -> RamCliStdin {
35+
RamCliStdin
36+
}
37+
38+
fn ramcli_stdout() -> RamCliStdout {
39+
RamCliStdout
40+
}
41+
42+
pub fn read_source(file: PathBuf) -> Result<String, RamCliError> {
43+
let file_path_str = if let Some(fps) = file.to_str() {
44+
fps
45+
} else {
46+
return Err(RamCliError::Io(
47+
"You specified a file with an invalid path".to_string(),
48+
));
49+
};
50+
51+
let abs_path = canonicalize(&file)
52+
.map_err(|_| RamCliError::Io(format!("File not found: {}", file_path_str)))?;
53+
54+
if !abs_path.is_file() {
55+
return Err(RamCliError::Io(format!(
56+
"You must specify a file, not a directory: {}",
57+
file_path_str
58+
)));
59+
}
60+
61+
let source = read_to_string(abs_path)
62+
.map_err(|_| RamCliError::Io(format!("Could not read file: {}", file_path_str)))?;
63+
64+
Ok(source)
65+
}
66+
67+
pub fn create_input_buf(input: Option<PathBuf>) -> Result<Box<dyn BufRead>, RamCliError> {
68+
if let Some(input) = input {
69+
let input_file = File::open(input)
70+
.map_err(|_| RamCliError::Io("Could not open input file".to_string()))?;
71+
Ok(Box::new(BufReader::new(input_file)))
72+
} else {
73+
Ok(Box::new(BufReader::new(ramcli_stdin())))
74+
}
75+
}
76+
77+
pub fn create_output_buf(output: Option<PathBuf>) -> Result<Box<dyn std::io::Write>, RamCliError> {
78+
if let Some(output) = output {
79+
let output_file = File::create(output)
80+
.map_err(|_| RamCliError::Io("Could not open output file".to_string()))?;
81+
Ok(Box::new(BufWriter::new(output_file)))
82+
} else {
83+
Ok(Box::new(ramcli_stdout()))
84+
}
85+
}

src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub mod cli;
2+
pub mod create_program;
3+
pub mod display_error;
4+
pub mod errors;
5+
pub mod io_manager;
6+
pub mod run;

src/main.rs

+40-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,45 @@
1-
mod cli;
21
use clap::Parser;
3-
use cli::Cli;
2+
use colored::Colorize;
3+
use ram_cli::cli::{Cli, Subcommands};
4+
use ram_cli::create_program::create_program;
5+
use ram_cli::io_manager::read_source;
6+
use ram_cli::run::run_source;
47

58
fn main() {
69
let args = Cli::parse();
10+
11+
match args.subcommand {
12+
Subcommands::Check { file } => {
13+
let source = match read_source(file.clone()) {
14+
Ok(s) => s,
15+
Err(e) => return println!("{}", e),
16+
};
17+
18+
let Err(e) = create_program(&source, file) else {
19+
return println!("{}: No errors found {}", "Syntax analysis".cyan().bold(), "✓".green().bold())
20+
};
21+
println!("{}", e);
22+
}
23+
24+
Subcommands::Run {
25+
file,
26+
input,
27+
output,
28+
} => {
29+
let source = match read_source(file.clone()) {
30+
Ok(s) => s,
31+
Err(e) => return println!("{}", e),
32+
};
33+
34+
let program = match create_program(&source, file) {
35+
Ok(program) => program,
36+
Err(e) => return println!("{}", e),
37+
};
38+
39+
let Err(e) = run_source(&source, program, input, output) else {
40+
return println!("{}: Program finished with no errors {}", "Runtime".cyan().bold(), "✓".green().bold())
41+
};
42+
println!("{}", e);
43+
}
44+
}
745
}

0 commit comments

Comments
 (0)