feat: compressing files, converting files

and error message handling
This commit is contained in:
Artemy 2022-08-17 14:21:21 +03:00
parent 30150961a9
commit 1da96d7704
4 changed files with 185 additions and 33 deletions

View file

@ -21,5 +21,7 @@ serde = { version = "1.0", features = ["derive"] }
json5 = "0.4.1" json5 = "0.4.1"
serde_json = "1.0" serde_json = "1.0"
serde_yaml = "0.9" serde_yaml = "0.9"
rmp-serde = "1.1.0"
clap = { version = "3.2", features = ["derive"] } clap = { version = "3.2", features = ["derive"] }
colored = "2" colored = "2"

View file

@ -74,5 +74,4 @@
// "21! == 51 090 942 171 709 440 000: ", // "21! == 51 090 942 171 709 440 000: ",
// { _eq: [{ fact: [21] }, 51090942171709440000] }, // { _eq: [{ fact: [21] }, 51090942171709440000] },
// ], some json and yaml troubles with number `51090942171709440000` // ], some json and yaml troubles with number `51090942171709440000`
{ calc: ["operand", "+", "operand"] }
] ]

View file

@ -3,10 +3,13 @@ use colored::*;
use json5; use json5;
use serde_json::{json, Map, Value}; use serde_json::{json, Map, Value};
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, Write}; use std::io::{self, Write};
use std::{thread, time}; use std::path::Path;
use std::{fs, thread, time};
pub struct Interpreter { pub struct Interpreter {
input: String, commands: Vec<Value>,
vars: HashMap<String, Var>, vars: HashMap<String, Var>,
pos: usize, pos: usize,
scope: usize, scope: usize,
@ -14,32 +17,159 @@ pub struct Interpreter {
} }
impl Interpreter { impl Interpreter {
pub fn new(input: String) -> Self { pub fn new(file_path: String) -> Self {
Self { match Path::new(&file_path)
input, .extension()
vars: HashMap::new(), .and_then(OsStr::to_str)
pos: 1, .expect("The file must have the extension (.yaml, .json, .json5, .onla or .conla)")
scope: 0, {
scopes: Vec::new(), "yaml" => {
let file_input = fs::read_to_string(&file_path).expect("File reading error");
let obj: serde_json::Value =
serde_yaml::from_str(&file_input).unwrap_or_else(|x| {
match x.location() {
Some(location) => {
eprintln!(
"{file_path}:{}:{} --> {x}",
location.column(),
location.line()
);
}
None => {
eprintln!("{}", x);
}
}
std::process::exit(1);
});
let commands = obj.as_array().unwrap_or_else(|| {
obj.get("main")
.expect("Each program must contain a `{main: [..commands]}` object or be a command array ([..commands])")
.as_array()
.expect("The program must be an array of commands")
});
Self {
commands: commands.clone(),
vars: HashMap::new(),
pos: 1,
scope: 0,
scopes: Vec::new(),
}
}
"conla" => {
let file_input = File::open(file_path).expect("File reading error");
let obj: serde_json::Value = rmp_serde::from_read(file_input)
.expect(".conla file (MessagePack) is invalid! ");
let commands = obj.as_array().unwrap_or_else(|| {
obj.get("main")
.expect("Each program must contain a `{main: [..commands]}` object or be a command array ([..commands])")
.as_array()
.expect("The program must be an array of commands")
});
Self {
commands: commands.clone(),
vars: HashMap::new(),
pos: 1,
scope: 0,
scopes: Vec::new(),
}
}
_ => {
let file_input = fs::read_to_string(&file_path).expect("File reading error");
let obj: serde_json::Value =
json5::from_str::<Value>(&file_input).unwrap_or_else(|x| {
eprintln!("{file_path}{x}");
std::process::exit(1);
});
let commands = obj.as_array().unwrap_or_else(|| {
obj.get("main")
.expect("Each program must contain a `{main: [..commands]}` object or be a command array ([..commands])")
.as_array()
.expect("The program must be an array of commands")
});
Self {
commands: commands.clone(),
vars: HashMap::new(),
pos: 1,
scope: 0,
scopes: Vec::new(),
}
}
} }
} }
pub fn run(&mut self) { pub fn compress(&mut self, output_path: String) {
let obj: serde_json::Value = json5::from_str::<Value>(&self.input).unwrap_or_else(|_| { let mut output = File::create(output_path).expect("Failed to create output file");
serde_yaml::from_str(&self.input) output
.expect("Your file format is invalid! (supported: json, json5 or yaml)") .write_all(
}); &rmp_serde::encode::to_vec(&self.commands)
let arr = obj.as_array().unwrap_or_else(|| { .expect("Error when compressing onlang to .conla"),
obj.get("main") )
.expect("Each program must contain a `{main: [..commands]}` object or be a command array ([..commands])") .expect("Error when writing to file");
.as_array() println!("Compressed");
.expect("The program must be an array of commands") }
});
pub fn convert(&self, format: String, output_path: String) {
match format.as_str() {
"yaml" => {
self.convert_to_yaml(output_path);
}
"json" => {
self.convert_to_json(output_path);
}
"json5" => {
self.convert_to_json5(output_path);
}
_ => {
self.error("The conversion format is not supported");
}
}
}
fn convert_to_yaml(&self, output_path: String) {
let mut output = File::create(output_path).expect("Failed to create output file");
write!(
output,
"{}",
serde_yaml::to_string(&self.commands).expect("Error when convert to yaml")
)
.expect("Error when writing to file");
println!("Converted");
}
fn convert_to_json(&self, output_path: String) {
let mut output = File::create(output_path).expect("Failed to create output file");
write!(
output,
"{}",
serde_json::to_string(&self.commands).expect("Error when convert to json")
)
.expect("Error when writing to file");
println!("Converted");
}
fn convert_to_json5(&self, output_path: String) {
let mut output = File::create(output_path).expect("Failed to create output file");
write!(
output,
"{}",
json5::to_string(&self.commands).expect("Error when convert to json5")
)
.expect("Error when writing to file");
println!("Converted");
}
pub fn run(&mut self) {
self.scopes.push(Vec::new()); self.scopes.push(Vec::new());
for command in arr { let length = self.commands.len();
for i in 0..length {
let command = &self.commands[i].clone();
self.eval_node(command); self.eval_node(command);
self.pos += 1; self.pos = i;
} }
} }

View file

@ -1,40 +1,61 @@
use clap::Parser; use clap::Parser;
use std::fs;
use std::time::Instant; use std::time::Instant;
mod types; mod types;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
struct Args { struct Args {
file: String, path: String,
#[clap(short, long)] #[clap(short, long)]
verbose: bool, verbose: bool,
#[clap(long)]
compress: bool,
#[clap(long)]
convert: Option<String>,
#[clap(short, long)]
out: Option<String>,
} }
mod interpreter; mod interpreter;
use interpreter::Interpreter; use interpreter::Interpreter;
fn main() { fn main() {
#[cfg(not(debug_assertions))] // #[cfg(not(debug_assertions))]
std::panic::set_hook(Box::new(|info| { std::panic::set_hook(Box::new(|info| {
eprint!( eprint!(
"{msg}", "{}",
msg = match info.payload().downcast_ref::<String>() { match info.payload().downcast_ref::<String>() {
None => "Program panicked without a message!", None => "Program panicked without a message!",
Some(x) => x, Some(x) => x,
} }
); )
})); }));
let start = Instant::now(); let start = Instant::now();
let args = Args::parse(); let args = Args::parse();
if args.verbose == true { if args.verbose == true {
println!("Running: {}\n", args.file); println!("Running: {}\n", args.path);
} }
let file_input = fs::read_to_string(args.file).expect("File reading error");
let mut onint = Interpreter::new(file_input);
onint.run(); let mut onint = Interpreter::new(args.path);
match args.out {
Some(output_path) => {
if args.compress {
onint.compress(output_path);
} else if let Some(format) = args.convert {
onint.convert(format, output_path);
} else {
eprintln!("The file conversion format is not specified, use the flag: --compress")
}
}
None => {
onint.run();
}
}
if args.verbose == true { if args.verbose == true {
println!("\nElapsed: {:?}", start.elapsed()); println!("\nElapsed: {:?}", start.elapsed());