Skip to content

Commit 686c566

Browse files
committed
Working on Deserializer Generation
1 parent c203a12 commit 686c566

File tree

4 files changed

+221
-40
lines changed

4 files changed

+221
-40
lines changed

pylasu/lionweb/ast_generation.py

+14-40
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import ast
22
import keyword
33
from pathlib import Path
4-
from typing import cast, List, Dict
4+
from typing import List, Dict
55

6-
import astor # Install with `pip install astor`
7-
import click
6+
import astor
87
from lionwebpython.language import Language, Concept, Interface, Containment, Property
98
from lionwebpython.language.classifier import Classifier
109
from lionwebpython.language.enumeration import Enumeration
1110
from lionwebpython.language.primitive_type import PrimitiveType
1211
from lionwebpython.language.reference import Reference
1312
from lionwebpython.lionweb_version import LionWebVersion
14-
from lionwebpython.serialization.serialization_provider import SerializationProvider
1513

1614
from pylasu.lionweb.starlasu import StarLasuBaseLanguage
1715

@@ -54,28 +52,7 @@ def visit(name: str):
5452

5553
return sorted_list
5654

57-
@click.command()
58-
@click.argument("dependencies", nargs=-1, type=click.Path(exists=True, dir_okay=False, readable=True))
59-
@click.argument("lionweb-language", type=click.Path(exists=True, dir_okay=False, readable=True))
60-
@click.argument("output", type=click.Path(exists=False, file_okay=False, writable=True))
61-
def main(dependencies, lionweb_language, output):
62-
"""Simple CLI that processes a file and writes results to a directory."""
63-
serialization = SerializationProvider.get_standard_json_serialization(LionWebVersion.V2023_1)
64-
65-
for dep in dependencies:
66-
click.echo(f"Processing dependency {dep}")
67-
with open(dep, "r", encoding="utf-8") as f:
68-
content = f.read()
69-
language = cast(Language, serialization.deserialize_string_to_nodes(content)[0])
70-
serialization.register_language(language=language)
71-
serialization.classifier_resolver.register_language(language)
72-
serialization.instance_resolver.add_tree(language)
73-
74-
click.echo(f"📄 Processing file: {lionweb_language}")
75-
with open(lionweb_language, "r", encoding="utf-8") as f:
76-
content = f.read()
77-
language = cast(Language, serialization.deserialize_string_to_nodes(content)[0])
78-
55+
def ast_generation(click, language: Language, output):
7956
import_abc = ast.ImportFrom(
8057
module='abc',
8158
names=[ast.alias(name='ABC', asname=None)],
@@ -114,8 +91,8 @@ def main(dependencies, lionweb_language, output):
11491
names=[ast.alias(name='Node', asname=None)],
11592
level=0
11693
)
117-
module = ast.Module(body=[import_abc, import_dataclass, import_typing, import_enum, import_starlasu, import_node], type_ignores=[])
118-
94+
module = ast.Module(body=[import_abc, import_dataclass, import_typing, import_enum, import_starlasu, import_node],
95+
type_ignores=[])
11996

12097
for element in language.get_elements():
12198
if isinstance(element, Concept):
@@ -178,9 +155,9 @@ def main(dependencies, lionweb_language, output):
178155
bases.append('ABC')
179156
dataclass_decorator = ast.Name(id="dataclass", ctx=ast.Load())
180157
classdef = ast.ClassDef(classifier.get_name(), bases=bases,
181-
keywords=[],
182-
body=[ast.Pass()],
183-
decorator_list=[dataclass_decorator])
158+
keywords=[],
159+
body=[ast.Pass()],
160+
decorator_list=[dataclass_decorator])
184161

185162
for feature in classifier.get_features():
186163
if isinstance(feature, Containment):
@@ -238,25 +215,22 @@ def main(dependencies, lionweb_language, output):
238215

239216
module.body.append(classdef)
240217
elif isinstance(classifier, Interface):
241-
bases=[]
218+
bases = []
242219
if len(classifier.get_extended_interfaces()) == 0:
243220
bases.append("Node")
244221
bases.append("ABC")
245222

246223
classdef = ast.ClassDef(classifier.get_name(), bases=bases,
247-
keywords=[],
248-
body=[ast.Pass()],
249-
decorator_list=[])
224+
keywords=[],
225+
body=[ast.Pass()],
226+
decorator_list=[])
250227
module.body.append(classdef)
251228
else:
252229
raise ValueError()
253230

