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:
@ -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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -7,5 +7,5 @@
|
||||
.hash = "abcdef123456",
|
||||
},
|
||||
},
|
||||
.tags = .["tag1", "tag2"],
|
||||
}
|
||||
.tags = .{ "tag1", "tag2" },
|
||||
}
|
||||
|
||||
14
examples/nested_test.zon
Normal file
14
examples/nested_test.zon
Normal 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
6
examples/simple.zon
Normal file
@ -0,0 +1,6 @@
|
||||
.{
|
||||
.name = "example",
|
||||
.version = "1.0.0",
|
||||
.numbers = .{ 1, 2, 3 },
|
||||
.strings = .{ "one", "two", "three" },
|
||||
}
|
||||
4
examples/simple_nested.zon
Normal file
4
examples/simple_nested.zon
Normal file
@ -0,0 +1,4 @@
|
||||
.{
|
||||
.simple_tuple = .{ 1, 2, 3 },
|
||||
.nested_tuple = .{ .{ 1, 2 }, .{ 3, 4 } },
|
||||
}
|
||||
6
examples/tuple_test.zon
Normal file
6
examples/tuple_test.zon
Normal 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
22
examples/zig_tuples.zon
Normal 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 = .{},
|
||||
}
|
||||
@ -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."""
|
||||
|
||||
@ -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,32 +171,44 @@ 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
|
||||
value = self._parse_value()
|
||||
result.append(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)
|
||||
|
||||
self._skip_whitespace_and_comments()
|
||||
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user