diff --git a/Package/JavaScript/src/bellande_parser.js b/Package/JavaScript/src/bellande_parser.js index c86feb1..1adb24a 100644 --- a/Package/JavaScript/src/bellande_parser.js +++ b/Package/JavaScript/src/bellande_parser.js @@ -1,45 +1,73 @@ +// Copyright (C) 2024 Bellande Algorithm Model Research Innovation Center, Ronaldson Bellande + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + const fs = require('fs'); class BellandeFormat { parseBellande(filePath) { const content = fs.readFileSync(filePath, 'utf8'); const lines = content.split('\n'); - return this.parseLines(lines); + const parsedData = this.parseLines(lines); + return this.toStringRepresentation(parsedData); } parseLines(lines) { const result = {}; - const stack = [[-1, result]]; + let currentKey = null; + let currentList = null; + const indentStack = [[-1, result]]; for (const line of lines) { const stripped = line.trim(); if (!stripped || stripped.startsWith('#')) continue; const indent = line.length - line.trimLeft().length; - while (stack.length && indent <= stack[stack.length - 1][0]) { - stack.pop(); - } - const parent = stack[stack.length - 1][1]; + while (indentStack.length && indent <= indentStack[indentStack.length - 1][0]) { + const popped = indentStack.pop(); + if (Array.isArray(popped[1])) { + currentList = null; + } + } if (stripped.includes(':')) { const [key, value] = stripped.split(':').map(s => s.trim()); + currentKey = key; if (value) { - parent[key] = this.parseValue(value); + result[key] = this.parseValue(value); } else { - const newDict = {}; - parent[key] = newDict; - stack.push([indent, newDict]); + result[key] = []; + currentList = result[key]; + indentStack.push([indent, currentList]); } } else if (stripped.startsWith('-')) { const value = stripped.slice(1).trim(); - if (Array.isArray(parent)) { - parent.push(this.parseValue(value)); + const parsedValue = this.parseValue(value); + if (currentList !== null) { + currentList.push(parsedValue); } else { - const newList = [this.parseValue(value)]; - const lastKey = Object.keys(parent).pop(); - parent[lastKey] = newList; - stack.push([indent, newList]); + if (Object.keys(result).length === 0) { + result = [parsedValue]; + currentList = result; + indentStack = [[-1, result]]; + } else { + result[currentKey] = [parsedValue]; + currentList = result[currentKey]; + indentStack.push([indent, currentList]); + } } } } @@ -57,9 +85,30 @@ class BellandeFormat { return value; } + toStringRepresentation(data) { + if (typeof data === 'object' && data !== null) { + if (Array.isArray(data)) { + const items = data.map(item => this.toStringRepresentation(item)); + return '[' + items.join(', ') + ']'; + } else { + const items = Object.entries(data).map(([k, v]) => `"${k}": ${this.toStringRepresentation(v)}`); + return '{' + items.join(', ') + '}'; + } + } else if (typeof data === 'string') { + return `"${data}"`; + } else if (typeof data === 'number') { + return data.toString(); + } else if (data === null) { + return 'null'; + } else if (typeof data === 'boolean') { + return data.toString().toLowerCase(); + } else { + return String(data); + } + } + writeBellande(data, filePath) { - const content = this.toBellandeString(data); - fs.writeFileSync(filePath, content); + fs.writeFileSync(filePath, this.toBellandeString(data)); } toBellandeString(data, indent = 0) { @@ -84,14 +133,19 @@ class BellandeFormat { formatValue(value) { if (typeof value === 'string') { - return value.includes(' ') || value.includes(':') ? `"${value}"` : value; + if (value.includes(' ') || value.includes(':') || ['true', 'false', 'null'].includes(value.toLowerCase())) { + return `"${value}"`; + } + return value; } if (typeof value === 'boolean') { - return value.toString(); + return value.toString().toLowerCase(); } if (value === null) { return 'null'; } - return value.toString(); + return String(value); } } + +module.exports = BellandeFormat; diff --git a/Package/Python/.gitignore b/Package/Python/.gitignore index 3e54b1f..b111f4b 100644 --- a/Package/Python/.gitignore +++ b/Package/Python/.gitignore @@ -2,3 +2,4 @@ publish.sh tests setup.py dist +src/bellande_format.egg-info diff --git a/Package/Rust/README.md b/Package/Rust/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Package/Rust/src/bellande_parser.rs b/Package/Rust/src/bellande_parser.rs new file mode 100644 index 0000000..c3fa077 --- /dev/null +++ b/Package/Rust/src/bellande_parser.rs @@ -0,0 +1,196 @@ +// Copyright (C) 2024 Bellande Algorithm Model Research Innovation Center, Ronaldson Bellande + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::collections::HashMap; +use std::fs; +use std::path::Path; + +#[derive(Debug, Clone)] +enum BellandeValue { + String(String), + Integer(i64), + Float(f64), + Boolean(bool), + Null, + List(Vec), + Map(HashMap), +} + +struct BellandeFormat; + +impl BellandeFormat { + pub fn parse_bellande>( + &self, + file_path: P, + ) -> Result { + let content = fs::read_to_string(file_path)?; + let lines: Vec<&str> = content.lines().collect(); + let parsed_data = self.parse_lines(&lines); + Ok(parsed_data) + } + + fn parse_lines(&self, lines: &[&str]) -> BellandeValue { + let mut result = HashMap::new(); + let mut current_key = String::new(); + let mut current_list: Option> = None; + let mut indent_stack = vec![(0, &mut result)]; + + for line in lines { + let stripped = line.trim(); + if stripped.is_empty() || stripped.starts_with('#') { + continue; + } + + let indent = line.len() - line.trim_start().len(); + + while indent_stack.last().map_or(false, |&(i, _)| indent <= i) { + indent_stack.pop(); + current_list = None; + } + + if let Some(colon_pos) = stripped.find(':') { + let (key, value) = stripped.split_at(colon_pos); + let key = key.trim().to_string(); + let value = value[1..].trim(); + + current_key = key.clone(); + if !value.is_empty() { + let parsed_value = self.parse_value(value); + indent_stack.last_mut().unwrap().1.insert(key, parsed_value); + } else { + let new_list = Vec::new(); + indent_stack + .last_mut() + .unwrap() + .1 + .insert(key, BellandeValue::List(new_list)); + if let BellandeValue::List(list) = indent_stack + .last_mut() + .unwrap() + .1 + .get_mut(¤t_key) + .unwrap() + { + current_list = Some(list); + indent_stack.push((indent, list)); + } + } + } else if stripped.starts_with('-') { + let value = stripped[1..].trim(); + let parsed_value = self.parse_value(value); + if let Some(list) = &mut current_list { + list.push(parsed_value); + } else { + let mut new_list = Vec::new(); + new_list.push(parsed_value); + indent_stack + .last_mut() + .unwrap() + .1 + .insert(current_key.clone(), BellandeValue::List(new_list)); + if let BellandeValue::List(list) = indent_stack + .last_mut() + .unwrap() + .1 + .get_mut(¤t_key) + .unwrap() + { + current_list = Some(list); + indent_stack.push((indent, list)); + } + } + } + } + + BellandeValue::Map(result) + } + + fn parse_value(&self, value: &str) -> BellandeValue { + if value.eq_ignore_ascii_case("true") { + BellandeValue::Boolean(true) + } else if value.eq_ignore_ascii_case("false") { + BellandeValue::Boolean(false) + } else if value.eq_ignore_ascii_case("null") { + BellandeValue::Null + } else if value.starts_with('"') && value.ends_with('"') { + BellandeValue::String(value[1..value.len() - 1].to_string()) + } else if let Ok(int_value) = value.parse::() { + BellandeValue::Integer(int_value) + } else if let Ok(float_value) = value.parse::() { + BellandeValue::Float(float_value) + } else { + BellandeValue::String(value.to_string()) + } + } + + pub fn write_bellande>( + &self, + data: &BellandeValue, + file_path: P, + ) -> Result<(), std::io::Error> { + let content = self.to_bellande_string(data, 0); + fs::write(file_path, content) + } + + fn to_bellande_string(&self, data: &BellandeValue, indent: usize) -> String { + match data { + BellandeValue::Map(map) => map + .iter() + .map(|(key, value)| { + let value_str = match value { + BellandeValue::Map(_) | BellandeValue::List(_) => { + format!("\n{}", self.to_bellande_string(value, indent + 2)) + } + _ => format!(" {}", self.format_value(value)), + }; + format!("{}{}: {}", " ".repeat(indent), key, value_str) + }) + .collect::>() + .join("\n"), + BellandeValue::List(list) => list + .iter() + .map(|item| { + format!( + "{}- {}", + " ".repeat(indent), + self.to_bellande_string(item, indent + 2) + ) + }) + .collect::>() + .join("\n"), + _ => self.format_value(data), + } + } + + fn format_value(&self, value: &BellandeValue) -> String { + match value { + BellandeValue::String(s) => { + if s.contains(' ') + || s.contains(':') + || ["true", "false", "null"].contains(&s.to_lowercase().as_str()) + { + format!("\"{}\"", s) + } else { + s.clone() + } + } + BellandeValue::Integer(i) => i.to_string(), + BellandeValue::Float(f) => f.to_string(), + BellandeValue::Boolean(b) => b.to_string().to_lowercase(), + BellandeValue::Null => "null".to_string(), + BellandeValue::List(_) | BellandeValue::Map(_) => unreachable!(), + } + } +}