Skip to content

Commit d101cfa

Browse files
committed
Add VIDL_STORAGE annotation for per-parameter storage type overrides
1 parent dc582c6 commit d101cfa

3 files changed

Lines changed: 276 additions & 13 deletions

File tree

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,38 @@ struct VIDLHandler
6060
};
6161
```
6262

63+
## Storage-type overrides
64+
65+
By default, Vidl strips references from parameter types when generating struct
66+
members (so `const std::vector<int>&` becomes `std::vector`). The
67+
`// VIDL_STORAGE` annotation lets you override the stored type for any
68+
individual parameter — useful when you want to avoid heap copies of large
69+
arguments.
70+
71+
```cpp
72+
// VIDL_GENERATE
73+
// VIDL_STORAGE: uniforms = vhArenaSpan< vhArenaUniformValue >
74+
void vhCmdSetStateUniforms(
75+
vhStateId id,
76+
const std::vector< vhState::UniformBufferValue >& uniforms );
77+
```
78+
79+
The generated struct stores `vhArenaSpan< … >` for `uniforms` and the ctor
80+
accepts that span type verbatim. The public function signature is unchanged;
81+
the hand-written implementation is responsible for converting the `vector` to
82+
a span before constructing the VIDL struct.
83+
84+
**Syntax rules:**
85+
86+
- Place each `// VIDL_STORAGE: <param_name> = <storage_type>` line between
87+
`// VIDL_GENERATE` and the function declaration it applies to.
88+
- One annotation line per parameter; multiple lines are allowed to override
89+
several parameters.
90+
- Annotations are scoped to the immediately following function only — they
91+
do not carry over to subsequent `// VIDL_GENERATE` blocks.
92+
- References an unknown parameter name → `ValueError`.
93+
- Annotates the same parameter twice → `ValueError`.
94+
6395
## Usage
6496
6597
You can use `vidl.py` directly to generate code from a source file.

test.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,5 +271,163 @@ def test_regression_reference_members(self):
271271
# The constructor parameter SHOULD be a reference
272272
self.assertIn("VIDL_vhBeginMarker(const std::string& _name)", output)
273273

