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 ObjectContent {
pending_key: String,
values: HashMap<String, Value>,
}
struct ArrayContent {
values: Vec<Value>,
}
struct MultiLineStringContent {
lines: Vec<String>,
}
enum ContextContent {
Object(ObjectContent),
Array(ArrayContent),
MultiLineString(MultiLineStringContent),
}
struct Context {
indent: usize,
content: ContextContent,
}
impl Context {
fn object_context(indent: usize, pending_key: String) -> Context {
Self {
indent,
content: ContextContent::Object(ObjectContent {
pending_key,
values: HashMap::new(),
}),
}
}
fn array_context(indent: usize) -> Context {
Self {
indent,
content: ContextContent::Array(ArrayContent { values: vec![] }),
}
}
fn multi_line_string_context(indent: usize) -> Context {
Self {
indent,
content: ContextContent::MultiLineString(MultiLineStringContent { lines: vec![] }),
}
}
fn is_object_context(&self) -> bool {
matches!(self.content, ContextContent::Object(_))
}
fn is_array_context(&self) -> bool {
matches!(self.content, ContextContent::Array(_))
}
fn get_indent(&self) -> usize {
self.indent
}
fn get_objects(self) -> Result<HashMap<String, Value>, ()> {
match self.content {
ContextContent::Object(obj) => Ok(obj.values),
_ => Err(()),
}
}
fn set_pending_key(&mut self, pending_key: String) {
match &mut self.content {
ContextContent::Object(obj) => obj.pending_key = pending_key,
_ => panic!(),
}
}
fn push_v(&mut self, value: Value) {
match &mut self.content {
ContextContent::Object(obj) => {
let key = std::mem::replace(&mut obj.pending_key, String::new());
obj.values.insert(key, value);
}
ContextContent::Array(arr) => {
arr.values.push(value);
}
_ => panic!(),
}
}
fn push_kv(&mut self, key: String, value: Value) {
match &mut self.content {
ContextContent::Object(obj) => {
obj.pending_key = String::new();
obj.values.insert(key, value);
}
_ => panic!(),
}
}
fn to_value(self) -> Value {
match self.content {
ContextContent::Object(obj) => Value::Object(obj.values),
ContextContent::Array(arr) => Value::Array(arr.values),
ContextContent::MultiLineString(mls) => {
Value::Primitive(PrimitiveValue::String(mls.lines.join("\n")))
}
}
}
}
pub struct Parser {
line_number: usize,
indention: Option<Indention>,
context_stack: Vec<Context>,
}
impl Parser {
pub fn new() -> Self {
let root_context = Context::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()
.push_v(context.to_value());
}
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));
}
let last = self.context_stack.last_mut().unwrap();
last.set_pending_key(key);
self.context_stack.push(Context::array_context(indent + 1));
return Ok(());
}
if line_parser.have(":") {
line_parser.consume_whitespaces();
let last = self.context_stack.last_mut().unwrap();
last.set_pending_key(key);
if line_parser.see_end_or_comment() {
self.context_stack
.push(Context::object_context(indent + 1, String::new()));
return Ok(());
}
if let Some(value) = line_parser.parse_inline_array()? {
last.push_v(value);
} else if let Some(primitive) = line_parser.parse_primitive()? {
last.push_v(Value::Primitive(primitive));
} else if line_parser.have("|") {
self.context_stack
.push(Context::multi_line_string_context(indent + 1));
}
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::array_context(indent + 1));
return Ok(());
}
if !line_parser.have("-") {
return Err(line_parser.generate_error(ParserErrorKind::expected("-")));
}
line_parser.consume_whitespaces();
if line_parser.see_end_or_comment() {
self.context_stack
.push(Context::object_context(indent + 1, String::new()));
return Ok(());
}
let key = line_parser.parse_key_with_colon()?;
if key.len() > 0 {
line_parser.consume_whitespaces();
let last = self.context_stack.last_mut().unwrap();
if line_parser.see_end_or_comment() {
self.context_stack
.push(Context::object_context(indent + 1, key));
self.context_stack
.push(Context::object_context(indent + 1, String::new()));
return Ok(());
}
if let Some(value) = line_parser.parse_inline_array()? {
last.push_v(Value::key_value_pair(key, value));
} else if let Some(primitive) = line_parser.parse_primitive()? {
last.push_v(Value::key_value_pair(key, primitive));
} else if line_parser.have("|") {
self.context_stack
.push(Context::object_context(indent + 1, key));
self.context_stack
.push(Context::multi_line_string_context(indent + 1));
}
if line_parser.see_end_or_comment() {
return Ok(());
} else {
return Err(line_parser.generate_error(ParserErrorKind::UnexpectedCharacter));
}
}
if line_parser.have("|") {
self.context_stack
.push(Context::multi_line_string_context(indent + 1));
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> {
let last = self.context_stack.last_mut().unwrap();
let indent = last.get_indent();
if let ContextContent::MultiLineString(mls) = &mut last.content {
let lines = &mut mls.lines;
if let Some(indention) = self.indention {
if !line_parser.have_indentions(indention, 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));
}
}
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 should_be_multi_line(s: &str) -> bool {
s.contains("'") | s.contains("\"") | s.contains("\n")
}
#[derive(Debug)]
enum EncodedValue {
Inlined(String),
MultiLineString(Vec<String>),
Object(HashMap<String, EncodedValue>),
InlinedArray(Vec<EncodedValue>),
MultiLineArray(Vec<EncodedValue>),
}
impl EncodedValue {
fn mls_from_str(s: &str) -> Self {
Self::MultiLineString(s.lines().map(ToString::to_string).collect())
}
fn inlined(s: impl ToString) -> Self {
Self::Inlined(s.to_string())
}
fn object_from_iter<K: ToString, V: Into<EncodedValue>>(
it: impl IntoIterator<Item = (K, V)>,
) -> Self {
Self::Object(HashMap::from_iter(
it.into_iter().map(|(k, v)| (k.to_string(), v.into())),
))
}
fn multi_line_array_from_iter<V: Into<EncodedValue>>(
it: impl IntoIterator<Item = V>,
) -> Self {
Self::MultiLineArray(it.into_iter().map(|v| v.into()).collect())
}
fn inline_array_from_iter<V: Into<EncodedValue>>(it: impl IntoIterator<Item = V>) -> Self {
Self::InlinedArray(it.into_iter().map(|v| v.into()).collect())
}
fn is_multi_line_array(&self) -> bool {
matches!(self, Self::MultiLineArray(..))
}
}
impl From<&PrimitiveValue> for EncodedValue {
fn from(p: &PrimitiveValue) -> Self {
match p {
PrimitiveValue::Number(p) => Self::Inlined(p.to_string()),
PrimitiveValue::Boolean(p) => Self::Inlined(p.to_string()),
PrimitiveValue::String(s) => {
if should_be_multi_line(s) {
Self::mls_from_str(s)
} else {
Self::Inlined(format!("'{s}'"))
}
}
PrimitiveValue::Null => Self::inlined("null"),
}
}
}
impl From<&Value> for EncodedValue {
fn from(v: &Value) -> Self {
match v {
Value::Primitive(p) => Self::from(p),
Value::Array(arr) => {
let encoded = arr
.into_iter()
.map(|value| EncodedValue::from(value))
.collect::<Vec<_>>();
let has_non_inlined = encoded
.iter()
.find(|v| !matches!(v, EncodedValue::Inlined(..)))
.is_some();
if has_non_inlined {
Self::multi_line_array_from_iter(encoded)
} else {
Self::inline_array_from_iter(encoded)
}
}
Value::Object(obj) => {
let encoded = obj
.into_iter()
.map(|(key, value)| (key, EncodedValue::from(value)));
Self::object_from_iter(encoded)
}
}
}
}
fn encode_indent(lines: &mut Vec<String>, indent_str: &str, indent: i32) {
for _ in 0..indent {
lines.last_mut().unwrap().push_str(indent_str);
}
}
fn encoded_to_lines(indent_str: &str, lines: &mut Vec<String>, indent: i32, v: EncodedValue) {
match v {
EncodedValue::Inlined(s) => {
lines.last_mut().unwrap().push_str(&s);
}
EncodedValue::MultiLineString(s) => {
lines.last_mut().unwrap().push_str("|");
for line in s {
lines.push(String::new());
encode_indent(lines, indent_str, indent);
lines.last_mut().unwrap().push_str(&line);
}
}
EncodedValue::Object(v) => {
for (key, value) in v {
lines.push(String::new());
encode_indent(lines, indent_str, indent);
if value.is_multi_line_array() {
lines.last_mut().unwrap().push_str(&format!("{key}:"));
} else {
lines.last_mut().unwrap().push_str(&format!("{key}: "));
}
encoded_to_lines(indent_str, lines, indent + 1, value);
}
}
EncodedValue::InlinedArray(arr) => {
lines.last_mut().unwrap().push_str("[");
if arr.len() > 0 {
let mut it = arr.into_iter();
encoded_to_lines(indent_str, lines, indent, it.next().unwrap());
for v in it {
lines.last_mut().unwrap().push_str(" ");
encoded_to_lines(indent_str, lines, indent, v);
}
}
lines.last_mut().unwrap().push_str("]");
}
EncodedValue::MultiLineArray(arr) => {
lines.last_mut().unwrap().push_str("--");
for v in arr {
lines.push(String::new());
encode_indent(lines, indent_str, indent);
if !matches!(v, EncodedValue::MultiLineArray(..)) {
lines.last_mut().unwrap().push_str("- ");
}
encoded_to_lines(indent_str, lines, indent + 1, v);
}
}
}
}
let indention = match indention {
Indention::Tabs => "\t".to_string(),
Indention::Spaces(spaces) => (" ").repeat(spaces).to_string(),
};
let encoded = EncodedValue::from(v);
let mut lines: Vec<String> = vec![String::new()];
encoded_to_lines(&indention, &mut lines, 0, encoded);
lines.join("\n")
}