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:
|
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`
|
- 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`
|
- Special identifiers can be quoted with `@`: `.@"special-name" = value`
|
||||||
- Comments use `//` syntax
|
- 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:
|
Example:
|
||||||
|
|
||||||
```zon
|
```zon
|
||||||
@ -110,7 +112,8 @@ Example:
|
|||||||
.hash = "abcdef123456",
|
.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",
|
.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()
|
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:
|
class TestZonFileParser:
|
||||||
"""Test cases for the file parsing functions."""
|
"""Test cases for the file parsing functions."""
|
||||||
|
|||||||
@ -78,14 +78,10 @@ class ZonParser:
|
|||||||
if char == ".":
|
if char == ".":
|
||||||
self._next_char() # Skip the dot
|
self._next_char() # Skip the dot
|
||||||
|
|
||||||
# Check if it's an object
|
# Check if it's an object or tuple
|
||||||
if self._current_char() == "{":
|
if self._current_char() == "{":
|
||||||
return self._parse_object()
|
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
|
# It's a field name or a special value
|
||||||
return self._parse_identifier()
|
return self._parse_identifier()
|
||||||
|
|
||||||
@ -103,12 +99,36 @@ class ZonParser:
|
|||||||
f"Unexpected character '{char}' at line {self.line}, column {self.col}"
|
f"Unexpected character '{char}' at line {self.line}, column {self.col}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_object(self) -> Dict[str, Any]:
|
def _parse_object(self) -> Union[Dict[str, Any], List[Any]]:
|
||||||
result = {}
|
"""Parse a ZON object (anonymous struct) or tuple."""
|
||||||
|
|
||||||
# Skip the opening brace
|
# Skip the opening brace
|
||||||
self._next_char()
|
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:
|
while True:
|
||||||
self._skip_whitespace_and_comments()
|
self._skip_whitespace_and_comments()
|
||||||
|
|
||||||
@ -151,32 +171,44 @@ class ZonParser:
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _parse_array(self) -> List[Any]:
|
def _parse_tuple(self) -> List[Any]:
|
||||||
|
"""Parse a tuple in ZON format."""
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
# Skip the opening bracket
|
|
||||||
self._next_char()
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
self._skip_whitespace_and_comments()
|
self._skip_whitespace_and_comments()
|
||||||
|
|
||||||
# Check for closing bracket
|
# Check for closing brace
|
||||||
if self._current_char() == "]":
|
if self._current_char() == "}":
|
||||||
self._next_char()
|
self._next_char()
|
||||||
break
|
break
|
||||||
|
|
||||||
# Parse value
|
# Check for nested tuple or object with dot prefix
|
||||||
value = self._parse_value()
|
if self._current_char() == ".":
|
||||||
result.append(value)
|
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()
|
self._skip_whitespace_and_comments()
|
||||||
|
|
||||||
# Check for comma
|
# Check for comma
|
||||||
if self._current_char() == ",":
|
if self._current_char() == ",":
|
||||||
self._next_char()
|
self._next_char()
|
||||||
elif self._current_char() != "]":
|
elif self._current_char() != "}":
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Expected ',' or ']' at line {self.line}, column {self.col}"
|
f"Expected ',' or '}}' at line {self.line}, column {self.col}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user