pub mod core;
pub mod ir;
pub mod jsonc;
use core::{Decoder, Encoder};
use ir::ast::Node;
use jsonc::Parser;
pub fn encode(node: &Node) -> Vec<u8> {
let mut encoder = Encoder::new();
encoder.encode(node)
}
pub fn decode(data: &[u8]) -> Result<Node, std::io::Error> {
let mut decoder = Decoder::new(data.to_vec());
decoder.decode()
}
pub fn parse_jsonc(input: &str) -> Result<Node, String> {
let mut parser = Parser::new(input);
parser.parse()
}
pub fn to_jsonc_string(node: &Node) -> String {
ir::to_string(node)
}
#[cfg(test)]
mod tests {
use super::*;
use ir::{Field, Node, Object, Tag, Value, ValueData, ValueType};
#[test]
fn test_tag_parse_direct() {
let result = Tag::parse("// mm:type=str;desc=用户名");
assert!(result.is_some(), "Tag::parse should succeed");
let tag = result.unwrap();
assert_eq!(tag.value_type, ValueType::Str);
assert_eq!(tag.desc, Some("用户名".to_string()));
}
#[test]
fn test_simple_encode_decode() {
let obj = Node::Object(Object {
fields: vec![Field {
key: "name".to_string(),
value: Node::Value(Value {
data: ValueData::String("test".to_string()),
text: "test".to_string(),
path: String::new(),
tag: None,
}),
}],
tag: Some(Tag::new()),
path: String::new(),
});
let encoded = encode(&obj);
assert!(!encoded.is_empty());
}
#[test]
fn test_jsonc_parse() {
let input = r#"{"name": "test", "value": 123}"#;
let node = parse_jsonc(input).unwrap();
match node {
Node::Object(o) => {
assert_eq!(o.fields.len(), 2);
}
_ => panic!("expected object"),
}
}
#[test]
fn test_jsonc_with_comment() {
let input = r#"{
// mm:type=str;desc=用户名
"name": "test"
}"#;
let node = parse_jsonc(input).unwrap();
match node {
Node::Object(o) => {
assert_eq!(o.fields.len(), 1);
let tag = o.fields[0].value.get_tag();
assert!(tag.is_some());
let tag = tag.unwrap();
assert_eq!(tag.value_type, ValueType::Str);
assert_eq!(tag.desc, Some("用户名".to_string()));
}
_ => panic!("expected object"),
}
}
#[test]
fn test_jsonc_roundtrip() {
let input = r#"{"name": "test", "age": 25}"#;
let node = parse_jsonc(input).unwrap();
let output = to_jsonc_string(&node);
assert!(output.contains("\"name\""));
assert!(output.contains("test"));
}
#[test]
fn test_tag_parse_basic() {
let tag =
Tag::parse("// mm:name=id;min=1;desc=用户ID;enums=active|pending|deleted").unwrap();
assert_eq!(tag.value_type, ValueType::Enum);
assert_eq!(tag.desc, Some("用户ID".to_string()));
assert_eq!(tag.min, Some("1".to_string()));
assert_eq!(tag.enums, Some("active|pending|deleted".to_string()));
}
#[test]
fn test_tag_parse_flags() {
let tag = Tag::parse("// mm:nullable;default_val=abc;max=10;min=5").unwrap();
assert!(tag.nullable);
assert_eq!(tag.default_val, Some("abc".to_string()));
assert_eq!(tag.max, Some("10".to_string()));
assert_eq!(tag.min, Some("5".to_string()));
}
#[test]
fn test_tag_parse_quoted_and_semicolon() {
let tag = Tag::parse(r#"// mm:name="id";desc="用户ID";enums="active|pending";pattern="^a,b$";type=str;min=1;max=5;nullable;default_val="x""#).unwrap();
assert_eq!(tag.desc, Some("用户ID".to_string()));
assert_eq!(tag.enums, Some("active|pending".to_string()));
assert_eq!(tag.pattern, Some("^a,b$".to_string()));
assert_eq!(tag.value_type, ValueType::Str);
assert_eq!(tag.min, Some("1".to_string()));
assert_eq!(tag.max, Some("5".to_string()));
assert!(tag.nullable);
assert_eq!(tag.default_val, Some("x".to_string()));
}
#[test]
fn test_tag_parse_block_comment() {
let tag = Tag::parse("/* mm:type=i64;desc=用户ID */").unwrap();
assert_eq!(tag.value_type, ValueType::I64);
assert_eq!(tag.desc, Some("用户ID".to_string()));
}
#[test]
fn test_tag_parse_non_mm_comment() {
let result = Tag::parse("// this is a regular comment");
assert!(result.is_none());
}
#[test]
fn test_tag_parse_empty_mm() {
let tag = Tag::parse("// mm:").unwrap();
assert_eq!(tag.value_type, ValueType::Unknown);
}
#[test]
fn test_tag_parse_type_aliases() {
let tag = Tag::parse("// mm:type=vec").unwrap();
assert_eq!(tag.value_type, ValueType::Vec);
let tag = Tag::parse("// mm:type=arr").unwrap();
assert_eq!(tag.value_type, ValueType::Arr);
let tag = Tag::parse("// mm:type=vec").unwrap();
assert_eq!(tag.value_type, ValueType::Vec);
let tag = Tag::parse("// mm:type=arr").unwrap();
assert_eq!(tag.value_type, ValueType::Arr);
}
#[test]
fn test_parse_object_empty() {
let node = parse_jsonc(r#"{}"#).unwrap();
match node {
Node::Object(o) => assert_eq!(o.fields.len(), 0),
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_object_valid() {
let node = parse_jsonc(r#"{"name": "Alice", "age": 18}"#).unwrap();
match node {
Node::Object(o) => {
assert_eq!(o.fields.len(), 2);
assert_eq!(o.fields[0].key, "name");
assert_eq!(o.fields[1].key, "age");
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_object_eof_after_colon() {
let node = parse_jsonc(r#"{"key": "#).unwrap();
match node {
Node::Object(o) => assert_eq!(o.fields.len(), 0),
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_object_eof_after_partial() {
let node = parse_jsonc(r#"{"name": "Alice", "age": "#).unwrap();
match node {
Node::Object(o) => {
assert_eq!(o.fields.len(), 1);
assert_eq!(o.fields[0].key, "name");
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_object_eof_after_comma() {
let node = parse_jsonc(r#"{"key": "val", "#).unwrap();
match node {
Node::Object(o) => {
assert_eq!(o.fields.len(), 1);
assert_eq!(o.fields[0].key, "key");
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_object_rbrace_after_colon() {
let result = parse_jsonc(r#"{"key":}"#);
assert!(result.is_err());
}
#[test]
fn test_parse_array_eof_after_open() {
let node = parse_jsonc(r#"["#).unwrap();
match node {
Node::Array(a) => assert_eq!(a.items.len(), 0),
_ => panic!("expected array"),
}
}
#[test]
fn test_parse_array_eof_after_item() {
let node = parse_jsonc(r#"[1, "#).unwrap();
match node {
Node::Array(a) => {
assert_eq!(a.items.len(), 1);
}
_ => panic!("expected array"),
}
}
#[test]
fn test_parse_array_empty() {
let node = parse_jsonc(r#"[]"#).unwrap();
match node {
Node::Array(a) => assert_eq!(a.items.len(), 0),
_ => panic!("expected array"),
}
}
#[test]
fn test_parse_array_with_items() {
let node = parse_jsonc(r#"[1, "two", true, null, 3.14]"#).unwrap();
match node {
Node::Array(a) => {
assert_eq!(a.items.len(), 5);
}
_ => panic!("expected array"),
}
}
#[test]
fn test_parse_nested_objects() {
let node = parse_jsonc(r#"{"outer": {"inner": "value"}}"#).unwrap();
match node {
Node::Object(o) => {
assert_eq!(o.fields.len(), 1);
match &o.fields[0].value {
Node::Object(inner) => {
assert_eq!(inner.fields.len(), 1);
assert_eq!(inner.fields[0].key, "inner");
}
_ => panic!("expected nested object"),
}
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_nested_arrays() {
let node = parse_jsonc(r#"[[1, 2], [3, 4]]"#).unwrap();
match node {
Node::Array(a) => {
assert_eq!(a.items.len(), 2);
}
_ => panic!("expected array"),
}
}
#[test]
fn test_parse_negative_number() {
let node = parse_jsonc(r#"{"val": -42}"#).unwrap();
match node {
Node::Object(o) => {
let val = &o.fields[0].value;
let tag = val.get_tag().unwrap();
assert_eq!(tag.value_type, ValueType::I);
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_float_number() {
let node = parse_jsonc(r#"{"val": 3.14}"#).unwrap();
match node {
Node::Object(o) => {
let val = &o.fields[0].value;
let tag = val.get_tag().unwrap();
assert_eq!(tag.value_type, ValueType::F64);
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_boolean() {
let node = parse_jsonc(r#"{"a": true, "b": false}"#).unwrap();
match node {
Node::Object(o) => {
let tag_a = o.fields[0].value.get_tag().unwrap();
let tag_b = o.fields[1].value.get_tag().unwrap();
assert_eq!(tag_a.value_type, ValueType::Bool);
assert_eq!(tag_b.value_type, ValueType::Bool);
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_val_nil_does_not_crash() {
let cases = vec![
("EOF after open brace", "{", true),
("EOF after open bracket", "[", true),
("EOF after key colon", r#"{"a": "#, true),
("EOF after comma in object", r#"{"a": 1, "#, true),
("EOF after comma in array", "[1, ", true),
];
for (name, input, expect_ok) in cases {
let result = parse_jsonc(input);
assert_eq!(result.is_ok(), expect_ok, "test case '{}' failed", name);
}
}
#[test]
fn test_tag_inheritance() {
let mut child = Tag::new();
let parent = Tag {
child_desc: Some("用户名".to_string()),
child_type: ValueType::Str,
child_nullable: true,
..Tag::new()
};
child.inherit(&parent);
assert_eq!(child.desc, Some("用户名".to_string()));
assert_eq!(child.value_type, ValueType::Str);
assert!(child.nullable);
assert!(child.is_inherit);
}
#[test]
fn test_parse_comment_blank_line_separator() {
let input = "{\n // mm:type=str;desc=用户名\n\n \"name\": \"test\"\n}";
let node = parse_jsonc(input).unwrap();
match node {
Node::Object(o) => {
let tag = o.fields[0].value.get_tag().unwrap();
assert_eq!(
tag.desc, None,
"comment separated by blank line should be dropped"
);
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_multiple_leading_comments() {
let input = "{\n // mm:type=str\n // mm:desc=用户名\n \"name\": \"test\"\n}";
let node = parse_jsonc(input).unwrap();
match node {
Node::Object(o) => {
let tag = o.fields[0].value.get_tag().unwrap();
assert_eq!(tag.value_type, ValueType::Str);
assert_eq!(tag.desc, Some("用户名".to_string()));
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_trailing_comment_merges_tag() {
let input = "{\n \"name\": \"test\" // mm:desc=用户名\n}";
let node = parse_jsonc(input).unwrap();
match node {
Node::Object(o) => {
let tag = o.fields[0].value.get_tag().unwrap();
assert_eq!(tag.desc, Some("用户名".to_string()));
assert_eq!(tag.value_type, ValueType::Str);
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_object_comment_before_brace() {
let input = "// mm:desc=用户对象\n{\n \"name\": \"test\"\n}";
let node = parse_jsonc(input).unwrap();
match node {
Node::Object(o) => {
let tag = o.tag.as_ref().unwrap();
assert_eq!(tag.desc, Some("用户对象".to_string()));
}
_ => panic!("expected object"),
}
}
#[test]
fn test_parse_array_size_based_type() {
let input = "// mm:size=3\n[1, 2, 3]";
let node = parse_jsonc(input).unwrap();
match node {
Node::Array(a) => {
let tag = a.tag.as_ref().unwrap();
assert_eq!(tag.value_type, ValueType::Arr);
assert_eq!(tag.size, Some(3));
}
_ => panic!("expected array"),
}
}
#[test]
fn test_comment_with_mm_only_no_tag() {
let input = "{\n // this is a normal comment\n \"name\": \"test\"\n}";
let node = parse_jsonc(input).unwrap();
match node {
Node::Object(o) => {
let tag = o.fields[0].value.get_tag();
assert!(tag.is_some());
let tag = tag.unwrap();
assert_eq!(tag.desc, None);
assert_eq!(tag.value_type, ValueType::Str);
}
_ => panic!("expected object"),
}
}
}