Enhance ZON parser to support tuples and update documentation

- Modify parser to handle ZON tuples (`.{ 1, 2, 3 }`) as arrays
- Update README with more detailed explanation of ZON tuple syntax
- Add multiple example ZON files demonstrating tuple usage
- Implement tuple parsing in `parser.py`
- Add test case for tuple parsing
This commit is contained in:
2025-03-07 17:01:12 +08:00
parent 807fcb2849
commit fd29f7e3af
9 changed files with 129 additions and 24 deletions

View File

@ -92,12 +92,14 @@ pytest --cov=zig_fetch_py --cov-report=html
Zig Object Notation (ZON) is a data format used by the Zig programming language. It's similar to JSON but with some differences in syntax:
- Objects are defined with `.{ ... }`
- Objects (anonymous structs) are defined with `.{ .key = value, ... }`
- Keys are prefixed with a dot: `.key = value`
- Arrays are defined with `.[ ... ]`
- Tuples are defined with `.{value1, value2, ...}` and are parsed as arrays in JSON
- Special identifiers can be quoted with `@`: `.@"special-name" = value`
- Comments use `//` syntax
Note: ZON doesn't have a dedicated array syntax like JSON's `[]`. Instead, tuples (`.{value1, value2, ...}`) serve a similar purpose and are converted to arrays in JSON.
Example:
```zon
@ -110,7 +112,8 @@ Example:
.hash = "abcdef123456",
},
},
.tags = .["tag1", "tag2"],
.tags = .{1, 2, 3}, // Tuple (parsed as array in JSON)
.paths = .{""}, // Single-item tuple
}
```

View File

@ -7,5 +7,5 @@
.hash = "abcdef123456",
},
},
.tags = .["tag1", "tag2"],
.tags = .{ "tag1", "tag2" },
}

14
examples/nested_test.zon Normal file
View File

@ -0,0 +1,14 @@
.{
.simple_object = .{
.x = 1,
.y = 2,
},
.simple_tuple = .{ 1, 2, 3 },
.nested_object = .{
.nested = .{
.a = 1,
.b = 2,
},
},
.nested_tuple = .{ .{ 1, 2 }, .{ 3, 4 } },
}

6
examples/simple.zon Normal file
View File

@ -0,0 +1,6 @@
.{
.name = "example",
.version = "1.0.0",
.numbers = .{ 1, 2, 3 },
.strings = .{ "one", "two", "three" },
}

View File

@ -0,0 +1,4 @@
.{
.simple_tuple = .{ 1, 2, 3 },
.nested_tuple = .{ .{ 1, 2 }, .{ 3, 4 } },
}

6
examples/tuple_test.zon Normal file
View File

@ -0,0 +1,6 @@
.{
.simple_tuple = .{ 1, 2, 3 },
.string_tuple = .{ "one", "two", "three" },
.empty_tuple = .{""},
.mixed_tuple = .{ 1, "two", true },
}

22
examples/zig_tuples.zon Normal file
View File

@ -0,0 +1,22 @@
.{
// Object with field values
.metadata = .{
.name = "example",
.version = "1.0.0",
},
// Tuples of different types
.numbers = .{ 1, 2, 3 },
.strings = .{ "one", "two", "three" },
.mixed = .{ 1, "two", true, null },
.empty_string = .{""},
// Nested structures
.nested = .{
.tuple_in_object = .{ 4, 5, 6 },
.object_in_tuple = .{ .{ .x = 1, .y = 2 }, .{ .x = 3, .y = 4 } },
},
// Empty tuple
.empty = .{},
}

View File

@ -151,6 +151,24 @@ class TestZonParser:
)
parser.parse()
def test_parse_tuple(self):
"""Test parsing tuples."""
parser = ZonParser(
""".{
.simple_tuple = .{1, 2, 3},
.string_tuple = .{"one", "two", "three"},
.empty_tuple = .{""},
.mixed_tuple = .{1, "two", true},
}"""
)
result = parser.parse()
assert result == {
"simple_tuple": [1, 2, 3],
"string_tuple": ["one", "two", "three"],
"empty_tuple": [""],
"mixed_tuple": [1, "two", True],
}
class TestZonFileParser:
"""Test cases for the file parsing functions."""

View File

@ -78,14 +78,10 @@ class ZonParser:
if char == ".":
self._next_char() # Skip the dot
# Check if it's an object
# Check if it's an object or tuple
if self._current_char() == "{":
return self._parse_object()
# Check if it's an array
if self._current_char() == "[":
return self._parse_array()
# It's a field name or a special value
return self._parse_identifier()
@ -103,12 +99,36 @@ class ZonParser:
f"Unexpected character '{char}' at line {self.line}, column {self.col}"
)
def _parse_object(self) -> Dict[str, Any]:
result = {}
def _parse_object(self) -> Union[Dict[str, Any], List[Any]]:
"""Parse a ZON object (anonymous struct) or tuple."""
# Skip the opening brace
self._next_char()
# Check if it's a tuple (values separated by commas without keys)
is_tuple = False
pos_before_check = self.pos
line_before_check = self.line
col_before_check = self.col
# Look ahead to see if this is a tuple
self._skip_whitespace_and_comments()
if self._current_char() != ".":
# If it doesn't start with a dot for a key, it might be a tuple
# or an empty object
if self._current_char() != "}":
is_tuple = True
# Reset position after look-ahead
self.pos = pos_before_check
self.line = line_before_check
self.col = col_before_check
if is_tuple:
return self._parse_tuple()
# Regular object parsing
result = {}
while True:
self._skip_whitespace_and_comments()
@ -151,21 +171,33 @@ class ZonParser:
return result
def _parse_array(self) -> List[Any]:
def _parse_tuple(self) -> List[Any]:
"""Parse a tuple in ZON format."""
result = []
# Skip the opening bracket
self._next_char()
while True:
self._skip_whitespace_and_comments()
# Check for closing bracket
if self._current_char() == "]":
# Check for closing brace
if self._current_char() == "}":
self._next_char()
break
# Parse value
# Check for nested tuple or object with dot prefix
if self._current_char() == ".":
self._next_char() # Skip the dot
# Check if it's a nested object or tuple
if self._current_char() == "{":
value = self._parse_object()
result.append(value)
else:
# It's a field name or a special value
self.pos -= 1 # Move back to include the dot
value = self._parse_value()
result.append(value)
else:
# Regular value
value = self._parse_value()
result.append(value)
@ -174,9 +206,9 @@ class ZonParser:
# Check for comma
if self._current_char() == ",":
self._next_char()
elif self._current_char() != "]":
elif self._current_char() != "}":
raise ValueError(
f"Expected ',' or ']' at line {self.line}, column {self.col}"
f"Expected ',' or '}}' at line {self.line}, column {self.col}"
)
return result