langroid 0.16.7__py3-none-any.whl → 0.17.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. langroid/agent/base.py +45 -21
  2. langroid/agent/chat_agent.py +22 -14
  3. langroid/agent/chat_document.py +22 -13
  4. langroid/agent/tool_message.py +11 -11
  5. langroid/agent/tools/file_tools.py +234 -0
  6. langroid/agent/xml_tool_message.py +179 -45
  7. langroid/utils/constants.py +2 -0
  8. langroid/utils/git_utils.py +251 -0
  9. langroid/utils/system.py +78 -0
  10. {langroid-0.16.7.dist-info → langroid-0.17.1.dist-info}/METADATA +6 -3
  11. {langroid-0.16.7.dist-info → langroid-0.17.1.dist-info}/RECORD +14 -89
  12. pyproject.toml +3 -2
  13. langroid/agent/md_tool_message_grammar.py +0 -455
  14. langroid/agent/tools/code_file_tool_parse.py +0 -150
  15. langroid/agent/tools/code_file_tool_pyparsing.py +0 -194
  16. langroid/agent/tools/code_file_tool_pyparsing2.py +0 -199
  17. langroid/agent/tools/extract_tool.py +0 -96
  18. langroid/agent/tools/formatted_model_custom.py +0 -150
  19. langroid/agent/tools/formatted_model_custom2.py +0 -168
  20. langroid/agent/tools/formatted_model_custom3.py +0 -279
  21. langroid/agent/tools/formatted_model_custom4.py +0 -395
  22. langroid/agent/tools/formatted_model_jinja.py +0 -133
  23. langroid/agent/tools/formatted_model_jinja.py-e +0 -122
  24. langroid/agent/tools/formatted_model_jinja2.py +0 -145
  25. langroid/agent/tools/formatted_model_jinja2.py-e +0 -135
  26. langroid/agent/tools/formatted_model_lark.py +0 -0
  27. langroid/agent/tools/formatted_model_lark2.py +0 -168
  28. langroid/agent/tools/formatted_model_parse.py +0 -105
  29. langroid/agent/tools/formatted_model_parse.py-e +0 -98
  30. langroid/agent/tools/formatted_model_parse2.py +0 -113
  31. langroid/agent/tools/formatted_model_parse2.py-e +0 -109
  32. langroid/agent/tools/formatted_model_parse3.py +0 -114
  33. langroid/agent/tools/formatted_model_parse3.py-e +0 -110
  34. langroid/agent/tools/formatted_model_parsimon.py +0 -194
  35. langroid/agent/tools/formatted_model_parsimon.py-e +0 -186
  36. langroid/agent/tools/formatted_model_pyparsing.py +0 -169
  37. langroid/agent/tools/formatted_model_pyparsing.py-e +0 -149
  38. langroid/agent/tools/formatted_model_pyparsing2.py +0 -159
  39. langroid/agent/tools/formatted_model_pyparsing2.py-e +0 -143
  40. langroid/agent/tools/formatted_model_pyparsing3.py +0 -133
  41. langroid/agent/tools/formatted_model_pyparsing3.py-e +0 -121
  42. langroid/agent/tools/formatted_model_pyparsing4.py +0 -213
  43. langroid/agent/tools/formatted_model_pyparsing4.py-e +0 -176
  44. langroid/agent/tools/formatted_model_pyparsing5.py +0 -173
  45. langroid/agent/tools/formatted_model_pyparsing5.py-e +0 -142
  46. langroid/agent/tools/formatted_model_regex.py +0 -246
  47. langroid/agent/tools/formatted_model_regex.py-e +0 -248
  48. langroid/agent/tools/formatted_model_regex2.py +0 -250
  49. langroid/agent/tools/formatted_model_regex2.py-e +0 -253
  50. langroid/agent/tools/formatted_model_tatsu.py +0 -172
  51. langroid/agent/tools/formatted_model_tatsu.py-e +0 -160
  52. langroid/agent/tools/formatted_model_template.py +0 -217
  53. langroid/agent/tools/formatted_model_template.py-e +0 -200
  54. langroid/agent/tools/formatted_model_xml.py +0 -178
  55. langroid/agent/tools/formatted_model_xml2.py +0 -178
  56. langroid/agent/tools/formatted_model_xml3.py +0 -132
  57. langroid/agent/tools/formatted_model_xml4.py +0 -130
  58. langroid/agent/tools/formatted_model_xml5.py +0 -130
  59. langroid/agent/tools/formatted_model_xml6.py +0 -113
  60. langroid/agent/tools/formatted_model_xml7.py +0 -117
  61. langroid/agent/tools/formatted_model_xml8.py +0 -164
  62. langroid/agent/tools/generator_tool.py +0 -20
  63. langroid/agent/tools/generic_tool.py +0 -165
  64. langroid/agent/tools/generic_tool_tatsu.py +0 -275
  65. langroid/agent/tools/grammar_based_model.py +0 -132
  66. langroid/agent/tools/grammar_based_model.py-e +0 -128
  67. langroid/agent/tools/grammar_based_model_lark.py +0 -156
  68. langroid/agent/tools/grammar_based_model_lark.py-e +0 -153
  69. langroid/agent/tools/grammar_based_model_parse.py +0 -86
  70. langroid/agent/tools/grammar_based_model_parse.py-e +0 -80
  71. langroid/agent/tools/grammar_based_model_parsimonious.py +0 -129
  72. langroid/agent/tools/grammar_based_model_parsimonious.py-e +0 -120
  73. langroid/agent/tools/grammar_based_model_pyparsing.py +0 -105
  74. langroid/agent/tools/grammar_based_model_pyparsing.py-e +0 -103
  75. langroid/agent/tools/grammar_based_model_regex.py +0 -139
  76. langroid/agent/tools/grammar_based_model_regex.py-e +0 -130
  77. langroid/agent/tools/grammar_based_model_regex2.py +0 -124
  78. langroid/agent/tools/grammar_based_model_regex2.py-e +0 -116
  79. langroid/agent/tools/grammar_based_model_tatsu.py +0 -80
  80. langroid/agent/tools/grammar_based_model_tatsu.py-e +0 -77
  81. langroid/agent/tools/lark_earley_example.py +0 -135
  82. langroid/agent/tools/lark_earley_example.py-e +0 -117
  83. langroid/agent/tools/lark_example.py +0 -72
  84. langroid/agent/tools/note_tool.py +0 -0
  85. langroid/agent/tools/parse_example.py +0 -76
  86. langroid/agent/tools/parse_example2.py +0 -87
  87. langroid/agent/tools/parse_example3.py +0 -42
  88. langroid/agent/tools/parse_test.py +0 -791
  89. langroid/agent/tools/run_python_code.py +0 -60
  90. {langroid-0.16.7.dist-info → langroid-0.17.1.dist-info}/LICENSE +0 -0
  91. {langroid-0.16.7.dist-info → langroid-0.17.1.dist-info}/WHEEL +0 -0
