-
Notifications
You must be signed in to change notification settings - Fork 104
/
zproject_class_api.gsl
689 lines (640 loc) · 22.7 KB
/
zproject_class_api.gsl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
# Process class API files in api/*.api
#
# This is a code generator built using the iMatix GSL code generation
# language. See https://github.com/zeromq/gsl for details.
#
# Copyright (c) the Contributors as noted in the AUTHORS file.
# This file is part of zproject.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# copy&paste from zproject_redhat.gsl
for project.target where ( target.name = "*" | target.name = "python_cffi" > 0 )
python_cffi = 1
endfor
# Resolve filename for each class API (undefined if the file is not found)
for project.class
class.private ?= "0"
if defined (class.api)
if !file.exists (class.api)
echo "Could not read API file '$(class.api)' for '$(class.name)'"
class.api = # undefined
endif
else
if file.exists ("api/$(class.name:c).api")
class.api = "api/$(class.name:c).api"
elsif file.exists ("api/$(class.name:c).xml")
class.api = "api/$(class.name:c).xml"
elsif file.exists ("/usr/local/share/zproject/$(class.name:c).api")
class.api = "/usr/local/share/zproject/$(class.name:c).api"
elsif file.exists ("/usr/share/zproject/$(class.name:c).api")
class.api = "/usr/share/zproject/$(class.name:c).api"
endif
endif
endfor
if python_cffi ?= 1
# Resolve python_cffi slurps
for project.use
if file.exists ("../$(use.project:c)/api/python_cffi.slurp")
use.python_cffi_slurp = "../$(use.project:c)/api/python_cffi.slurp"
elsif file.exists ("tmp-deps/$(use.project:c)/api/python_cffi.slurp")
use.python_cffi_slurp = "tmp-deps/$(use.project:c)/api/python_cffi.slurp"
elsif file.exists ("/usr/local/share/zproject/$(use.project:c)/python_cffi.slurp")
use.python_cffi_slurp = "/usr/local/share/zproject/$(use.project:c)/python_cffi.slurp"
elsif file.exists ("/usr/share/zproject/$(use.project:c)/python_cffi.slurp")
use.python_cffi_slurp = "/usr/share/zproject/$(use.project:c)/python_cffi.slurp"
endif
endfor
endif
# Replace each class item with the class model from its API file (if any)
for project.class where defined (class.api)
new_class = XML.load_file (class.api)?
if !defined (new_class)
echo "Error loading api file: $(class.api): $(xml.error?)"
class.api = # undefined
else
new_class.api = class.api
new_class.api_dir = directory.resolve ('api/')
new_class.private = class.private
new_class.scope = class.scope
new_class.state = class.state? new_class.state?
new_class.selftest ?= 1
move new_class after class
delete class
endif
endfor
#
# Set the API state and draft attribute of an element
#
function set_state (element, default)
my.element.state ?= my.default
if my.element.state = "stable" \
| my.element.state = "legacy" \
| my.element.state = "deprecated" \
| my.element.private ?= 1
my.element.draft = 0
elsif my.element.stable ?= 0 | my.element.stable ?= 1
echo "LEGACY HACK WARNING: Usage of 'stable' boolean attribute in $(name (my.element)) is deprecated, please choose a 'state' instead"
if my.element.stable ?= 0
my.element.draft = 1
my.element.state = "draft"
else
my.element.draft = 0
my.element.state = "stable"
endif
elsif my.element.state = "draft"
my.element.draft = 1
else
abort "E: $(name (my.element)).state must be draft/stable/legacy/deprecated (is '$(my.element.state)')"
endif
endfunction
# Resolve missing or implicit details in the given container.
#
# Here, "container" refers to an <argument/> or <return/> in the model XML.
# In other words, a container fully describes any value / variable reference
# that can be passed to or returned from a method.
#
function resolve_c_container (container)
# Resolve semantic attributes.
#
# After this function, these should all be fully resolved to a value.
# Some changes may occur later in this function during type resolution.
#
my.container.name ?= "_"
my.container.type ?= "nothing"
my.container.by_reference ?= 0
my.container.callback ?= 0
my.container.fresh ?= 0
my.container.variadic ?= 0
my.container.optional ?= 0
# Resolve language-specific attributes for the C language.
#
# It's important to do this here because languages that bind
# directly to the C implementation will need C-specific information
# to bind correctly when they run later.
#
my.container.c_name ?= "$(my.container.name:c)"
if defined (my.container.c_type)
return
endif
my.type = my.container.type
if my.type = "real" # use different default for backwards compatibility
my.size = my.container.size? 4
else
my.size = my.container.size? 1
endif
my.c_type = ""
my.stars = ""
# Resolve the `c_type` for this `type`, finish tweaking semantic attributes.
#
if my.type = ""
my.c_type = "void"
elsif my.type = "nothing"
my.c_type = "void"
elsif my.type = "anything" | my.type = "sockish"
my.c_type = "void"
my.stars = "*"
elsif my.type = "byte"
my.c_type = "byte"
elsif my.type = "char"
my.c_type = "char"
elsif my.type = "integer"
my.c_type = "int"
elsif my.type = "size"
my.c_type = "size_t"
elsif my.type = "real"
if my.size = 4
my.c_type = "float"
elsif my.size = 8
my.c_type = "double"
endif
elsif my.type = "number"
if my.size = 1
my.c_type = "uint8_t"
elsif my.size = 2
my.c_type = "uint16_t"
elsif my.size = 4
my.c_type = "uint32_t"
elsif my.size = 8
my.c_type = "uint64_t"
endif
elsif my.type = "boolean"
my.c_type = "bool"
elsif my.type = "file_size"
my.c_type = "off_t"
elsif my.type = "time"
my.c_type = "time_t"
elsif my.type = "msecs"
my.c_type = "int64_t"
elsif my.type = "FILE"
my.c_type = "FILE"
my.stars = "*"
elsif my.type = "va_list"
my.c_type = "va_list"
elsif my.type = "socket"
my.c_type = "SOCKET"
elsif my.type = "zmq_pollitem"
my.c_type = "zmq_pollitem_t"
my.stars = "*"
elsif my.type = "string"
my.c_type = "char"
my.stars = "*"
if my.container.fresh = 1 | my.container.by_reference = 1
my.container.mutable ?= 1
else
my.container.mutable ?= 0
endif
elsif my.type = "format"
my.c_type = "char"
my.stars = "*"
my.container.mutable ?= 0
my.container.variadic = 1
elsif my.type = "buffer"
my.c_type = "byte"
my.stars = "*"
if my.container.fresh = 1 | my.container.by_reference = 1
my.container.mutable ?= 1
else
my.container.mutable ?= 0
endif
elsif my.container.callback
my.c_type = "$(my.type:c)"
else
# Map unknown type XYZ to "XYZ_t *" and resolve later
my.container.foreign = 1
my.c_type = "$(my.type:c)_t"
my.stars = "*"
endif
if my.container.by_reference
my.stars += "*"
endif
# This attribute is resolved late because it may be influenced during type resolution
my.container.mutable ?= 1
if !my.container.mutable
my.c_type = "const " + my.c_type
endif
if string.length (my.stars)
my.c_type += " " + my.stars
endif
my.container.c_type = my.c_type
endfunction
# Resolve missing or implicit details in a C method model.
#
# Here, "actor" refers to a <actor/> entity in the model XML.
#
function resolve_c_actor (actor, default_description)
# Resolve semantic attributes
my.actor.description ?= "$(string.trim (my.actor.?my.default_description):left)"
endfunction
# Resolve missing or implicit details in a C method model.
#
# Here, "method" refers to a <method/> entity in the model XML.
#
# All inner containers (arguments and returns) will be fully resolved,
# as well as any semantic or C-specific attributes of the method itself.
#
function resolve_c_method (method, default_description)
# Resolve semantic attributes
my.method.name ?= "_"
my.method.description ?= "$(string.trim (my.method.?my.default_description):left)"
my.method.singleton ?= 0
my.method.is_constructor ?= 0
my.method.is_destructor ?= 0
my.method.polymorphic ?= 0
my.method.private ?= 0
# Resolve language-specific attributes for the C language.
my.method.c_name ?= "$(my.method.name:c)"
# Add an implicit return container if none exists.
if !count (my.method.return)
new return to my.method
endnew
endif
# Resolve each argument container in the method.
for my.method.argument as container
resolve_c_container (container)
if container.variadic
# Create a new argument to represent the variadic arguments
# ------------------------------------------------------------
# PH 2015/11/29: variadic means two different things depending
# on how far we got through processing, which is kind of nasty.
# ------------------------------------------------------------
container.variadic = 0
new argument to my.method
argument.variadic = 1
argument.va_start = container.name
# Won't be included in current loop, so resolve now
resolve_c_container (argument)
endnew
endif
endfor
# Resolve each return container in the method.
for my.method.return as container
resolve_c_container (container)
if container.type = "format"
abort "method cannot return a 'format'"
endif
endfor
endfunction
# Resolve C constant
function resolve_c_constant (constant, default_state)
if defined (my.constant.type) & (my.constant.type = 'string')
my.constant.value = '"' + my.constant.value + '"'
endif
my.constant.description ?= "$(string.trim (my.constant.?""):left)"
resolve_c_container (my.constant)
set_state (my.constant, my.default_state)
endfunction
# Resolve missing or implicit details in a C class model
#
# Here, "class" refers to a <class/> entity in the model XML.
#
# All inner methods will be fully resolved, as well as any
# semantic or C-specific attributes of the method itself.
#
function resolve_c_class (class)
my.class.c_name ?= "$(my.class.name:c)"
my.class.description ?= "$(string.trim (my.class.?""):left)"
if !defined (my.class.selftest)
my.class.selftest ?= 1
endif
set_state (my.class, "draft")
# Includes of XML files
for my.class.include
if !defined (include.filename)
abort "E: required attribute 'filename' not defined"
endif
my.include_file = my.class.load_file (my.class.api_dir + filename)?
if defined (my.include_file)
move my.include_file after include
else
abort "E: <include> failed: $(xml.error?)"
endif
endfor
# All classes must have a test method... unless selftest="0" in project.xml
if !count (my.class.method, method.name = "test") & my.class.selftest
new method to my.class
method.name = "test"
method.singleton = 1
method.description = "Self test of this class."
new argument to method
argument.name = "verbose"
argument.type = "boolean"
endnew
endnew
endif
# Resolve details of each method
for my.class.callback_type as method
resolve_c_method (method, "")
set_state (method, my.class.state)
endfor
# Resolve details of an actor
for my.class.actor
actor.name ?= "$(class.name:c)"
resolve_c_actor (actor, "")
endfor
for my.class.method
resolve_c_method (method, "")
set_state (method, my.class.state)
# Test if a variadic function has a sibling that accepts a va_list.
# By convention these sibling methods prepend a 'v' to the method name.
# This information might be used by the various language bindings.
method.has_va_list_sibling = 0
if count (method.argument, argument.variadic ?= "1")
for my.class.method as m
if m.name = "v" + method.name \
& count (m.argument, argument.type = "va_list")
method.has_va_list_sibling = 1
endif
endfor
endif
endfor
for my.class.constructor as method
method.name ?= "new"
method.singleton = 1
method.is_constructor = 1
method.has_va_list_sibling = 0
# Add a new return value to the first slot - the created object
new return to method as ret
ret.type = my.class.c_name
# constructors always return a fresh value
ret.fresh = 1
move ret before method->return # Move to first slot
endnew
resolve_c_method (method, "Create a new $(my.class.c_name).")
set_state (method, my.class.state)
endfor
for my.class.destructor as method
method.name ?= "destroy"
method.singleton = 1
method.is_destructor = 1
method.has_va_list_sibling = 0
# Add a new argument to the first slot - the object to be destroyed
new argument to method as arg
arg.type = my.class.c_name
arg.name = "self_p"
arg.by_reference = 1
arg.destructor_self = 1
move arg before method->argument # Move to first slot
endnew
resolve_c_method (method, "Destroy the $(my.class.c_name).")
set_state (method, my.class.state)
endfor
# Resolve details of each constant
for my.class.constant
resolve_c_constant (constant, my.class.state)
endfor
endfunction
# Parse a path looking for a class and resolve it if found
#
function parse_directory (path, api_subdir, class)
dir = directory.open (my.path)?
if defined (dir)
for dir.directory
api_file = directory.path + directory.name + my.api_subdir + "$(my.class:c).xml"
if resolved = 0 & file.exists (api_file)
dependency = XML.load_file (api_file)
dependency.api_dir = directory.path + directory.name + my.api_subdir
dependency.resolved = 0
dependency.project = directory.name
resolve_c_class (dependency)
move dependency to project->dependencies
resolved = 1
echo "Importing $(api_file)"
endif
api_file = directory.path + directory.name + my.api_subdir + "$(my.class:c).api"
if resolved = 0 & file.exists (api_file)
dependency = XML.load_file (api_file)
dependency.api_dir = directory.path + directory.name + my.api_subdir
dependency.resolved = 0
dependency.project = directory.name
resolve_c_class (dependency)
move dependency to project->dependencies
resolved = 1
echo "Importing $(api_file)"
endif
endfor
endif
endfunction
# Resolve all dependent data types for one container and load their API file
# if it exists.
#
function resolve_container_dependencies (class_name, container)
if my.container.foreign ?= 1 \
& count (project.class, class.c_name = my.container.type) = 0 \
& count (project->dependencies.class, class.c_name = my.container.type) = 0
resolved = 0
# Search the workspace directory for the dependency
parse_directory ("..", "/api/", my.container.type)
if !resolved
# Search the installation directory for the dependency
parse_directory ("/usr/local/share/zproject", "/", my.container.type)
endif
if !resolved
# Search the system installation directory for the dependency
parse_directory ("/usr/share/zproject", "/", my.container.type)
endif
if !resolved
echo "W: in $(class.name): '" + my.container.type +"' does not resolve to a class"
endif
endif
endfunction
# Resolve all dependent data types from foreign zproject projects.
#
function resolve_class_dependencies (class)
# Create dependencies tag if it doesn't exists
if count (project.dependencies) = 0
dependencies = XML.new ("dependencies")
move dependencies to project
endif
# Resolve argument and return containers
for my.class.method
for method.argument
resolve_container_dependencies (my.class.name, argument)
endfor
for method.return
resolve_container_dependencies (my.class.name, return)
endfor
endfor
endfunction
##
# The following functions are entirely C-specific code generation helpers.
# They contain no useful information for higher-level language bindings.
function _c_method_arglist (method)
# this requires the tmp_args to be set up
my.out = "("
for my.method.tmp_arg as argument
if argument.variadic
my.out += "..."
else
my.out += argument.c_type?""
if !regexp.match ("\\*$", argument.c_type?"")
my.out += " "
endif
my.out += (argument.c_name?"") + (last() ?? '' ? ', ')
endif
endfor
if count(my.method.tmp_arg) = 0
my.out += "void"
endif
my.out += ")"
return my.out
endfunction
function c_method_signature (method)
# this will set method.format_index to the index of
# the format argument, if any
if !my.method.singleton
new tmp_arg to method as arg
arg.name = 'self'
arg.c_name = 'self'
arg.type = '__self__'
arg.variadic = 0
if my.method.polymorphic?0
arg.c_type = 'void *'
else
arg.c_type = "$(class.c_name)_t *"
endif
endnew
endif
for my.method.argument
copy argument to my.method as tmp_arg
endfor
my.out = "$(my.method->return.c_type:)\n"
my.out += " $(class.c_name)_$(my.method.c_name) "
my.out += _c_method_arglist(my.method)
for my.method.tmp_arg as argument
if argument.type = "format"
# Next argument is the format list
my.method.format_index = index()
endif
endfor
for my.method.tmp_arg
delete tmp_arg
endfor
return my.out
endfunction
#
# Construct the string for a callback typedef in a C header
#
function c_callback_typedef (method)
my.out = "typedef $(my.method->return.c_type:) "
my.out += "($(class.c_name)_$(my.method.c_name)) ("
my.out += "\n "
for my.method.argument
my.out += argument.c_type?""
if !regexp.match ("\\*$", argument.c_type?"")
my.out += " "
endif
my.out += argument.c_name?""
if !last ()
my.out += ", "
endif
endfor
my.out += ");"
return my.out
endfunction
#
# Saves the state of resolved C classes.
#
# This function SHOULD BE called by language bindings that
# modify the class state with their own properties to avoid
# property conflicts with other language bindings.
#
# If language bindings require the original class they use the
# saved state at 'project.c_class'. Under no circumstance is a
# bindings allowed to modify those classes. In case this is needed
# the language binding MUST copy them first.
#
function save_c_class_state()
for class
copy class to project as c_class
endfor
endfunction
#
# Restores the state of resolved C classes.
#
# This function will discard any changes a language binding may
# have done to the class state.
#
function restore_c_class_state()
for class
delete class
endfor
for c_class
move c_class to project as class
endfor
endfunction
# Resolve each class using the functions written above.
#
# This includes creating implicit XML entities as well as resolving both
# the semantic and the C-specific attributes of all entities
#
# Other code generation scripts can depend on these being fully resolved here,
# though they should NOT depend on attributes resolved by other generators.
#
for project.main
if project.use_cxx
main.source = "src/$(main.name:$(project.filename_prettyprint)).$(project.source_ext)"
else
main.source = "src/$(main.name:$(project.filename_prettyprint)).$(project.source_ext)"
endif
skeleton_main_source ()
endfor
for project.actor
if scope = "private"
actor.header = "src/$(actor.name:$(project.filename_prettyprint)).$(project.header_ext)"
else
actor.header = "include/$(actor.name:$(project.filename_prettyprint)).$(project.header_ext)"
endif
if project.use_cxx
actor.source = "src/$(actor.name:$(project.filename_prettyprint)).$(project.source_ext)"
else
actor.source = "src/$(actor.name:$(project.filename_prettyprint)).$(project.source_ext)"
endif
skeleton_actor_header ()
skeleton_actor_source ()
endfor
for project.class
if scope = "private"
class.header = "src/$(class.name:$(project.filename_prettyprint)).$(project.header_ext)"
else
class.header = "include/$(class.name:$(project.filename_prettyprint)).$(project.header_ext)"
endif
if project.use_cxx
class.source = "src/$(class.name:$(project.filename_prettyprint)).$(project.source_ext)"
else
class.source = "src/$(class.name:$(project.filename_prettyprint)).$(project.source_ext)"
endif
if defined (class.api)
resolve_c_class (class)
else
# If there is no API model for this class,
# all we can do is resolve the name and state
class.c_name = "$(class.name:c)"
set_state (class, "draft")
endif
endfor
if count (project.class, !private & !draft)
project.stable = 1
else
project.stable = 0
endif
# Resolve the dependencies of the classes from this project and load their APIs
#
for project.class
if defined (class.api)
resolve_class_dependencies (class)
endif
endfor
# Resolve C-related properties of the dependencies API models, find further
# dependencies, load their APIs and repeat if further dependencies have been
# found.
#
if defined (project->dependencies) & count (project->dependencies.class)
while count (project->dependencies.class, !class.resolved)
for project->dependencies.class where !class.resolved
resolve_class_dependencies (class)
class.resolved = 1
endfor
endwhile
endif