pub mod error;
pub mod indention;
mod line_parser;
#[cfg(test)]
mod tests;
pub mod value;
use std::collections::HashMap;
use error::{ParserError, ParserErrorKind};
use indention::Indention;
use line_parser::LineParser;
use value::Value;
use crate::value::PrimitiveValue;
pub type ParserResult<T> = Result<T, ParserError>;
struct ObjectContext {
indent: usize,
key: String,
values: HashMap<String, Value>,
}
impl ObjectContext {
fn consume_context(&mut self, context: Context) {
match context {
Context::Object(ctx_obj) => {
self.values
.insert(ctx_obj.key, Value::Object(ctx_obj.values));
}
Context::Array(ctx_arr) => {
if ctx_arr.key.len() == 0 {
panic!("error - this should never happen");
}
self.values
.insert(ctx_arr.key, Value::Array(ctx_arr.values));
}
Context::MultiLineString(ctx_mls) => {
if ctx_mls.key.is_empty() {
panic!("error - this should never happen");
}
self.values.insert(
ctx_mls.key,
Value::Primitive(ctx_mls.lines.join("\n").into()),
);
}
}
}
}
struct ArrayContext {
indent: usize,
key: String,
values: Vec<Value>,
}
impl ArrayContext {
fn consume_context(&mut self, context: Context) {
match context {
Context::Object(ctx_obj) => {
let mut m = HashMap::new();
m.insert(ctx_obj.key, Value::Object(ctx_obj.values));
self.values.push(Value::Object(m));
}
Context::Array(ctx_arr) => {
if ctx_arr.key.len() != 0 {
todo!("error - but this should never happen");
}
self.values.push(Value::Array(ctx_arr.values));
}
Context::MultiLineString(ctx_mls) => {
if !ctx_mls.key.is_empty() {
todo!("this shouldn't happen");
}
self.values
.push(Value::Primitive(ctx_mls.lines.join("\n").into()));
}
}
}
}
struct MultiLineStringContext {
indent: usize,
key: String,
lines: Vec<String>,
}
enum Context {
Object(ObjectContext),
Array(ArrayContext),
MultiLineString(MultiLineStringContext),
}
impl Context {
fn empty_object_context(indent: usize, key: String) -> Context {
Context::Object(ObjectContext {
indent,
key,
values: HashMap::new(),
})
}
fn empty_multi_line_array_root_context(indent: usize, key: String) -> Context {
Context::Array(ArrayContext {
indent,
key,
values: Vec::new(),
})
}
fn empty_multi_line_array_context(indent: usize) -> Context {
Context::Array(ArrayContext {
indent,
key: String::new(),
values: Vec::new(),
})
}
fn empty_multi_line_string(indent: usize, key: String) -> Context {
Self::MultiLineString(MultiLineStringContext {
indent,
key,
lines: Vec::new(),
})
}
fn is_object_context(&self) -> bool {
matches!(self, Self::Object(..))
}
fn is_array_context(&self) -> bool {
matches!(self, Self::Array(..))
}
fn get_indent(&self) -> usize {
match self {
Self::Object(ctx_obj) => ctx_obj.indent,
Self::Array(ctx_arr) => ctx_arr.indent,
Self::MultiLineString(ctx_mls) => ctx_mls.indent,
}
}
fn get_objects(self) -> Result<HashMap<String, Value>, ()> {
match self {
Self::Object(ctx_obj) => Ok(ctx_obj.values),
_ => Err(()),
}
}
fn push_kv(&mut self, key: String, value: Value) {
match self {
Context::Object(v) => {
v.values.insert(key, value);
}
_ => todo!("error"),
}
}
fn push_v(&mut self, value: Value) {
match self {
Context::Array(v) => {
v.values.push(value);
}
_ => todo!("error"),
}
}
fn consume_context(&mut self, context: Context) {
match self {
Self::Object(ctx_obj) => ctx_obj.consume_context(context),
Self::Array(arr_obj) => arr_obj.consume_context(context),
_ => todo!("error"),
}
}
}
pub struct Parser {
line_number: usize,
indention: Option<Indention>,
context_stack: Vec<Context>,
}
impl Parser {
pub fn new() -> Self {
let root_context = Context::empty_object_context(0, String::new());
Self {
line_number: 0,
indention: None,
context_stack: vec![root_context],
}
}
fn calculate_indent(
&mut self,
line_parser: &LineParser,
tabs_count: usize,
spaces_count: usize,
) -> ParserResult<usize> {
if tabs_count > 0 || spaces_count > 0 {
if tabs_count > 0 && spaces_count > 0 {
return Err(line_parser.generate_error(ParserErrorKind::MixedTabsAndSpaces));
}
if let Some(indention) = &self.indention {
match indention {
Indention::Tabs => {
if spaces_count > 0 {
return Err(line_parser.generate_error(
ParserErrorKind::InconsistentIndention(
indention.clone(),
Indention::Spaces(spaces_count),
),
));
} else if tabs_count > 0 {
Ok(tabs_count)
} else {
todo!("error - this should never happen");
}
}
Indention::Spaces(spaces) => {
if spaces_count > 0 {
if spaces_count % spaces == 0 {
return Err(line_parser
.generate_error(ParserErrorKind::SpacesNotMultipleOfIndent));
} else {
Ok(spaces_count / spaces)
}
} else if tabs_count > 0 {
return Err(line_parser.generate_error(
ParserErrorKind::InconsistentIndention(
indention.clone(),
Indention::Tabs,
),
));
} else {
todo!("error - this should never happen");
}
}
}
} else {
if spaces_count > 0 {
self.indention = Some(Indention::Spaces(spaces_count));
}
if tabs_count > 1 {
return Err(line_parser.generate_error(ParserErrorKind::MultipleTabIndent));
}
self.indention = Some(Indention::Tabs);
Ok(1)
}
} else {
Ok(0)
}
}
fn pop_stack(&mut self) {
let context = self.context_stack.pop().unwrap();
self.context_stack
.last_mut()
.unwrap()
.consume_context(context);
}
fn collapse_context_to_indent(&mut self, indent: usize) {
while self
.context_stack
.last()
.map(|ctx| ctx.get_indent())
.unwrap() > indent
{
self.pop_stack();
}
}
pub fn collapse_context(&mut self) {
self.collapse_context_to_indent(0);
}
fn process_post_indent_object(
&mut self,
line_parser: &mut LineParser,
indent: usize,
) -> ParserResult<()> {
let key = line_parser.parse_key()?;
line_parser.consume_whitespaces();
if line_parser.have(":--") {
if !line_parser.see_end_or_comment() {
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
self.context_stack
.push(Context::empty_multi_line_array_root_context(
indent + 1,
key,
));
return Ok(());
}
if line_parser.have(":") {
line_parser.consume_whitespaces();
if let Some(value) = line_parser.parse_inline_array()? {
self.context_stack.last_mut().unwrap().push_kv(key, value);
if !line_parser.see_end_or_comment() {
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
} else if line_parser.see_end_or_comment() {
self.context_stack
.push(Context::empty_object_context(indent + 1, key));
} else if let Some(primitive) = line_parser.parse_primitive()? {
self.context_stack
.last_mut()
.unwrap()
.push_kv(key, Value::Primitive(primitive));
} else if line_parser.have("|") {
self.context_stack
.push(Context::empty_multi_line_string(indent + 1, key));
}
if line_parser.see_end_or_comment() {
return Ok(());
} else {
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
}
if !line_parser.see_end_or_comment() {
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
self.context_stack
.last_mut()
.unwrap()
.push_kv(key, Value::null());
Ok(())
}
fn process_post_indent_array(
&mut self,
line_parser: &mut LineParser,
indent: usize,
) -> ParserResult<()> {
if line_parser.have("--") {
if !line_parser.see_end_or_comment() {
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
self.context_stack
.push(Context::empty_multi_line_array_context(indent + 1));
return Ok(());
}
if !line_parser.have("-") {
return Err(line_parser.generate_error(ParserErrorKind::expected("-")));
}
line_parser.consume_whitespaces();
let key = line_parser.parse_key_with_colon()?;
if key.len() > 0 {
line_parser.consume_whitespaces();
if let Some(value) = line_parser.parse_inline_array()? {
self.context_stack
.last_mut()
.unwrap()
.push_v(Value::key_value_pair(key, value));
} else if let Some(value) = line_parser.parse_primitive()? {
self.context_stack
.last_mut()
.unwrap()
.push_v(Value::key_value_pair(key, value));
if !line_parser.see_end_or_comment() {
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
return Ok(());
} else {
if !line_parser.see_end_or_comment() {
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
self.context_stack
.push(Context::empty_object_context(indent + 1, key));
return Ok(());
}
}
if line_parser.have("|") {
self.context_stack
.push(Context::empty_multi_line_string(indent + 1, String::new()));
return Ok(());
}
loop {
line_parser.consume_whitespaces();
if line_parser.see_end_or_comment() {
break;
}
if let Some(value) = line_parser.parse_inline_array()? {
self.context_stack.last_mut().unwrap().push_v(value);
continue;
}
if let Some(primitive) = line_parser.parse_primitive()? {
self.context_stack
.last_mut()
.unwrap()
.push_v(Value::Primitive(primitive));
continue;
}
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
if !line_parser.see_end_or_comment() {
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
Ok(())
}
fn process_multi_line_string_line(
&mut self,
line_parser: &mut LineParser,
) -> ParserResult<bool> {
if let Context::MultiLineString(ctx) = self.context_stack.last_mut().unwrap() {
if let Some(indention) = self.indention {
if !line_parser.have_indentions(indention, ctx.indent) {
self.pop_stack();
return Ok(false);
}
} else {
if line_parser.have("\t") {
self.indention = Some(Indention::Tabs);
} else {
let (tabs_count, spaces_count) = line_parser.next_whitespaces();
if tabs_count > 0 && spaces_count > 0 {
return Err(line_parser.generate_error(ParserErrorKind::MixedTabsAndSpaces));
}
if spaces_count == 0 {
self.pop_stack();
return Ok(false);
}
self.indention = Some(Indention::Spaces(spaces_count));
}
}
ctx.lines.push(line_parser.consume_rest().to_string());
Ok(true)
} else {
Ok(false)
}
}
fn process_line(&mut self, line: &str) -> ParserResult<()> {
let mut line_parser = LineParser::new(self.line_number, line);
if self.process_multi_line_string_line(&mut line_parser)? {
return Ok(());
}
if line_parser.see_end_or_comment() {
return Ok(());
}
let (tabs_count, spaces_count) = line_parser.next_whitespaces();
let indent = self.calculate_indent(&line_parser, tabs_count, spaces_count)?;
let max_indent = match self.context_stack.last() {
Some(ctx) => ctx.get_indent(),
None => 0,
};
if indent > max_indent {
return Err(line_parser.generate_error(ParserErrorKind::InvalidIndention));
}
self.collapse_context_to_indent(indent);
if self.context_stack.last().unwrap().is_object_context() {
return self.process_post_indent_object(&mut line_parser, indent);
}
if self.context_stack.last().unwrap().is_array_context() {
return self.process_post_indent_array(&mut line_parser, indent);
}
Ok(())
}
pub fn next_line(&mut self, line: &str) -> ParserResult<()> {
self.process_line(line)?;
self.line_number += 1;
Ok(())
}
}
pub fn parse_string(s: &str) -> ParserResult<Value> {
let mut parser = Parser::new();
for line in s.lines() {
parser.next_line(line)?;
}
parser.collapse_context();
Ok(Value::Object(
parser
.context_stack
.into_iter()
.next()
.unwrap()
.get_objects()
.unwrap(),
))
}
pub fn encode_string_expanded(v: &Value, indention: Indention) -> String {
fn encode_indent(indent: usize, indent_str: &str, buf: &mut String) {
for _ in 0..indent {
buf.push_str(indent_str);
}
}
fn should_be_multi_line(s: &str) -> bool {
s.contains("'") | s.contains("'") | s.contains("\n")
}
fn encode_multi_line_string(indent: usize, indent_str: &str, s: &String, buf: &mut String) {
for line in s.lines() {
encode_indent(indent, indent_str, buf);
buf.push_str(line);
buf.push_str("\n");
}
}
fn encode_primitive(
indent: usize,
indent_str: &str,
primitive: &PrimitiveValue,
buf: &mut String,
) {
match primitive {
PrimitiveValue::Number(p) => buf.push_str(&p.to_string()),
PrimitiveValue::Boolean(p) => buf.push_str(&p.to_string()),
PrimitiveValue::String(s) => {
if should_be_multi_line(s) {
buf.push_str("|\n");
encode_multi_line_string(indent, indent_str, s, buf);
} else {
buf.push_str("'");
buf.push_str(s);
buf.push_str("'");
}
}
PrimitiveValue::Null => buf.push_str("null"),
}
}
fn encode_value(indent: usize, indent_str: &str, v: &Value, buf: &mut String) {
match v {
Value::Primitive(primitive) => {
encode_primitive(indent, indent_str, primitive, buf);
}
Value::Object(objects) => {
if indent > 0 {
buf.push_str("\n");
}
for (key, v) in objects {
encode_indent(indent, indent_str, buf);
buf.push_str(key);
buf.push_str(": ");
encode_value(indent + 1, indent_str, v, buf);
buf.push_str("\n");
}
}
Value::Array(values) => {
let mut multi_line = false;
for v in values {
match v {
Value::Primitive(primitive) => match primitive {
PrimitiveValue::String(s) => {
if should_be_multi_line(s) {
multi_line = true;
break;
}
}
_ => {}
},
_ => {
multi_line = true;
break;
}
}
}
if multi_line {
buf.push_str("--");
for v in values {
buf.push_str("\n");
encode_indent(indent, indent_str, buf);
encode_value(indent + 1, indent_str, v, buf);
}
} else {
buf.push_str("[");
let mut values = values.iter();
if let Some(v) = values.next() {
encode_value(indent, indent_str, v, buf);
for v in values {
buf.push_str(" ");
encode_value(indent, indent_str, v, buf);
}
}
buf.push_str("]");
}
}
}
}
let indention = match indention {
Indention::Tabs => "\t".to_string(),
Indention::Spaces(spaces) => (" ").repeat(spaces).to_string(),
};
let mut buf = String::new();
encode_value(0, &indention, v, &mut buf);
buf.lines()
.filter(|l| l.trim_start().len() > 0)
.collect::<Vec<_>>()
.join("\n")
}