-
Notifications
You must be signed in to change notification settings - Fork 0
/
IFaceTableGen.py
406 lines (344 loc) · 13.6 KB
/
IFaceTableGen.py
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
#!/usr/bin/env python3
# IFaceTableGen.py - regenerate the IFaceTable.cxx from the Scintilla.iface
# interface definition file. Based on Scintilla's HFacer.py.
# The header files are copied to a temporary file apart from the section between a //++Autogenerated
# comment and a //--Autogenerated comment which is generated by the printHFile and printLexHFile
# functions. After the temporary file is created, it is copied back to the original file name.
# Requires Python 3.6 or later
import sys
import os
srcRoot = "../.."
sys.path.append(srcRoot + "/scintilla/scripts")
import Face
import FileGenerator
def CommentString(prop):
if prop and prop["Comment"]:
return (" -- " + " ".join(prop["Comment"])).replace("<", "<")
return ""
def ConvertEnu(t):
if Face.IsEnumeration(t):
return "int"
else:
return t
def GetScriptableInterface(f):
"""Returns a tuple of (constants, functions, properties)
constants - a sorted list of (name, features) tuples, including all
constants except for SCLEX_ constants which are presumed not used by
scripts. The SCI_ constants for functions are omitted, since they
can be derived, but the SCI_ constants for properties are included
since they cannot be derived from the property names.
functions - a sorted list of (name, features) tuples, for the features
that should be exposed to script as functions. This includes all
'fun' functions; it is up to the program to decide if a given
function cannot be scripted. It is also up to the caller to
export the SCI_ constants for functions.
properties - a sorted list of (name, property), where property is a
dictionary containing these keys: "GetterValue", "SetterValue",
"PropertyType", "IndexParamType", "IndexParamName", "GetterName",
"SetterName", "GetterComment", "SetterComment", and "Category".
If the property is read-only, SetterValue will be 0, and the other
Setter attribtes will be None. Likewise for write-only properties,
GetterValue will be 0 etc. If the getter and/or setter are not
compatible with one another, or with our interpretation of how
properties work, then the functions are instead added to the
functions list. It is still up to the language binding to decide
whether the property can / should be exposed to script."""
constants = [] # returned as a sorted list
functions = {} # returned as a sorted list of items
properties = {} # returned as a sorted list of items
for name in f.order:
features = f.features[name]
if features["Category"] != "Deprecated":
if features["FeatureType"] == "val":
constants.append( (name, features) )
elif features["FeatureType"] in ["fun","get","set"]:
if features["FeatureType"] == "get":
propname = name.replace("Get", "", 1)
properties[propname] = (name, properties.get(propname,(None,None))[1])
elif features["FeatureType"] == "set":
propname = name.replace("Set", "", 1)
properties[propname] = (properties.get(propname,(None,None))[0], name)
else:
functions[name] = features
propertiesCopy = properties.copy()
for propname, (getterName, setterName) in propertiesCopy.items():
getter = getterName and f.features[getterName]
setter = setterName and f.features[setterName]
getterValue, getterIndex, getterIndexName, getterType = 0, None, None, None
setterValue, setterIndex, setterIndexName, setterType = 0, None, None, None
propType, propIndex, propIndexName = None, None, None
isok = (getterName or setterName) and not (getter is setter)
if isok and getter:
if getter['Param2Type'] == 'stringresult':
getterType = getter['Param2Type']
else:
getterType = getter['ReturnType']
getterType = ConvertEnu(getterType)
getterValue = getter['Value']
getterIndex = getter['Param1Type'] or 'void'
getterIndexName = getter['Param1Name']
isok = ((getter['Param2Type'] or 'void') == 'void') or (getterType == 'stringresult')
if isok and setter:
setterValue = setter['Value']
setterType = ConvertEnu(setter['Param1Type']) or 'void'
setterIndex = 'void'
if (setter['Param2Type'] or 'void') != 'void':
setterIndex = setterType
setterIndexName = setter['Param1Name']
setterType = ConvertEnu(setter['Param2Type'])
isok = (setter['ReturnType'] == 'void') or (setter['ReturnType'] == 'int' and setterType=='string')
if isok and getter and setter:
isok = ((getterType == setterType) or (getterType == 'stringresult' and setterType == 'string')) and (getterIndex == setterIndex)
propType = getterType or setterType
propIndex = getterIndex or setterIndex
propIndexName = getterIndexName or setterIndexName
if isok:
# do the types appear to be useable? THIS IS OVERRIDDEN BELOW
isok = (propType in ('int', 'position', 'line', 'pointer', 'colour', 'colouralpha', 'bool', 'string', 'stringresult')
and propIndex in ('void','int','position','line','string','bool'))
# getters on string properties follow a different protocol with this signature
# for a string getter and setter:
# get int funcname(void,stringresult)
# set void funcname(void,string)
#
# For an indexed string getter and setter, the indexer goes in
# wparam and must not be called 'int length', since 'int length'
# has special meaning.
# A bool indexer has a special meaning. It means "if the script
# assigns the language's nil value to the property, call the
# setter with args (0,0); otherwise call it with (1, value)."
#
# Although there are no getters indexed by bool, I suggest the
# following protocol: If getter(1,0) returns 0, return nil to
# the script. Otherwise return getter(0,0).
if isok:
properties[propname] = {
"GetterValue" : getterValue,
"SetterValue" : setterValue,
"PropertyType" : propType,
"IndexParamType" : propIndex,
"IndexParamName" : propIndexName,
# The rest of this metadata is added to help generate documentation
"Category" : (getter or setter)["Category"],
"GetterName" : getterName,
"SetterName" : setterName,
"GetterComment" : CommentString(getter),
"SetterComment" : CommentString(setter)
}
#~ print(properties[propname])
# If it is exposed as a property, the constant name is not picked up implicitly
# (because the name is different) but its constant should still be exposed.
if getter:
constants.append( ("SCI_" + getterName.upper(), getter))
if setter:
constants.append( ("SCI_" + setterName.upper(), setter))
else:
# Cannot parse as scriptable property (e.g. not symmetrical), so export as functions
del(properties[propname])
if getter:
functions[getterName] = getter
if setter:
functions[setterName] = setter
funclist = list(functions.items())
funclist.sort()
proplist = list(properties.items())
proplist.sort()
constants.sort()
return (constants, funclist, proplist)
def printIFaceTableCXXFile(facesAndIDs):
out = []
f, fLex, ids = facesAndIDs
(constants, functions, properties) = GetScriptableInterface(f)
# Lexilla only defines constants, no functions or properties
(constantsLex, _, _) = GetScriptableInterface(fLex)
constants.extend(constantsLex)
constants.extend(ids)
constants.sort()
out.append("")
out.append("static IFaceConstant ifaceConstants[] = {")
if constants:
lastName = constants[-1][0]
for name, features in constants:
comma = "" if name == lastName else ","
val = features["Value"]
if int(val, base=0) >= 0x8000000:
val = "static_cast<int>(" + val + ")"
out.append('\t{"%s",%s}%s' % (name, val, comma))
out.append("};")
else:
out.append('{"",0}};')
# Write an array of function descriptions. This can be
# used as a sort of compiled typelib.
out.append("")
out.append("static IFaceFunction ifaceFunctions[] = {")
if functions:
lastName = functions[-1][0]
for name, features in functions:
comma = "" if name == lastName else ","
returnType = ConvertEnu(features["ReturnType"])
param1Type = ConvertEnu(features["Param1Type"]) or "void"
param2Type = ConvertEnu(features["Param2Type"]) or "void"
# Fix-up: if a param is an int (or position) named length, change to iface_type_length.
if param1Type in ["int", "position"] and features["Param1Name"] == "length":
param1Type = "length"
if param2Type in ["int", "position"] and features["Param2Name"] == "length":
param2Type = "length"
out.append('\t{"%s", %s, iface_%s, {iface_%s, iface_%s}}%s' % (
name, features["Value"], returnType, param1Type, param2Type, comma
))
out.append("};")
else:
out.append('{"",0,iface_void,{iface_void,iface_void}} };')
out.append("")
out.append("static IFaceProperty ifaceProperties[] = {")
if properties:
lastName = properties[-1][0]
for propname, property in properties:
comma = "" if propname == lastName else ","
out.append('\t{"%s", %s, %s, iface_%s, iface_%s}%s' % (
propname,
property["GetterValue"],
property["SetterValue"],
property["PropertyType"], property["IndexParamType"],
comma
))
out.append("};")
out.append("")
else:
out.append('{"", 0, iface_void, iface_void} };')
out.append("enum {")
out.append("\tifaceFunctionCount = %d," % len(functions))
out.append("\tifaceConstantCount = %d," % len(constants))
out.append("\tifacePropertyCount = %d" % len(properties))
out.append("};")
out.append("")
return out
def convertStringResult(s):
if s == "stringresult":
return "string"
else:
return s
def idsFromDocumentation(filename):
""" Read the Scintilla documentation and return a list of all the features
in the same order as they are explained in the documentation.
Also include the previous header with each feature. """
idsInOrder = []
segment = ""
with open(filename) as f:
for l in f:
if "<h2" in l:
segment = l.split(">")[1].split("<")[0]
if 'id="SCI_' in l:
idFeature = l.split('"')[1]
#~ print(idFeature)
idsInOrder.append([segment, idFeature])
return idsInOrder
nonScriptableTypes = ["cells", "textrange", "findtext", "formatrange"]
def printIFaceTableHTMLFile(faceAndIDs):
out = []
f, ids, idsInOrder = faceAndIDs
(constants, functions, properties) = GetScriptableInterface(f)
explanations = {}
for name, features in functions:
featureDefineName = "SCI_" + name.upper()
explanation = ""
href = ""
hrefEnd = ""
href = "<a href='https://www.scintilla.org/ScintillaDoc.html#" + featureDefineName + "'>"
hrefEnd = "</a>"
if features['Param1Type'] in nonScriptableTypes or features['Param2Type'] in nonScriptableTypes:
#~ print(name, features)
continue
parameters = ""
stringresult = ""
if features['Param2Type'] == "stringresult":
stringresult = "string "
if features['Param1Name'] and features['Param1Name'] != "length":
parameters += features['Param1Type'] + " " + features['Param1Name']
else:
if features['Param1Name']:
parameters += ConvertEnu(features['Param1Type']) + " " + features['Param1Name']
if features['Param1Name'] == "length" and features['Param2Type'] == "string":
# special case removal
parameters = ""
if features['Param2Name']:
if parameters:
parameters += ", "
parameters += ConvertEnu(features['Param2Type']) + " " + features['Param2Name']
returnType = stringresult
if not returnType and Face.IsEnumeration(features["ReturnType"]):
returnType = "int "
if not returnType and features["ReturnType"] != "void":
returnType = convertStringResult(features["ReturnType"]) + " "
explanation += '%seditor:%s%s%s(%s)' % (
returnType,
href,
name,
hrefEnd,
parameters
)
if features["Comment"]:
explanation += '<span class="comment">%s</span>' % CommentString(features)
explanations[featureDefineName] = explanation
for propname, property in properties:
functionName = property['SetterName'] or property['GetterName']
featureDefineName = "SCI_" + functionName.upper()
explanation = ""
href = "<a href='https://www.scintilla.org/ScintillaDoc.html#" + featureDefineName + "'>"
hrefEnd = "</a>"
direction = ""
if not property['SetterName']:
direction = " read-only"
if not property['GetterName']:
direction = " write-only"
indexExpression = ""
if property["IndexParamType"] != "void":
indexExpression = "[" + property["IndexParamType"] + " " + property["IndexParamName"] + "]"
explanation += '%s editor.%s%s%s%s%s' % (
convertStringResult(property["PropertyType"]),
href,
propname,
hrefEnd,
indexExpression,
direction
)
if property["SetterComment"]:
explanation += '<span class="comment">%s</span>' % (property["SetterComment"].replace("<", "<"))
explanations[featureDefineName] = explanation
lastSegment = ""
for segment, featureId in idsInOrder:
if featureId in explanations:
if segment != lastSegment:
out.append('\t<h2>' + segment + '</h2>')
lastSegment = segment
out.append('\t<p>' + explanations[featureId] + '</p>')
out.append("")
return out
def ReadMenuIDs(filename):
ids = []
f = open(filename)
try:
for l in f:
if l.startswith("#define"):
#~ print l
try:
_d, name, number = l.split()
if name.startswith("IDM_"):
ids.append((name, {"Value":number}))
except ValueError:
# No value present
pass
finally:
f.close()
return ids
def RegenerateAll():
faceLex = Face.Face()
faceLex.ReadFromFile(srcRoot + "/lexilla/include/LexicalStyles.iface")
face = Face.Face()
face.ReadFromFile(srcRoot + "/scintilla/include/Scintilla.iface")
menuIDs = ReadMenuIDs(srcRoot + "/scite/src/SciTE.h")
idsInOrder = idsFromDocumentation(srcRoot + "/scintilla/doc/ScintillaDoc.html")
FileGenerator.Regenerate(srcRoot + "/scite/src/IFaceTable.cxx", "//", printIFaceTableCXXFile([face, faceLex, menuIDs]))
FileGenerator.Regenerate(srcRoot + "/scite/doc/PaneAPI.html", "<!--", printIFaceTableHTMLFile([face, menuIDs, idsInOrder]))
if __name__=="__main__":
RegenerateAll()