From 1da96d77040e2f7f3a5170751f41a23763495740 Mon Sep 17 00:00:00 2001 From: Artemy Date: Wed, 17 Aug 2022 14:21:21 +0300 Subject: [PATCH] feat: compressing files, converting files and error message handling --- Cargo.toml | 2 + examples/factorial.onla | 1 - src/interpreter.rs | 174 +++++++++++++++++++++++++++++++++++----- src/main.rs | 41 +++++++--- 4 files changed, 185 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fe697c0..ad5f5fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,5 +21,7 @@ serde = { version = "1.0", features = ["derive"] } json5 = "0.4.1" serde_json = "1.0" serde_yaml = "0.9" +rmp-serde = "1.1.0" + clap = { version = "3.2", features = ["derive"] } colored = "2" \ No newline at end of file diff --git a/examples/factorial.onla b/examples/factorial.onla index a626bd0..8f57383 100644 --- a/examples/factorial.onla +++ b/examples/factorial.onla @@ -74,5 +74,4 @@ // "21! == 51 090 942 171 709 440 000: ", // { _eq: [{ fact: [21] }, 51090942171709440000] }, // ], some json and yaml troubles with number `51090942171709440000` - { calc: ["operand", "+", "operand"] } ] diff --git a/src/interpreter.rs b/src/interpreter.rs index e83093b..b37bafb 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -3,10 +3,13 @@ use colored::*; use json5; use serde_json::{json, Map, Value}; use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs::File; use std::io::{self, Write}; -use std::{thread, time}; +use std::path::Path; +use std::{fs, thread, time}; pub struct Interpreter { - input: String, + commands: Vec, vars: HashMap, pos: usize, scope: usize, @@ -14,32 +17,159 @@ pub struct Interpreter { } impl Interpreter { - pub fn new(input: String) -> Self { - Self { - input, - vars: HashMap::new(), - pos: 1, - scope: 0, - scopes: Vec::new(), + pub fn new(file_path: String) -> Self { + match Path::new(&file_path) + .extension() + .and_then(OsStr::to_str) + .expect("The file must have the extension (.yaml, .json, .json5, .onla or .conla)") + { + "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::(&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) { - let obj: serde_json::Value = json5::from_str::(&self.input).unwrap_or_else(|_| { - serde_yaml::from_str(&self.input) - .expect("Your file format is invalid! (supported: json, json5 or yaml)") - }); - let arr = 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") - }); + pub fn compress(&mut self, output_path: String) { + let mut output = File::create(output_path).expect("Failed to create output file"); + output + .write_all( + &rmp_serde::encode::to_vec(&self.commands) + .expect("Error when compressing onlang to .conla"), + ) + .expect("Error when writing to file"); + println!("Compressed"); + } + 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()); - 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.pos += 1; + self.pos = i; } } diff --git a/src/main.rs b/src/main.rs index b746f52..28b67b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,40 +1,61 @@ use clap::Parser; -use std::fs; use std::time::Instant; mod types; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { - file: String, + path: String, #[clap(short, long)] verbose: bool, + + #[clap(long)] + compress: bool, + + #[clap(long)] + convert: Option, + + #[clap(short, long)] + out: Option, } mod interpreter; use interpreter::Interpreter; fn main() { - #[cfg(not(debug_assertions))] + // #[cfg(not(debug_assertions))] std::panic::set_hook(Box::new(|info| { eprint!( - "{msg}", - msg = match info.payload().downcast_ref::() { + "{}", + match info.payload().downcast_ref::() { None => "Program panicked without a message!", Some(x) => x, } - ); + ) })); let start = Instant::now(); let args = Args::parse(); 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 { println!("\nElapsed: {:?}", start.elapsed());