@@ -1,156 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import Dict
3
-
4
- from lark import Lark, Transformer, Visitor
5
-
6
- from langroid.pydantic_v1 import BaseModel
7
-
8
-
9
- class GrammarBasedModel(BaseModel, ABC):
10
- @classmethod
11
- @abstractmethod
12
- def grammar(cls) -> str:
13
- pass
14
-
15
- @classmethod
16
- @abstractmethod
17
- def start_rule(cls) -> str:
18
- pass
19
-
20
- @classmethod
21
- @abstractmethod
22
- def field_mappings(cls) -> Dict[str, str]:
23
- pass
24
-
25
- @classmethod
26
- def parse(cls, text: str) -> "GrammarBasedModel":
27
- parser = Lark(cls.grammar(), start=cls.start_rule())
28
- tree = parser.parse(text)
29
-
30
- class TreeToDict(Transformer):
31
- def __init__(self, field_mappings):
32
- self.field_mappings = field_mappings
33
-
34
- def __default__(self, data, children, meta):
35
- for field, rule in self.field_mappings.items():
36
- if data == rule:
37
- return {field: children[0]}
38
- return children
39
-
40
- def start(self, items):
41
- result = {}
42
- for item in items:
43
- if isinstance(item, dict):
44
- result.update(item)
45
- return result
46
-
47
- transformer = TreeToDict(cls.field_mappings())
48
- data = transformer.transform(tree)
49
- return cls(**data)
50
-
51
- def generate(self) -> str:
52
- parser = Lark(self.grammar(), start=self.start_rule())
53
-
54
- class ModelToString(Visitor):
55
- def __init__(self, model):
56
- self.model = model
57
- self.result = []
58
-
59
- def __default__(self, tree):
60
- if tree.data in self.model.field_mappings().values():
61
- field = next(
62
- k
63
- for k, v in self.model.field_mappings().items()
64
- if v == tree.data
65
- )
66
- value = getattr(self.model, field)
67
- self.result.append(f"{' '.join(tree.children)} {value}")
68
- else:
69
- for child in tree.children:
70
- if isinstance(child, str):
71
- self.result.append(child)
72
- else:
73
- self.visit(child)
74
-
75
- visitor = ModelToString(self)
76
- tree = parser.parse(" ".join(self.grammar().split()))
77
- visitor.visit(tree)
78
- return " ".join(visitor.result)
79
-
80
-
81
- class PersonSpec(GrammarBasedModel):
82
- name: str
83
- age: int
84
- city: str
85
-
86
- @classmethod
87
- def grammar(cls):
88
- return """
89
- start: "<spec>" name age city "</spec>"
90
- name: "name:" WORD
91
- age: "age" "is" NUMBER
92
- city: "lives" "in" WORD
93
- %import common.WORD
94
- %import common.NUMBER
95
- %import common.WS
96
- %ignore WS
97
- """
98
-
99
- @classmethod
100
- def start_rule(cls):
101
- return "start"
102
-
103
- @classmethod
104
- def field_mappings(cls):
105
- return {"name": "name", "age": "age", "city": "city"}
106
-
107
-
108
- if __name__ == "__main__":
109
- # Test parsing
110
- test_string = """
111
- <spec>
112
- name: John
113
- age is 30
114
- lives in Tokyo
115
- </spec>
116
- """
117
- parsed_person = PersonSpec.parse(test_string)
118
- print("Parsed person:", parsed_person)
119
-
120
- # Test generating
121
- new_person = PersonSpec(name="Alice", age=25, city="NewYork")
122
- generated_string = new_person.generate()
123
- print("\nGenerated string:")
124
- print(generated_string)
125
-
126
- # Test round-trip
127
- round_trip_person = PersonSpec.parse(generated_string)
128
- print("\nRound-trip parsed person:", round_trip_person)
129
-
130
- assert new_person == round_trip_person, "Round-trip parsing failed"
131
- print("\nRound-trip test passed!")
132
-
133
- # Test with modified grammar
134
- class ModifiedPersonSpec(PersonSpec):
135
- @classmethod
136
- def grammar(cls):
137
- return """
138
- start: "<person>" name age city "</person>"
139
- name: "Name:" WORD
140
- age: "Age:" NUMBER "years"
141
- city: "City:" WORD
142
- %import common.WORD
143
- %import common.NUMBER
144
- %import common.WS
145
- %ignore WS
146
- """
147
-
148
- modified_person = ModifiedPersonSpec(name="Bob", age=40, city="London")
149
- modified_string = modified_person.generate()
150
- print("\nModified grammar generated string:")
151
- print(modified_string)
152
-
153
- parsed_modified = ModifiedPersonSpec.parse(modified_string)
154
- print("Parsed modified person:", parsed_modified)
155
- assert modified_person == parsed_modified, "Modified grammar round-trip failed"
156
- print("Modified grammar round-trip test passed!")
@@ -1,153 +0,0 @@
1
- from pydantic import BaseModel
2
- from lark import Lark, Transformer, Visitor
3
- from abc import ABC, abstractmethod
4
- from typing import Dict, Any
5
-
6
- class GrammarBasedModel(BaseModel, ABC):
7
- @classmethod
8
- @abstractmethod
9
- def grammar(cls) -> str:
10
- pass
11
-
12
- @classmethod
13
- @abstractmethod
14
- def start_rule(cls) -> str:
15
- pass
16
-
17
- @classmethod
18
- @abstractmethod
19
- def field_mappings(cls) -> Dict[str, str]:
20
- pass
21
-
22
- @classmethod
23
- def parse(cls, text: str) -> 'GrammarBasedModel':
24
- parser = Lark(cls.grammar(), start=cls.start_rule())
25
- tree = parser.parse(text)
26
-
27
- class TreeToDict(Transformer):
28
- def __init__(self, field_mappings):
29
- self.field_mappings = field_mappings
30
-
31
- def __default__(self, data, children, meta):
32
- for field, rule in self.field_mappings.items():
33
- if data == rule:
34
- return {field: children[0]}
35
- return children
36
-
37
- def start(self, items):
38
- result = {}
39
- for item in items:
40
- if isinstance(item, dict):
41
- result.update(item)
42
- return result
43
-
44
- transformer = TreeToDict(cls.field_mappings())
45
- data = transformer.transform(tree)
46
- return cls(**data)
47
-
48
- def generate(self) -> str:
49
- parser = Lark(self.grammar(), start=self.start_rule())
50
-
51
- class ModelToString(Visitor):
52
- def __init__(self, model):
53
- self.model = model
54
- self.result = []
55
-
56
- def __default__(self, tree):
57
- if tree.data in self.model.field_mappings().values():
58
- field = next(k for k, v in self.model.field_mappings().items() if v == tree.data)
59
- value = getattr(self.model, field)
60
- self.result.append(f"{' '.join(tree.children)} {value}")
61
- else:
62
- for child in tree.children:
63
- if isinstance(child, str):
64
- self.result.append(child)
65
- else:
66
- self.visit(child)
67
-
68
- visitor = ModelToString(self)
69
- tree = parser.parse(" ".join(self.grammar().split()))
70
- visitor.visit(tree)
71
- return " ".join(visitor.result)
72
-
73
- class PersonSpec(GrammarBasedModel):
74
- name: str
75
- age: int
76
- city: str
77
-
78
- @classmethod
79
- def grammar(cls):
80
- return """
81
- start: "<spec>" name age city "</spec>"
82
- name: "name:" WORD
83
- age: "age" "is" NUMBER
84
- city: "lives" "in" WORD
85
- %import common.WORD
86
- %import common.NUMBER
87
- %import common.WS
88
- %ignore WS
89
- """
90
-
91
- @classmethod
92
- def start_rule(cls):
93
- return "start"
94
-
95
- @classmethod
96
- def field_mappings(cls):
97
- return {
98
- "name": "name",
99
- "age": "age",
100
- "city": "city"
101
- }
102
-
103
- if __name__ == "__main__":
104
- # Test parsing
105
- test_string = """
106
- <spec>
107
- name: John
108
- age is 30
109
- lives in Tokyo
110
- </spec>
111
- """
112
- parsed_person = PersonSpec.parse(test_string)
113
- print("Parsed person:", parsed_person)
114
-
115
- # Test generating
116
- new_person = PersonSpec(name="Alice", age=25, city="NewYork")
117
- generated_string = new_person.generate()
118
- print("\nGenerated string:")
119
- print(generated_string)
120
-
121
- # Test round-trip
122
- round_trip_person = PersonSpec.parse(generated_string)
123
- print("\nRound-trip parsed person:", round_trip_person)
124
-
125
- assert new_person == round_trip_person, "Round-trip parsing failed"
126
- print("\nRound-trip test passed!")
127
-
128
- # Test with modified grammar
129
- class ModifiedPersonSpec(PersonSpec):
130
- @classmethod
131
- def grammar(cls):
132
- return """
133
- start: "<person>" name age city "</person>"
134
- name: "Name:" WORD
135
- age: "Age:" NUMBER "years"
136
- city: "City:" WORD
137
- %import common.WORD
138
- %import common.NUMBER
139
- %import common.WS
140
- %ignore WS
141
- """
142
-
143
- modified_person = ModifiedPersonSpec(name="Bob", age=40, city="London")
144
- modified_string = modified_person.generate()
145
- print("\nModified grammar generated string:")
146
- print(modified_string)
147
-
148
- parsed_modified = ModifiedPersonSpec.parse(modified_string)
149
- print("Parsed modified person:", parsed_modified)
150
- assert modified_person == parsed_modified, "Modified grammar round-trip failed"
151
- print("Modified grammar round-trip test passed!")
152
-
153
-
@@ -1,86 +0,0 @@
1
- from abc import ABC, abstractmethod
2
-
3
- from parse import compile
4
-
5
- from langroid.pydantic_v1 import BaseModel
6
-
7
-
8
- class GrammarBasedModel(BaseModel, ABC):
9
- @classmethod
10
- @abstractmethod
11
- def grammar(cls) -> str:
12
- pass
13
-
14
- @classmethod
15
- def parse(cls, string: str):
16
- parser = compile(cls.grammar())
17
- result = parser.parse(string)
18
- if result is None:
19
- raise ValueError("Invalid string format")
20
- return cls(**result.named)
21
-
22
- def generate(self) -> str:
23
- return self.grammar().format(**self.dict())
24
-
25
- class Config:
26
- arbitrary_types_allowed = True
27
-
28
-
29
- class Person(GrammarBasedModel):
30
- name: str
31
- age: int
32
- city: str
33
-
34
- @classmethod
35
- def grammar(cls) -> str:
36
- return """
37
- {:s}<spec>{:s}
38
- {:s}name={name:S}{:s}
39
- {:s}age={age:d}{:s}
40
- {:s}city={city:S}{:s}
41
- {:s}</spec>{:s}
42
- """
43
-
44
-
45
- class SimpleFormat(GrammarBasedModel):
46
- key: str
47
- value: str
48
-
49
- @classmethod
50
- def grammar(cls) -> str:
51
- return "{:s}{key:S}{:s}:{:s}{value:S}{:s}"
52
-
53
-
54
- if __name__ == "__main__":
55
- # Test Person class
56
- input_string = """
57
- <spec>
58
- name=John Doe
59
- age=30
60
- city=New York
61
- </spec>
62
- """
63
-
64
- person = Person.parse(input_string)
65
- print("Parsed person:", person)
66
-
67
- generated_string = person.generate()
68
- print("Generated string:")
69
- print(generated_string)
70
-
71
- # Test SimpleFormat class
72
- simple_input = " Hello : World "
73
- simple = SimpleFormat.parse(simple_input)
74
- print("Parsed simple format:", simple)
75
-
76
- simple_generated = simple.generate()
77
- print("Generated simple format:", simple_generated)
78
-
79
- # Test with different whitespace
80
- input_string2 = "<spec>\nname=Jane Smith\n\n age=25\n\t\tcity=London\n</spec>"
81
- person2 = Person.parse(input_string2)
82
- print("Parsed person with different whitespace:", person2)
83
-
84
- generated_string2 = person2.generate()
85
- print("Generated string for person2:")
86
- print(generated_string2)
@@ -1,80 +0,0 @@
1
- from pydantic import BaseModel
2
- from abc import ABC, abstractmethod
3
- from parse import compile
4
-
5
- class GrammarBasedModel(BaseModel, ABC):
6
- @classmethod
7
- @abstractmethod
8
- def grammar(cls) -> str:
9
- pass
10
-
11
- @classmethod
12
- def parse(cls, string: str):
13
- parser = compile(cls.grammar())
14
- result = parser.parse(string)
15
- if result is None:
16
- raise ValueError("Invalid string format")
17
- return cls(**result.named)
18
-
19
- def generate(self) -> str:
20
- return self.grammar().format(**self.dict())
21
-
22
- class Config:
23
- arbitrary_types_allowed = True
24
-
25
- class Person(GrammarBasedModel):
26
- name: str
27
- age: int
28
- city: str
29
-
30
- @classmethod
31
- def grammar(cls) -> str:
32
- return """
33
- {:s}<spec>{:s}
34
- {:s}name={name:S}{:s}
35
- {:s}age={age:d}{:s}
36
- {:s}city={city:S}{:s}
37
- {:s}</spec>{:s}
38
- """
39
-
40
- class SimpleFormat(GrammarBasedModel):
41
- key: str
42
- value: str
43
-
44
- @classmethod
45
- def grammar(cls) -> str:
46
- return "{:s}{key:S}{:s}:{:s}{value:S}{:s}"
47
-
48
- if __name__ == "__main__":
49
- # Test Person class
50
- input_string = """
51
- <spec>
52
- name=John Doe
53
- age=30
54
- city=New York
55
- </spec>
56
- """
57
-
58
- person = Person.parse(input_string)
59
- print("Parsed person:", person)
60
-
61
- generated_string = person.generate()
62
- print("Generated string:")
63
- print(generated_string)
64
-
65
- # Test SimpleFormat class
66
- simple_input = " Hello : World "
67
- simple = SimpleFormat.parse(simple_input)
68
- print("Parsed simple format:", simple)
69
-
70
- simple_generated = simple.generate()
71
- print("Generated simple format:", simple_generated)
72
-
73
- # Test with different whitespace
74
- input_string2 = "<spec>\nname=Jane Smith\n\n age=25\n\t\tcity=London\n</spec>"
75
- person2 = Person.parse(input_string2)
76
- print("Parsed person with different whitespace:", person2)
77
-
78
- generated_string2 = person2.generate()
79
- print("Generated string for person2:")
80
- print(generated_string2)
@@ -1,129 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import ClassVar, Dict
3
-
4
- from parsimonious import Grammar, NodeVisitor
5
-
6
- from langroid.pydantic_v1 import BaseModel
7
-
8
-
9
- class GrammarBasedModel(BaseModel, ABC):
10
- grammar: ClassVar[str]
11
- start_token: ClassVar[str]
12
- end_token: ClassVar[str]
13
- field_mappings: ClassVar[Dict[str, str]]
14
-
15
- @classmethod
16
- @abstractmethod
17
- def get_grammar(cls) -> str:
18
- pass
19
-
20
- @classmethod
21
- def parse(cls, text: str) -> "GrammarBasedModel":
22
- grammar = Grammar(cls.get_grammar())
23
- tree = grammar.parse(text[len(cls.start_token) : -len(cls.end_token)].strip())
24
-
25
- class ModelVisitor(NodeVisitor):
26
- def __init__(self, field_mappings):
27
- self.field_mappings = field_mappings
28
- self.data = {}
29
-
30
- def generic_visit(self, node, visited_children):
31
- return visited_children or node.text
32
-
33
- def visit_start(self, node, visited_children):
34
- return self.data
35
-
36
- def __getattr__(self, name):
37
- if name.startswith("visit_"):
38
- field = name[6:]
39
- if field in self.field_mappings.values():
40
-
41
- def visit_method(node, visited_children):
42
- model_field = next(
43
- k for k, v in self.field_mappings.items() if v == field
44
- )
45
- self.data[model_field] = node.text.strip()
46
- return node.text
47
-
48
- return visit_method
49
- return super().__getattribute__(name)
50
-
51
- visitor = ModelVisitor(cls.field_mappings)
52
- model_dict = visitor.visit(tree)
53
- return cls(**model_dict)
54
-
55
- def generate(self) -> str:
56
- grammar = Grammar(self.get_grammar())
57
-
58
- class ModelGenerator(NodeVisitor):
59
- def __init__(self, model):
60
- self.model = model
61
-
62
- def generic_visit(self, node, visited_children):
63
- return "".join(filter(None, visited_children))
64
-
65
- def __getattr__(self, name):
66
- if name.startswith("visit_"):
67
- field = name[6:]
68
- if field in self.model.field_mappings.values():
69
- model_field = next(
70
- k
71
- for k, v in self.model.field_mappings.items()
72
- if v == field
73
- )
74
- return lambda node, children: str(
75
- getattr(self.model, model_field)
76
- )
77
- return lambda node, children: node.text
78
-
79
- generator = ModelGenerator(self)
80
- generated_content = generator.visit(grammar["start"])
81
- return f"{self.start_token}\n{generated_content}\n{self.end_token}"
82
-
83
-
84
- class PersonSpec(GrammarBasedModel):
85
- name: str
86
- age: int
87
- city: str
88
-
89
- grammar = r"""
90
- start = name_line age_line city_line
91
- name_line = "name:" ws name newline
92
- age_line = "age is" ws age newline
93
- city_line = "lives in" ws city newline?
94
- name = ~r"[^\n]+"
95
- age = ~r"\d+"
96
- city = ~r"[^\n]+"
97
- ws = ~r"\s+"
98
- newline = ~r"\n"
99
- """
100
- start_token = "<spec>"
101
- end_token = "</spec>"
102
- field_mappings = {"name": "name", "age": "age", "city": "city"}
103
-
104
- @classmethod
105
- def get_grammar(cls):
106
- return cls.grammar
107
-
108
-
109
- if __name__ == "__main__":
110
- # Test parsing
111
- input_str = """<spec>
112
- name: John Doe
113
- age is 30
114
- lives in New York
115
- </spec>"""
116
- person = PersonSpec.parse(input_str)
117
- print("Parsed person:", person)
118
-
119
- # Test generation
120
- generated_str = person.generate()
121
- print("\nGenerated string:")
122
- print(generated_str)
123
-
124
- # Test round-trip
125
- round_trip_person = PersonSpec.parse(generated_str)
126
- print("\nRound-trip parsed person:", round_trip_person)
127
-
128
- assert person == round_trip_person, "Round-trip parsing failed"
129
- print("\nRound-trip test passed!")