274+
275+
class TestVidlStorage(unittest.TestCase):
276+
277+
def test_no_annotation_unchanged(self):
278+
src = """
279+
// VIDL_GENERATE
280+
void foo( int a, std::vector<int> b );
281+
"""
282+
out = vidl.generate_source(src)
283+
self.assertIn("int a;", out)
284+
self.assertIn("std::vector<int> b;", out)
285+
self.assertIn("foo(int _a, std::vector<int> _b)", out)
286+
287+
def test_single_storage_override(self):
288+
src = """
289+
// VIDL_GENERATE
290+
// VIDL_STORAGE: b = MySpan<int>
291+
void foo( int a, std::vector<int> b );
292+
"""
293+
out = vidl.generate_source(src)
294+
self.assertIn("int a;", out)
295+
self.assertIn("MySpan<int> b;", out)
296+
self.assertNotIn("std::vector<int> b;", out)
297+
self.assertIn("foo(int _a, MySpan<int> _b)", out)
298+
299+
def test_multiple_overrides_one_func(self):
300+
src = """
301+
// VIDL_GENERATE
302+
// VIDL_STORAGE: a = ArenaA
303+
// VIDL_STORAGE: b = ArenaB
304+
void foo( int a, int b, int c );
305+
"""
306+
out = vidl.generate_source(src)
307+
self.assertIn("ArenaA a;", out)
308+
self.assertIn("ArenaB b;", out)
309+
self.assertIn("int c;", out)
310+
self.assertIn("foo(ArenaA _a, ArenaB _b, int _c)", out)
311+
312+
def test_override_with_template_args(self):
313+
src = """
314+
// VIDL_GENERATE
315+
// VIDL_STORAGE: data = vhArenaSpan< glm::mat4 >
316+
void foo( vhStateId id, std::vector< glm::mat4 > data );
317+
"""
318+
out = vidl.generate_source(src)
319+
self.assertIn("vhArenaSpan< glm::mat4 > data;", out)
320+
self.assertIn("vhStateId _id, vhArenaSpan< glm::mat4 > _data", out)
321+
322+
def test_whitespace_tolerance(self):
323+
src = """
324+
// VIDL_GENERATE
325+
//VIDL_STORAGE:b=MyType
326+
void foo( int a, int b );
327+
"""
328+
out = vidl.generate_source(src)
329+
self.assertIn("MyType b;", out)
330+
331+
def test_annotation_scoped_to_one_block(self):
332+
src = """
333+
// VIDL_GENERATE
334+
// VIDL_STORAGE: x = OverrideA
335+
void foo( int x );
336+
337+
// VIDL_GENERATE
338+
void bar( int x );
339+
"""
340+
out = vidl.generate_source(src)
341+
self.assertIn("OverrideA x;", out)
342+
foo_idx = out.index("VIDL_foo")
343+
bar_idx = out.index("VIDL_bar")
344+
bar_section = out[bar_idx:]
345+
self.assertNotIn("OverrideA", bar_section)
346+
self.assertIn("int x;", bar_section)
347+
348+
def test_keyword_param_name_with_override(self):
349+
src = """
350+
// VIDL_GENERATE
351+
// VIDL_STORAGE: class = MyClass
352+
void foo( int class );
353+
"""
354+
out = vidl.generate_source(src)
355+
self.assertIn("MyClass class_;", out)
356+
self.assertIn("MyClass _class_", out)
357+
358+
def test_magic_constants_stable_with_override(self):
359+
src_no = "// VIDL_GENERATE\nvoid foo( std::vector<int> a );"
360+
src_yes = (
361+
"// VIDL_GENERATE\n"
362+
"// VIDL_STORAGE: a = MySpan\n"
363+
"void foo( std::vector<int> a );"
364+
)
365+
m1 = re.search(r"kMagic = (\w+)", vidl.generate_source(src_no)).group(1)
366+
m2 = re.search(r"kMagic = (\w+)", vidl.generate_source(src_yes)).group(1)
367+
self.assertEqual(m1, m2)
368+
369+
def test_unknown_param_name_raises(self):
370+
src = """
371+
// VIDL_GENERATE
372+
// VIDL_STORAGE: typo = MyType
373+
void foo( int a );
374+
"""
375+
with self.assertRaises(ValueError) as ctx:
376+
vidl.generate_source(src)
377+
msg = str(ctx.exception)
378+
self.assertIn("typo", msg)
379+
self.assertIn("foo", msg)
380+
self.assertIn("a", msg)
381+
382+
def test_duplicate_annotation_raises(self):
383+
src = """
384+
// VIDL_GENERATE
385+
// VIDL_STORAGE: x = TypeA
386+
// VIDL_STORAGE: x = TypeB
387+
void foo( int x );
388+
"""
389+
with self.assertRaises(ValueError) as ctx:
390+
vidl.generate_source(src)
391+
msg = str(ctx.exception)
392+
self.assertIn("duplicate", msg)
393+
self.assertIn("x", msg)
394+
self.assertIn("foo", msg)
395+
396+
def test_default_value_preserved_with_override(self):
397+
src = """
398+
// VIDL_GENERATE
399+
// VIDL_STORAGE: x = MyType
400+
void foo( int x = 5 );
401+
"""
402+
out = vidl.generate_source(src)
403+
self.assertIn("MyType x = 5;", out)
404+
405+
def test_override_only_named_param_affected(self):
406+
src = """
407+
// VIDL_GENERATE
408+
// VIDL_STORAGE: b = Overridden
409+
void foo( std::vector<int> a, std::vector<int> b, std::vector<float> c );
410+
"""
411+
out = vidl.generate_source(src)
412+
self.assertIn("std::vector<int> a;", out)
413+
self.assertIn("Overridden b;", out)
414+
self.assertIn("std::vector<float> c;", out)
415+
self.assertNotIn("std::vector<int> b;", out)
416+
417+
def test_ctor_param_uses_override_not_reference(self):
418+
# Without override, a reference param like `const std::vector<int>& v`
419+
# strips the & for the member type but keeps it in the ctor.
420+
# With an override the ctor must use the override type verbatim.
421+
src = """
422+
// VIDL_GENERATE
423+
// VIDL_STORAGE: v = MySpan
424+
void foo( const std::vector<int>& v );
425+
"""
426+
out = vidl.generate_source(src)
427+
self.assertIn("MySpan v;", out)
428+
self.assertIn("MySpan _v", out)
429+
self.assertNotIn("MySpan& _v", out)
430+
431+
274432
if __name__ == '__main__':
275433
unittest.main()

vidl.py

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@
2121

2222
# WARNING: Vibe code generation tool ahead. Read at your own risk of sanity.
2323

24+
# ----------------------------------------------------------------------------
25+
# // VIDL_STORAGE — per-parameter storage type override
26+
# ----------------------------------------------------------------------------
27+
#
28+
# Form:
29+
#
30+
# // VIDL_GENERATE
31+
# // VIDL_STORAGE: <param_name> = <storage_type>
32+
# ReturnType funcname( ... );
33+
#
34+
# Replaces the generated struct member type and matching ctor argument for
35+
# <param_name> with <storage_type>, verbatim. The function declaration itself
36+
# is left alone — converting the original argument into the storage type is
37+
# the caller's job. Motivating case: letting a setter that takes
38+
# `const std::vector<X>&` store a non-owning arena span, so enqueueing the
39+
# command does not heap-copy the vector.
40+
#
41+
# Multiple lines per block are allowed (one per overridden param). Scope is
42+
# strictly the next `// VIDL_GENERATE` block. Naming an unknown parameter, or
43+
# annotating the same parameter twice, raises `ValueError`.
44+
# ----------------------------------------------------------------------------
45+
2446
import os, sys, re, hashlib
2547

