angelmarkup/
lib.rs

1/*
2Angel Markup Language by Alexander Abraham,
3a.k.a. "Angeldust Duke" a.k.a. "The Black Unicorn".
4Licensed under the MIT license.
5*/
6
7use std::fmt;
8use regex::Regex;
9use std::fs::File;
10use std::fs::write;
11use colored::Colorize;
12use std::error::Error;
13use std::fs::read_to_string;
14use std::collections::HashMap;
15use serde_json::to_string_pretty;
16
17/// Converts a HashMap into a YAML string.
18pub fn to_yaml(subject:HashMap<String, String>) -> String {
19    let mut result_list: Vec<String> = Vec::new();
20    for (key, value) in subject.into_iter() {
21        let code_string: String = format!("{}: \"{}\"", key, value);
22        result_list.push(code_string);
23    }
24    let result: String = result_list.join("\n");
25    return result;
26}
27
28/// Converts a HashMap into a TOML string.
29pub fn to_toml(subject:HashMap<String, String>) -> String {
30    let mut result_list: Vec<String> = Vec::new();
31    for (key, value) in subject.into_iter() {
32        let code_string: String = format!("{} = \"{}\"", key, value);
33        result_list.push(code_string);
34    }
35    let result: String = result_list.join("\n");
36    return result;
37}
38
39/// Converts a HashMap into a Angelmarkup string.
40pub fn to_aml(subject:HashMap<String, String>) -> String {
41    let mut result_list: Vec<String> = Vec::new();
42    for (key, value) in subject.into_iter() {
43        let code_string: String = format!("\'{}\' => \'{}\'", key, value);
44        result_list.push(code_string);
45    }
46    let result: String = result_list.join("\n");
47    return result;
48}
49
50// Checks whether a file exists and
51/// returns a boolean to that effect.
52pub fn file_is(filename: String) -> bool {
53    let mut result: Vec<bool> = Vec::new();
54    let contents = read_to_string(filename);
55    match contents {
56        Ok(_n) => result.push(true),
57        Err(_x) => result.push(false)
58    }
59    return result[0];
60}
61
62/// Tries to read a file and return
63/// its contents.
64pub fn read_file(filename: String) -> String {
65    let mut result: String = String::from("");
66    let fname_copy: String = filename.clone();
67    if file_is(filename) == true {
68        result = read_to_string(fname_copy).unwrap();
69    }
70    else {}
71    return result;
72}
73
74/// Checks if "subject" has the index "index".
75pub fn has_index(subject: Vec<Token>, index: usize) -> bool {
76    let mut result: bool = false;
77    if index >= subject.len(){
78        result = true;
79    }
80    else {}
81    return result;
82}
83
84/// A struct to store and retrieve data
85/// about all lexed tokens.
86#[derive(Clone,Eq,PartialEq)]
87pub struct Token {
88    name: String,
89    value: String
90}
91
92/// Populates the [Token] struct with
93/// empty values for easier use.
94impl Default for Token {
95    fn default () -> Token {
96        Token {
97            name: String::from(""),
98            value: String::from("")
99        }
100    }
101}
102
103impl Token {
104    pub fn to_string(&self) -> String {
105        return format!("{} : {}", self.name, self.value);
106    }
107}
108
109/// A [HashMap] for tokens the lexer recognises.
110pub fn pattern_pool() -> HashMap<String, Regex>{
111    let mut pool: HashMap<String, Regex> = HashMap::new();
112    pool.insert(String::from("ENTITY"), Regex::new(r"'(.*)'").unwrap());
113    pool.insert(String::from("ASSIGN"), Regex::new(r"(=>)").unwrap());
114    pool.insert(String::from("COMMENT"), Regex::new(r"%(.*)").unwrap());
115    return pool;
116}
117
118/// The actual lexing function: Iterates through all lines
119/// and then through all characters and builds a vector of tokens
120/// while doing so and finally returns this vector.
121pub fn lex(source_code: String) -> Vec<Token>{
122    let lines: Vec<String> = clean_split(source_code, String::from("\n"));
123    let mut result: Vec<Token> = Vec::new();
124    let pool: HashMap<String, Regex> = pattern_pool();
125    for line in lines {
126        let char_list: Vec<String> = clean_split(line, String::from(""));
127        let mut new_char_list: Vec<String> = Vec::new();
128        for char_item in char_list {
129            new_char_list.push(char_item);
130            let collected_chars: String = new_char_list.join("");
131            for (key,value) in pool.clone().into_iter() {
132                if value.is_match(&collected_chars) {
133                    new_char_list.clear();
134                    let captured = value.captures(&collected_chars).unwrap();
135                    let new_token: Token = Token {
136                        name: key,
137                        value: captured.get(1).unwrap().as_str().to_string()
138                    };
139                    result.push(new_token);
140                }
141                else {}
142            }
143        }
144    }
145    return result;
146}
147
148// Returns a vector of strings from a character split for a string.
149/// Both the string and split character have to be strings.
150pub fn clean_split(subject: String, split_char: String) -> Vec<String> {
151    let mut result: Vec<String> = Vec::new();
152    for item in subject.split(&split_char) {
153        let new_item: String = item.to_string();
154        result.push(new_item);
155    }
156    return result;
157}
158
159/// An error struct
160/// to catch Angelmarkup
161/// errors.
162#[derive(Debug)]
163pub struct AngelMarkupError {
164    details: String
165}
166
167/// Implements a method to instantiate the
168/// struct.
169impl AngelMarkupError {
170    fn new(msg: &str) -> AngelMarkupError {
171        AngelMarkupError{details: msg.to_string()}
172    }
173}
174
175/// Implements stuff for better display.
176impl fmt::Display for AngelMarkupError {
177    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178        write!(f,"{}",self.details)
179    }
180}
181
182/// Implements the error trait.
183impl Error for AngelMarkupError {
184    fn description(&self) -> &str {
185        &self.details
186    }
187}
188
189/// Serializes an AML string into a "HashMap".
190pub fn serialize(src: String) -> Result<HashMap<String, String>,AngelMarkupError> {
191    let lexed_tokens: Vec<Token> = lex(src);
192    let lexed_tokens_clone_one: Vec<Token> = lexed_tokens.clone();
193    let lexed_tokens_clone_two: Vec<Token> = lexed_tokens_clone_one.clone();
194    let lexed_tokens_clone_three: Vec<Token> = lexed_tokens_clone_two.clone();
195    let lexed_tokens_clone_four: Vec<Token> =  lexed_tokens_clone_three.clone();
196    let lexed_tokens_clone_five: Vec<Token> =  lexed_tokens_clone_four.clone();
197    let lexed_tokens_clone_six: Vec<Token> =  lexed_tokens_clone_five.clone();
198    let mut result: HashMap<String, String> = HashMap::new();
199    for (index,token) in lexed_tokens.into_iter().enumerate() {
200        if token.name == String::from("ASSIGN"){
201            let last_index: usize = index-1;
202            let next_index: usize = index+1;
203            if last_index < lexed_tokens_clone_three.len() && next_index < lexed_tokens_clone_four.len() {
204                let key: String = lexed_tokens_clone_five[last_index.clone()].clone().value;
205                let value: String = lexed_tokens_clone_six[next_index.clone()].clone().value;
206                result.insert(key,value);
207            }
208            else {
209                let msg: String = String::from("Syntax error detected!");
210                return Err(AngelMarkupError::new(&msg));
211            }
212        }
213        else if token.name == String::from("COMMENT") {}
214        else {}
215    }
216    if result.is_empty(){
217        let msg: String = String::from("Result is empty.");
218        return Err(AngelMarkupError::new(&msg));
219    }
220    else {}
221    Ok(result)
222}
223
224/// Lints your AML code.
225/// If everything is A-OK,
226/// "true" is returned.
227pub fn lint(src: String) -> bool {
228    let mut result: bool = false;
229    let match_op = serialize(src);
230    match match_op {
231        Ok(_x) => {
232            result = true;
233        },
234        Err(_e) => {}
235    };
236    return result;
237}
238
239// Tries to create a file and returns
240/// a boolean depending on whether the
241/// operation succeeded.
242pub fn create_file(filename: String) -> bool {
243    let mut result: Vec<bool> = Vec::new();
244    let new_file = File::create(filename);
245    match new_file {
246        Ok(_n) => result.push(true),
247        Err(_x) => result.push(false)
248    }
249    return result[0];
250}
251
252/// Tries to write to a file and returns
253/// a boolean depending on whether the
254/// operation succeeded.
255pub fn write_to_file(filename: String, contents: String) -> bool {
256    let mut result: Vec<bool> = Vec::new();
257    let fname_copy: String = filename.clone();
258    if file_is(filename) == true {
259        let write_op = write(fname_copy, contents);
260        match write_op {
261            Ok(_n) => result.push(true),
262            Err(_x) => result.push(false)
263        }
264    }
265    return result[0];
266}
267
268/// Compiles an AML file to a JSON file.
269pub fn compile_to_json(src: String, target: String) {
270    let src_clone_one: String = src.clone();
271    let src_clone_two: String = src_clone_one.clone();
272    let target_clone_one: String = target.clone();
273    let target_clone_two: String = target_clone_one.clone();
274    if lint(read_file(src_clone_one)) == true {
275        let json_string: String = to_string_pretty(&serialize(read_file(src_clone_two)).unwrap()).unwrap();
276        create_file(target_clone_one);
277        write_to_file(target_clone_two, json_string);
278    }
279    else {
280        let msg: String = format!("An error occurred while parsing your Angelmarkup file.").red().to_string();
281        println!("{}", msg);
282    }
283}
284
285/// Compiles an AML file to a YAML file.
286pub fn compile_to_yaml(src: String, target: String) {
287    let src_clone_one: String = src.clone();
288    let src_clone_two: String = src_clone_one.clone();
289    let target_clone_one: String = target.clone();
290    let target_clone_two: String = target_clone_one.clone();
291    if lint(read_file(src_clone_one)) == true {
292        let yml_string: String = to_yaml(serialize(read_file(src_clone_two)).unwrap());
293        create_file(target_clone_one);
294        write_to_file(target_clone_two, yml_string);
295    }
296    else {
297        let msg: String = format!("An error occurred while parsing your Angelmarkup file.").red().to_string();
298        println!("{}", msg);
299    }
300}
301
302/// Compiles an AML file to a TOML file.
303pub fn compile_to_toml(src: String, target: String) {
304    let src_clone_one: String = src.clone();
305    let src_clone_two: String = src_clone_one.clone();
306    let target_clone_one: String = target.clone();
307    let target_clone_two: String = target_clone_one.clone();
308    if lint(read_file(src_clone_one)) == true {
309        let toml_string: String = to_toml(serialize(read_file(src_clone_two)).unwrap());
310        create_file(target_clone_one);
311        write_to_file(target_clone_two, toml_string);
312    }
313    else {
314        let msg: String = format!("An error occurred while parsing your Angelmarkup file.").red().to_string();
315        println!("{}", msg);
316    }
317}