254-
click.echo(f"📂 Saving results to: {output}")
231+
click.echo(f"📂 Saving ast to: {output}")
255232
generated_code = astor.to_source(module)
256233
output_path = Path(output)
257234
output_path.mkdir(parents=True, exist_ok=True)
258235
with Path(f"{output}/ast.py").open("w", encoding="utf-8") as f:
259-
f.write(generated_code)
260-
261-
if __name__ == "__main__":
262-
main()
236+
f.write(generated_code)
+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import ast
2+
import keyword
3+
from pathlib import Path
4+
from typing import List, Dict
5+
6+
import astor
7+
from lionwebpython.language import Language, Concept, Interface, Containment, Property
8+
from lionwebpython.language.classifier import Classifier
9+
from lionwebpython.language.enumeration import Enumeration
10+
from lionwebpython.language.primitive_type import PrimitiveType
11+
from lionwebpython.language.reference import Reference
12+
from lionwebpython.lionweb_version import LionWebVersion
13+
14+
from pylasu.lionweb.starlasu import StarLasuBaseLanguage
15+
from pylasu.lionweb.utils import to_snake_case
16+
17+
18+
def make_cond(enumeration_name: str, member_name: str):
19+
return ast.Compare(
20+
left=ast.Name(id="serialized", ctx=ast.Load()),
21+
ops=[ast.Eq()],
22+
comparators=[
23+
ast.Attribute(
24+
value=ast.Attribute(
25+
value=ast.Name(id=enumeration_name, ctx=ast.Load()),
26+
attr=member_name,
27+
ctx=ast.Load()
28+
),
29+
attr="value",
30+
ctx=ast.Load()
31+
)
32+
]
33+
)
34+
35+
# The return: return AssignmentType.Add
36+
def make_return(enumeration_name: str, member_name: str):
37+
return ast.Return(
38+
value=ast.Attribute(
39+
value=ast.Name(id=enumeration_name, ctx=ast.Load()),
40+
attr=member_name,
41+
ctx=ast.Load()
42+
)
43+
)
44+
45+
46+
def deserializer_generation(click, language: Language, output):
47+
import_abc = ast.ImportFrom(
48+
module='abc',
49+
names=[ast.alias(name='ABC', asname=None)],
50+
level=0
51+
)
52+
import_dataclass = ast.ImportFrom(
53+
module='dataclasses',
54+
names=[ast.alias(name='dataclass', asname=None)],
55+
level=0
56+
)
57+
import_enum = ast.ImportFrom(
58+
module="enum",
59+
names=[ast.alias(name="Enum", asname=None)],
60+
level=0
61+
)
62+
import_typing = ast.ImportFrom(
63+
module='typing',
64+
names=[ast.alias(name='Optional', asname=None)],
65+
level=0
66+
)
67+
import_starlasu = ast.ImportFrom(
68+
module='pylasu.model.metamodel',
69+
names=[ast.alias(name='Expression', asname='StarLasuExpression'),
70+
ast.alias(name='PlaceholderElement', asname='StarLasuPlaceholderElement'),
71+
ast.alias(name='Named', asname='StarLasuNamed'),
72+
ast.alias(name='TypeAnnotation', asname='StarLasuTypeAnnotation'),
73+
ast.alias(name='Parameter', asname='StarLasuParameter'),
74+
ast.alias(name='Statement', asname='StarLasuStatement'),
75+
ast.alias(name='EntityDeclaration', asname='StarLasuEntityDeclaration'),
76+
ast.alias(name='BehaviorDeclaration', asname='StarLasuBehaviorDeclaration'),
77+
ast.alias(name='Documentation', asname='StarLasuDocumentation')],
78+
level=0
79+
)
80+
import_node = ast.ImportFrom(
81+
module='pylasu.model',
82+
names=[ast.alias(name='Node', asname=None)],
83+
level=0
84+
)
85+
import_ast = ast.ImportFrom(
86+
module='ast',
87+
names=[ast.alias(name=e.get_name(), asname=None) for e in language.get_elements() if not isinstance(e, PrimitiveType)],
88+
level=0
89+
)
90+
import_primitives = ast.ImportFrom(
91+
module='primitive_types',
92+
names=[ast.alias(name=e.get_name(), asname=None) for e in language.get_elements() if isinstance(e, PrimitiveType)],
93+
level=0
94+
)
95+
module = ast.Module(body=[import_abc, import_dataclass, import_typing, import_enum, import_starlasu, import_node,
96+
import_ast, import_primitives],
97+
type_ignores=[])
98+
99+
100+
101+
for e in language.get_elements():
102+
if isinstance(e, Enumeration):
103+
arg_serialized = ast.arg(arg="serialized", annotation=ast.Name(id="str", ctx=ast.Load()))
104+
# The raise: raise ValueError(f"...")
105+
raise_stmt = ast.Raise(
106+
exc=ast.Call(
107+
func=ast.Name(id="ValueError", ctx=ast.Load()),
108+
args=[
109+
ast.JoinedStr(values=[
110+
ast.Constant(value=f"Invalid value for {e.get_name()}: "),
111+
ast.FormattedValue(
112+
value=ast.Name(id="serialized", ctx=ast.Load()),
113+
conversion=-1
114+
)
115+
])
116+
],
117+
keywords=[]
118+
),
119+
cause=None
120+
)
121+
# The function body
122+
literals = e.get_literals()
123+
current_if = ast.If(
124+
test=make_cond(e.get_name(), literals[0].get_name()),
125+
body=[make_return(e.get_name(), literals[0].get_name())],
126+
orelse=[]
127+
)
128+
root_if = current_if
129+
130+
for literal in literals[1:]:
131+
next_if = ast.If(
132+
test=make_cond(e.get_name(), literal.get_name()),
133+
body=[make_return(e.get_name(), literal.get_name())],
134+
orelse=[]
135+
)
136+
current_if.orelse = [next_if]
137+
current_if = next_if
138+
139+
# Final else
140+
current_if.orelse = [raise_stmt]
141+
142+
# Function definition
143+
func_def = ast.FunctionDef(
144+
name=f"_deserialize_{to_snake_case(e.get_name())}",
145+
args=ast.arguments(
146+
posonlyargs=[],
147+
args=[arg_serialized],
148+
kwonlyargs=[],
149+
kw_defaults=[],
150+
defaults=[]
151+
),
152+
body=[root_if],
153+
decorator_list=[],
154+
returns=ast.Constant(value=e.get_name())
155+
)
156+
module.body.append(func_def)
157+
158+
generated_code = astor.to_source(module)
159+
output_path = Path(output)
160+
output_path.mkdir(parents=True, exist_ok=True)
161+
click.echo(f"📂 Saving deserializer to: {output}")
162+
with Path(f"{output}/deserializer.py").open("w", encoding="utf-8") as f:
163+
f.write(generated_code)

