Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f40901f

Browse files
author
Paolo Tranquilli
committed
Rust: archiving + skeleton def translator
1 parent 2a2b79e commit f40901f

16 files changed

Lines changed: 914 additions & 75 deletions

File tree

misc/codegen/codegen.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def _parse_args() -> argparse.Namespace:
3030

3131
p = argparse.ArgumentParser(description="Code generation suite")
3232
p.add_argument("--generate", type=lambda x: x.split(","),
33-
help="specify what targets to generate as a comma separated list, choosing among dbscheme, ql, trap "
34-
"and cpp")
33+
help="specify what targets to generate as a comma separated list, choosing among dbscheme, ql, "
34+
"trap, cpp and rust")
3535
p.add_argument("--verbose", "-v", action="store_true", help="print more information")
3636
p.add_argument("--quiet", "-q", action="store_true", help="only print errors")
3737
p.add_argument("--configuration-file", "-c", type=_abspath, default=conf,
@@ -57,6 +57,9 @@ def _parse_args() -> argparse.Namespace:
5757
p.add_argument("--cpp-output",
5858
help="output directory for generated C++ files, required if trap or cpp is provided to "
5959
"--generate"),
60+
p.add_argument("--rust-output",
61+
help="output directory for generated Rust files, required if rust is provided to "
62+
"--generate"),
6063
p.add_argument("--generated-registry",
6164
help="registry file containing information about checked-in generated code. A .gitattributes"
6265
"file is generated besides it to mark those files with linguist-generated=true. Must"

misc/codegen/generators/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from . import dbschemegen, qlgen, trapgen, cppgen
1+
from . import dbschemegen, qlgen, trapgen, cppgen, rustgen
22

33

44
def generate(target, opts, renderer):

misc/codegen/generators/rustgen.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Rust trap class generation
3+
"""
4+
5+
import functools
6+
import typing
7+
8+
import inflection
9+
10+
from misc.codegen.lib import rust, schema
11+
from misc.codegen.loaders import schemaloader
12+
13+
14+
def _get_type(t: str) -> str:
15+
match t:
16+
case None | "boolean": # None means a predicate
17+
return "bool"
18+
case "string":
19+
return "String"
20+
case "int":
21+
return "i32"
22+
case _ if t[0].isupper():
23+
return "TrapLabel"
24+
case _:
25+
return t
26+
27+
28+
def _get_field(cls: schema.Class, p: schema.Property) -> rust.Field:
29+
table_name = None
30+
if not p.is_single:
31+
table_name = f"{cls.name}_{p.name}"
32+
if p.is_predicate:
33+
table_name = inflection.underscore(table_name)
34+
else:
35+
table_name = inflection.tableize(table_name)
36+
args = dict(
37+
field_name=p.name + ("_" if p.name in rust.keywords else ""),
38+
base_type=_get_type(p.type),
39+
is_optional=p.is_optional,
40+
is_repeated=p.is_repeated,
41+
is_predicate=p.is_predicate,
42+
is_unordered=p.is_unordered,
43+
table_name=table_name,
44+
)
45+
args.update(rust.get_field_override(p.name))
46+
return rust.Field(**args)
47+
48+
49+
def _get_properties(
50+
cls: schema.Class, lookup: dict[str, schema.Class]
51+
) -> typing.Iterable[schema.Property]:
52+
for b in cls.bases:
53+
yield from _get_properties(lookup[b], lookup)
54+
yield from cls.properties
55+
56+
57+
class Processor:
58+
def __init__(self, data: schema.Schema):
59+
self._classmap = data.classes
60+
61+
def _get_class(self, name: str) -> rust.Class:
62+
cls = self._classmap[name]
63+
return rust.Class(
64+
name=name,
65+
fields=[
66+
_get_field(cls, p)
67+
for p in _get_properties(cls, self._classmap)
68+
if "rust_skip" not in p.pragmas and not p.synth
69+
],
70+
table_name=inflection.tableize(cls.name),
71+
)
72+
73+
def get_classes(self):
74+
ret = {"": []}
75+
for k, cls in self._classmap.items():
76+
if not cls.synth and not cls.derived:
77+
ret.setdefault(cls.group, []).append(self._get_class(cls.name))
78+
return ret
79+
80+
81+
def generate(opts, renderer):
82+
assert opts.rust_output
83+
processor = Processor(schemaloader.load_file(opts.schema))
84+
out = opts.rust_output
85+
groups = set()
86+
for group, classes in processor.get_classes().items():
87+
group = group or "top"
88+
groups.add(group)
89+
renderer.render(
90+
rust.ClassList(
91+
classes,
92+
opts.schema,
93+
),
94+
out / f"{group}.rs",
95+
)
96+
renderer.render(
97+
rust.ModuleList(
98+
groups,
99+
opts.schema,
100+
),
101+
out / f"mod.rs",
102+
)

misc/codegen/lib/rust.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import dataclasses
2+
import re
3+
import typing
4+
5+
# taken from https://doc.rust-lang.org/reference/keywords.html
6+
keywords = {
7+
"as",
8+
"break",
9+
"const",
10+
"continue",
11+
"crate",
12+
"else",
13+
"enum",
14+
"extern",
15+
"false",
16+
"fn",
17+
"for",
18+
"if",
19+
"impl",
20+
"in",
21+
"let",
22+
"loop",
23+
"match",
24+
"mod",
25+
"move",
26+
"mut",
27+
"pub",
28+
"ref",
29+
"return",
30+
"self",
31+
"Self",
32+
"static",
33+
"struct",
34+
"super",
35+
"trait",
36+
"true",
37+
"type",
38+
"unsafe",
39+
"use",
40+
"where",
41+
"while",
42+
"async",
43+
"await",
44+
"dyn",
45+
"abstract",
46+
"become",
47+
"box",
48+
"do",
49+
"final",
50+
"macro",
51+
"override",
52+
"priv",
53+
"typeof",
54+
"unsized",
55+
"virtual",
56+
"yield",
57+
"try",
58+
}
59+
60+
_field_overrides = [
61+
(
62+
re.compile(r"(start|end)_(line|column)|(.*_)?index|width|num_.*"),
63+
{"base_type": "usize"},
64+
),
65+
(re.compile(r"(.*)_"), lambda m: {"field_name": m[1]}),
66+
]
67+
68+
69+
def get_field_override(field: str):
70+
for r, o in _field_overrides:
71+
m = r.fullmatch(field)
72+
if m:
73+
return o(m) if callable(o) else o
74+
return {}
75+
76+
77+
@dataclasses.dataclass
78+
class Field:
79+
field_name: str
80+
base_type: str
81+
table_name: str = None
82+
is_optional: bool = False
83+
is_repeated: bool = False
84+
is_unordered: bool = False
85+
is_predicate: bool = False
86+
first: bool = False
87+
88+
def __post_init__(self):
89+
if self.field_name in keywords:
90+
self.field_name += "_"
91+
92+
@property
93+
def type(self) -> str:
94+
type = self.base_type
95+
if self.is_optional:
96+
type = f"Option<{type}>"
97+
if self.is_repeated:
98+
type = f"Vec<{type}>"
99+
return type
100+
101+
# using @property breaks pystache internals here
102+
def emitter(self):
103+
if self.type == "String":
104+
return lambda x: f"quoted(&{x})"
105+
else:
106+
return lambda x: x
107+
108+
@property
109+
def is_single(self):
110+
return not (self.is_optional or self.is_repeated or self.is_predicate)
111+
112+
@property
113+
def is_label(self):
114+
return self.base_type == "TrapLabel"
115+
116+
117+
@dataclasses.dataclass
118+
class Class:
119+
name: str
120+
table_name: str
121+
fields: list[Field] = dataclasses.field(default_factory=list)
122+
123+
@property
124+
def single_fields(self):
125+
return [f for f in self.fields if f.is_single]
126+
127+
128+
@dataclasses.dataclass
129+
class ClassList:
130+
template: typing.ClassVar[str] = "rust_classes"
131+
132+
classes: list[Class]
133+
source: str
134+
135+
136+
@dataclasses.dataclass
137+
class ModuleList:
138+
template: typing.ClassVar[str] = "rust_module"
139+
140+
modules: list[str]
141+
source: str
File renamed without changes.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// generated by {{generator}}
2+
3+
use crate::trap::{TrapLabel, TrapEntry, quoted};
4+
use std::io::Write;
5+
6+
{{#classes}}
7+
#[derive(Debug)]
8+
pub struct {{name}} {
9+
pub key: Option<String>,
10+
{{#fields}}
11+
pub {{field_name}}: {{type}},
12+
{{/fields}}
13+
}
14+
15+
impl TrapEntry for {{name}} {
16+
type Label = TrapLabel;
17+
18+
fn prefix() -> &'static str { "{{name}}_" }
19+
20+
fn key(&self) -> Option<&str> { self.key.as_ref().map(String::as_str) }
21+
22+
fn emit<W: Write>(&self, id: &Self::Label, out: &mut W) -> std::io::Result<()> {
23+
write!(out, "{{table_name}}({id}{{#single_fields}}, {}{{/single_fields}})\n"{{#single_fields}}, {{#emitter}}self.{{field_name}}{{/emitter}}{{/single_fields}})?;
24+
{{#fields}}
25+
{{#is_predicate}}
26+
if self.{{field_name}} {
27+
write!(out, "{{table_name}}({id})\n")?;
28+
}
29+
{{/is_predicate}}
30+
{{#is_optional}}
31+
{{^is_repeated}}
32+
if let Some(ref v) = &self.{{field_name}} {
33+
write!(out, "{{table_name}}({id}, {})\n", {{#emitter}}v{{/emitter}})?;
34+
}
35+
{{/is_repeated}}
36+
{{/is_optional}}
37+
{{#is_repeated}}
38+
for (i, &ref v) in self.{{field_name}}.iter().enumerate() {
39+
{{^is_optional}}
40+
write!(out, "{{table_name}}({id}, {{^is_unordered}}{}, {{/is_unordered}}{})\n", {{^is_unordered}}i, {{/is_unordered}}{{#emitter}}v{{/emitter}})?;
41+
{{/is_optional}}
42+
{{#is_optional}}
43+
if let Some(ref vv) = &v {
44+
write!(out, "{{table_name}}({id}, {{^is_unordered}}{}, {{/is_unordered}}{})\n", {{^is_unordered}}i, {{/is_unordered}}{{#emitter}}vv{{/emitter}})?;
45+
}
46+
{{/is_optional}}
47+
}
48+
{{/is_repeated}}
49+
{{/fields}}
50+
Ok(())
51+
}
52+
}
53+
54+
{{/classes}}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// generated by {{generator}}
2+
3+
{{#modules}}
4+
mod {{.}};
5+
pub use {{.}}::*;
6+
7+
{{/modules}}

rust/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
exports_files([
2+
"codegen.conf",
3+
"schema.py",
4+
])

0 commit comments

Comments
 (0)