use std::collections::HashMap;
use regex::Regex;
#[derive(Debug)]
pub enum Tokens {
Null,
Boolean(bool),
String(String),
Table(HashMap<String, Tokens>)
}
pub fn parse(ckt: &String) -> Result<HashMap<String, Tokens>, String> {
return run(ckt, 0, false).0
}
fn run(ckt: &String, offset: usize, spawned: bool) -> (Result<HashMap<String, Tokens>, String>,usize) {
let mut cfg = HashMap::new();
let mut tableindex = 0;
let commentslurper = Regex::new(r"(?m)^\s*#.*").unwrap();
let appr = commentslurper.replace_all(ckt, "");
let mut skipto = offset;
let mut reading = false;
let mut quoted = false;
let mut backslashed = false;
let mut assigning = false;
let mut trimval = true;
let mut multiline = false;
let mut multikey = "".to_string();
let mut buffer = Vec::new();
let mut key = Vec::new();
for (i, c) in appr.chars().enumerate() {
if skipto > i { continue }
if c == '\\' {
if backslashed {
if reading {
buffer.push('\\');
}
} else {
backslashed = true; }
continue;
}
if !reading {
if c == ' ' || c == '\t' || c == '\n' {
continue; } else {
if c == '"' {
quoted = true; reading = true;
if assigning {
trimval = false;
}
multiline = false;
multikey = "".to_string();
} else if c == '[' {
let res = run(ckt, i+1, true);
skipto = res.1;
multiline = false;
multikey = "".to_string();
if assigning {
let val = match res.0 {
Ok(v) => {
Tokens::Table(v)
}
Err(e) => {
return (Err(e.clone()),0);
}
};
cfg.insert(key.iter().collect::<String>().trim().to_string(), val);
key = Vec::new();
buffer = Vec::new();
assigning = false;
} else {
let val = match res.0 {
Ok(v) => {
Tokens::Table(v)
}
Err(e) => {
return (Err(e.clone()),0);
}
};
cfg.insert(tableindex.to_string(), val);
tableindex += 1;
buffer = Vec::new();
}
} else if c == ']' {
if spawned {
return (Ok(cfg),i+1);
}
} else if c == '|' {
if multiline {
reading = true;
} else {
multiline = true;
reading = true;
}
} else {
buffer.push(c);
reading = true;
multiline = false;
multikey = "".to_string();
}
}
} else {
if c == '=' {
reading = false;
assigning = true;
if multiline {
return (Err("Cannot use multiline string as key".to_string()),0);
}
key = buffer.clone(); buffer = Vec::new();
}
if c == 'n' && backslashed {
buffer.push('\n');
}
if c == '"' {
if quoted {
if backslashed {
buffer.push('"');
backslashed = false;
} else {
quoted = false;
}
} else {
buffer.push('"');
}
}
if c == '\n' || c == ';' || c == ']' || c == ',' {
if c == '\n' || c == ';' || c == ',' { reading = false; }
if backslashed && c == '\n' {
return (Err("Cannot escape newline".to_string()),0);
}
if backslashed && (c == ';' || c == ']' || c == ',') {
buffer.push(c);
backslashed = false;
}
if quoted && c == '\n' {
return (Err("Expected end quote before linebreak".to_string()),0);
}
if quoted && (c == ';' || c == ']' || c == ',') {
buffer.push(c);
}
if assigning {
if buffer.len() == 0 {
return (Err("Expected value after assignment operator (=)".to_string()),0);
} else {
let mut v: String = buffer.iter().collect();
let k = key.iter().collect::<String>();
if multiline {
if multikey == "" {
multikey = k.trim().to_string();
} else {
let oldv = match cfg.get(&multikey) {
Some(a) => match a {
Tokens::String(o) => o.to_owned(),
Tokens::Null => "null".to_string(),
Tokens::Boolean(o) => if o.to_owned() { "true".to_string() } else { "false".to_string() },
Tokens::Table(_o) => {
return (Err("Something horrible has happened".to_string()),0);
}
}
None => "".to_string()
};
v = if buffer.first().unwrap() == &']' && multiline { format!("{}{}",oldv,v) } else { format!("{}\n{}",oldv,v) }
}
}
if trimval {
v = v.trim().to_string();
}
let t = if v == "true" {
Tokens::Boolean(true)
} else if v == "false" {
Tokens::Boolean(false)
} else if v == "null" {
Tokens::Null
} else {
Tokens::String(v)
};
if multikey == "" {
cfg.insert(key.iter().collect::<String>().trim().to_string(), t);
} else {
cfg.insert(multikey.clone(), t);
}
buffer = Vec::new();
key = Vec::new();
assigning = false;
}
} else {
let mut v: String = buffer.iter().collect();
let k = tableindex.to_string();
if multiline {
if multikey == "" {
multikey = k;
} else {
let oldv = match cfg.get(&multikey) {
Some(a) => match a {
Tokens::String(o) => o.to_owned(),
Tokens::Null => "null".to_string(),
Tokens::Boolean(o) => if o.to_owned() { "true".to_string() } else { "false".to_string() },
Tokens::Table(_o) => {
return (Err("Something horrible has happened".to_string()),0);
}
}
None => "".to_string()
};
v = if buffer.first().unwrap() == &']' && multiline { format!("{}{}",oldv,v) } else { format!("{}\n{}",oldv,v) }
}
}
if trimval {
v = v.trim().to_string();
}
let t = if v == "true" {
Tokens::Boolean(true)
} else if v == "false" {
Tokens::Boolean(false)
} else if v == "null" {
Tokens::Null
} else {
Tokens::String(v)
};
if multikey == "" {
cfg.insert(tableindex.to_string(), t);
tableindex += 1;
} else {
cfg.insert(multikey.clone(), t);
}
buffer = Vec::new();
}
if c == ']' {
if multiline && reading {
reading = true;
} else {
if spawned {
return (Ok(cfg),i+1);
}
}
}
}
if c != '=' && c != '"' && c != '\n' && c != ';' && c != ',' {
if !backslashed {
buffer.push(c); } else {
backslashed = false;
}
}
}
}
if spawned {
return (Err("Expected closing bracket".to_string()),0);
}
(Ok(cfg),0)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn exampletest() {
let unparsed_file = fs::read_to_string("test.ckt").expect("cannot read file");
let file = parse(&unparsed_file)
.expect("unsuccessful parse");
for (key, value) in file.into_iter() {
println!("{}: {:?}", key, value);
}
}
}