2648
def generate_magic(name):
@@ -96,48 +118,95 @@ def parse_function(text):
96118
}
97119

98120
def generate_source(content):
99-
# Find all // VIDL_GENERATE
100-
# Then find the function signature that follows
101-
# We look for // VIDL_GENERATE and then everything until a semicolon
121+
"""Parse *content* for // VIDL_GENERATE blocks and return a string
122+
of generated C++ structs plus a VIDLHandler dispatcher.
123+
124+
Supports per-parameter storage-type overrides via // VIDL_STORAGE
125+
annotations placed between // VIDL_GENERATE and the function declaration.
126+
See the top-of-file comment block in vidl.py for the full syntax.
127+
128+
Raises ``ValueError`` when a VIDL_STORAGE annotation references an
129+
unknown parameter name or when the same parameter is annotated twice
130+
within a single block.
131+
"""
102132
pattern = r'//\s*VIDL_GENERATE(.*?);'
103133
matches = re.findall(pattern, content, re.DOTALL)
104134

105135
generated_structs = []
106136
handler_funcs = []
107137

108138
for m in matches:
139+
# Collect VIDL_STORAGE annotations into a list *before* stripping
140+
# comments so that we can validate duplicates and unknown params
141+
# once we know the function's parameter names.
142+
storage_annotations = []
143+
for sm in re.finditer(
144+
r'//\s*VIDL_STORAGE\s*:\s*(\w+)\s*=\s*(.+)', m):
145+
storage_annotations.append(
146+
(sm.group(1).strip(), sm.group(2).strip()))
147+
109148
# Strip comments from the captured block
110149
m = re.sub(r'//.*', '', m)
111150
m = re.sub(r'/\*.*?\*/', '', m, flags=re.DOTALL)
112-
151+
113152
func = parse_function(m)
114153
if not func:
115154
continue
116-
155+
156+
# Validate annotations and build storage_overrides dict
157+
valid_names = {p['orig_name'] for p in func['params']}
158+
valid_names |= {p['name'] for p in func['params']}
159+
160+
storage_overrides = {}
161+
seen_params = set()
162+
for param_key, storage_type in storage_annotations:
163+
if param_key in seen_params:
164+
raise ValueError(
165+
f"VIDL_STORAGE: duplicate annotation for parameter "
166+
f"'{param_key}' in function '{func['name']}'"
167+
)
168+
seen_params.add(param_key)
169+
if param_key not in valid_names:
170+
orig_names = ', '.join(
171+
p['orig_name'] for p in func['params'])
172+
raise ValueError(
173+
f"VIDL_STORAGE: parameter '{param_key}' not found "
174+
f"in function '{func['name']}' "
175+
f"(params: {orig_names})"
176+
)
177+
storage_overrides[param_key] = storage_type
178+
117179
magic = generate_magic(func['name'])
118180
handler_funcs.append({'name': func['name'], 'magic': magic})
119-
181+
120182
# Generate struct
121183
struct_lines = [f"struct VIDL_{func['name']}", "{"]
122184
struct_lines.append(f" static constexpr uint64_t kMagic = {magic};")
123185
struct_lines.append(" uint64_t MAGIC = kMagic;")
124-
186+
125187
ctor_params = []
126188
initializer_list = []
127-
189+
128190
for p in func['params']:
129-
member_type = p['type'].replace('&', '').strip()
191+
override = storage_overrides.get(p['orig_name']) or storage_overrides.get(p['name'])
192+
if override is not None:
193+
# Use the override storage type for both the struct member and the ctor param.
194+
member_type = override
195+
ctor_param_type = override
196+
else:
197+
member_type = p['type'].replace('&', '').strip()
198+
ctor_param_type = p['type']
130199
default_str = f" = {p['default']}" if p.get('default') else ""
131200
struct_lines.append(f" {member_type} {p['name']}{default_str};")
132201
# For constructor: type _name
133-
ctor_params.append(f"{p['type']} _{p['name']}")
202+
ctor_params.append(f"{ctor_param_type} _{p['name']}")
134203
# For initializer: name(_name)
135204
initializer_list.append(f"{p['name']}(_{p['name']})")
136-
205+
137206
# Default constructor
138207
struct_lines.append("")
139208
struct_lines.append(f" VIDL_{func['name']}() = default;")
140-
209+
141210
# Parameterized constructor
142211
if ctor_params:
143212
struct_lines.append("")
@@ -191,7 +260,11 @@ def main():
191260
with open(input_file, 'r', encoding='utf-8') as f:
192261
content = f.read()
193262

194-
result = generate_source(content)
263+
try:
264+
result = generate_source(content)
265+
except ValueError as e:
266+
print(f"vidl.py error: {e}", file=sys.stderr)
267+
sys.exit(1)
195268

196269
if len(sys.argv) >= 3:
197270
output_file = sys.argv[2]

0 commit comments

Comments
 (0)