pylasu/lionweb/generator.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from typing import cast
2+
3+
import click
4+
from lionwebpython.language import Language
5+
from lionwebpython.lionweb_version import LionWebVersion
6+
from lionwebpython.serialization.serialization_provider import SerializationProvider
7+
8+
9+
@click.command()
10+
@click.argument("dependencies", nargs=-1, type=click.Path(exists=True, dir_okay=False, readable=True))
11+
@click.argument("lionweb-language", type=click.Path(exists=True, dir_okay=False, readable=True))
12+
@click.argument("output", type=click.Path(exists=False, file_okay=False, writable=True))
13+
def main(dependencies, lionweb_language, output):
14+
from pylasu.lionweb.ast_generation import ast_generation
15+
from pylasu.lionweb.deserializer_generation import deserializer_generation
16+
"""Simple CLI that processes a file and writes results to a directory."""
17+
serialization = SerializationProvider.get_standard_json_serialization(LionWebVersion.V2023_1)
18+
19+
for dep in dependencies:
20+
click.echo(f"Processing dependency {dep}")
21+
with open(dep, "r", encoding="utf-8") as f:
22+
content = f.read()
23+
language = cast(Language, serialization.deserialize_string_to_nodes(content)[0])
24+
serialization.register_language(language=language)
25+
serialization.classifier_resolver.register_language(language)
26+
serialization.instance_resolver.add_tree(language)
27+
28+
click.echo(f"📄 Processing file: {lionweb_language}")
29+
with open(lionweb_language, "r", encoding="utf-8") as f:
30+
content = f.read()
31+
language = cast(Language, serialization.deserialize_string_to_nodes(content)[0])
32+
ast_generation(click, language, output)
33+
deserializer_generation(click, language, output)
34+
35+
36+
if __name__ == "__main__":
37+
main()

pylasu/lionweb/utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import re
2+
3+
def to_snake_case(name: str) -> str:
4+
# Replace capital letters with _lowercase, except at the beginning
5+
name = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name)
6+
name = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', name)
7+
return name.lower()

0 commit comments

Comments
 (0)