blob: fceec8af539c474928cee1416a56b2cf1512ed3a [file] [log] [blame]
#!/usr/bin/env python
# ---
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ---
import os
import re
import sys
import json
import optparse
# see:
# http://www.sitepen.com/blog/2009/06/23/unobtrusive-javascript-typing-via-json-schema-interfaces/
# for some info on JSON Schema for interfaces
#--------------------------------------------------------------------
def main():
# parse args
parser = optparse.OptionParser()
parser.add_option("--validate", action="store_true", help="validate types")
parser.add_option("--anyType", action="append", metavar="TYPE", help="treat TYPE as an any")
(options, args) = parser.parse_args()
if options.anyType:
AnyTypes.extend(options.anyType)
if len(args) <= 0: error("no input files specified")
iFileName = args[0]
if len(args) <= 1:
oFileName = "<stdout>"
else:
oFileName = args[1]
# read input
iFile = file(iFileName)
content = iFile.read()
iFile.close()
# convert to JSONable
module = parseIDL(content)
splitNotifyInterfaces(module)
# if module["name"] == "core":
# if len(module["interfaces"]) == 1:
# if module["interfaces"][0]["name"] == "Inspector":
# splitInspectorInterfaces(module)
# convert out parms to callback parms
convertOutParms(module)
# validate
if options.validate: validate(module)
# convert to JSON
jsonModule = json.dumps(module, indent=3)
# write output
if oFileName == "<stdout>":
oFile = sys.stdout
else:
oFile = file(oFileName,"w")
oFile.write(jsonModule)
# close file
if oFileName != "<stdout>":
oFile.close()
log("generated json file '%s'" %oFileName)
#--------------------------------------------------------------------
def convertOutParms(module):
for interface in module["interfaces"]:
if "methods" in interface:
for method in interface["methods"]:
method["callbackParameters"] = []
newParameters = []
for parameter in method["parameters"]:
if "out" in parameter:
method["callbackParameters"].append(parameter)
else:
newParameters.append(parameter)
method["parameters"] = newParameters
#--------------------------------------------------------------------
def splitNotifyInterfaces(module):
newInterfaces = {}
for interface in module["interfaces"][:]:
if "methods" in interface:
for method in interface["methods"][:]:
if "extendedAttributes" not in method: continue
if "notify" not in method["extendedAttributes"]: continue
newInterfaceName = interface["name"] + "Notify"
newInterface = newInterfaces.get(newInterfaceName)
if not newInterface:
newInterface = {
"name": newInterfaceName,
"methods": []
}
newInterfaces[newInterfaceName] = newInterface
module["interfaces"].append(newInterface)
for parameter in method["parameters"]:
if "out" not in parameter:
log("%s notify method %s has an unexpected non-out parameter %s" % (interface["name"], method["name"], parameter["name"]))
else:
del parameter["out"]
newInterface["methods"].append(method)
interface["methods"].remove(method)
#--------------------------------------------------------------------
def splitInspectorInterfaces(module):
intfOrig = module["interfaces"][0]
newInterfaces = {}
module["interfaces"] = []
for method in intfOrig["methods"]:
if "domain" not in method["extendedAttributes"]:
log("Inspector method %s does not have a 'domain' extended attribute" % (method["name"]))
continue
intfName = method["extendedAttributes"]["domain"]
if "notify" in method["extendedAttributes"]:
intfName += "Notify"
for parameter in method["parameters"]:
if "out" not in parameter:
log("Inspector method %s has an unexpected non-out parameter %s" % (method["name"], parameter["name"]))
else:
del parameter["out"]
intf = newInterfaces.get(intfName)
if not intf:
intf = {
"name": intfName,
"methods": []
}
newInterfaces[intfName] = intf
module["interfaces"].append(intf)
intf["methods"].append(method)
# for parameter in method["parameters"]:
# if "out" not in parameter:
# log("Inspector method %s has an unexpected non-out parameter %s" % (method["name"], parameter["name"]))
# else:
# del parameter["out"]
# intfWebInspector["methods"].append(method)
#--------------------------------------------------------------------
def validate(module):
interfaces = {}
errors = False
# build table of interface names
for interface in module["interfaces"]:
interfaces[interface["name"]] = interface
# check interfaces
for interface in module["interfaces"]:
if "methods" in interface:
for method in interface["methods"]:
location = "%s.%s" % (interface["name"], method["name"])
errors = checkType(location, interfaces, method["returns"]) or errors
for parameter in method["parameters"]:
errors = checkType(location, interfaces, parameter["type"]) or errors
if "attributes" in interface:
for attribute in interface["attributes"]:
location = "%s.%s" % (interface["name"], attribute["name"])
errors = checkType(location, interfaces, attribute["type"]) or errors
#--------------------------------------------------------------------
def checkType(location, interfaces, type):
typeName = type["name"]
if typeName in BuiltInTypes: return False
if typeName in interfaces: return False
log("type '%s' is not valid in %s" % (typeName, location))
return True
#--------------------------------------------------------------------
def parseIDL(content):
content = clean(content)
match = PatternModule.match(content)
if not match: error("no module found in input")
moduleName = match.group(1).strip()
content = match.group(2)
module = {}
module["name"] = moduleName
interfaces = []
module["interfaces"] = interfaces
while True:
match = PatternInterface.match(content)
if not match: break
interfaceEAs = match.group(1)
interfaceName = match.group(2).strip()
interfaceBody = match.group(3)
content = match.group(4)
interface = {}
interface["name"] = interfaceName
parseExtendedAttributes(interface, interfaceEAs)
interfaces.append(interface)
while True:
match = PatternMethod.match(interfaceBody)
if match:
method = parseMethod(match)
if not "methods" in interface: interface["methods"] = []
interface["methods"].append(method)
interfaceBody = match.group(6)
continue
match = PatternAttribute.match(interfaceBody)
if match:
attribute = parseAttribute(match)
if not "attributes" in interface: interface["attributes"] = []
interface["attributes"].append(attribute)
interfaceBody = match.group(5)
continue
if interfaceBody.strip() != "":
error("unexpected input: '%s'" % interfaceBody)
break
if content.strip() != "}": error("unexpected input: '%s'" % content)
return module
#--------------------------------------------------------------------
def parseExtendedAttributes(object, eaStrings):
if not eaStrings: return
if eaStrings == "": return
eaStrings = eaStrings[1:-1]
eaStrings = eaStrings.split(",")
eas = {}
for eaString in eaStrings:
match = PatternExtendedAttribute.match(eaString)
if not match:
error("invalid extended attribute: '%s'" % eaString)
if match.group(3):
eas[match.group(1)] = match.group(3)
else:
eas[match.group(1)] = True
if len(eas):
object["extendedAttributes"] = eas
#--------------------------------------------------------------------
def parseMethod(match):
method = {}
eas = match.group(1)
method["returns"] = getType(match.group(2), match.group(3))
method["name"] = match.group(4)
method["parameters"] = parseMethodParameters(match.group(5))
parseExtendedAttributes(method, eas)
return method
#--------------------------------------------------------------------
def parseAttribute(match):
attribute = {}
eas = match.group(1)
attribute["type"] = getType(match.group(2), match.group(3))
attribute["name"] = match.group(4)
parseExtendedAttributes(attribute, eas)
return attribute
#--------------------------------------------------------------------
def parseMethodParameters(parameterString):
parameters = []
parameterString = parameterString.strip()
if "" == parameterString: return parameters
parmStrings = parameterString.split(",")
for parmString in parmStrings:
parameter = {}
parts = parmString.split()
if parts[0] in ["in", "out"]:
parmString = " ".join(parts[1:])
if parts[0] == "out":
parameter["out"] = True
match = PatternParameter.match(parmString)
if not match:
error("error parsing parameter in '" + parameterString + "'")
parameter["type"] = getType(match.group(1), match.group(2))
if match.group(5):
parameter["name"] = match.group(5)
else:
parameter["name"] = match.group(3)
parameters.append(parameter)
return parameters
#--------------------------------------------------------------------
def getType(name, rank):
name = name.strip()
rank = PatternWhiteSpace.sub("", rank)
origName = name
if name == "long": name = "int"
if name == "int": name = "int"
if name == "double": name = "float"
if name == "unsigned": name = "int"
if name == "DOMString": name = "string"
if name == "String": name = "string"
if name in AnyTypes: name = "any"
if name == "Array":
return {
"name": "any",
"rank": 1
}
result = {}
result["name"] = name
if name != origName: result["originalName"] = origName
if rank:
if (rank == "[]"):
result["rank"] = 1
else:
error("currently only one dimensional arrays are supported: %s" % name)
return result
#--------------------------------------------------------------------
def clean(content):
content = PatternCommentsPP.sub("", content)
content = PatternPreprocessor.sub("", content)
content = PatternNewLine.sub("", content)
content = PatternComments.sub("", content)
return content
#--------------------------------------------------------------------
def log(message):
message = "%s: %s" % (PROGRAM_NAME, message)
print >>sys.stderr, message
#--------------------------------------------------------------------
def error(message):
log(message)
sys.exit(-1)
#--------------------------------------------------------------------
PROGRAM_NAME = os.path.basename(sys.argv[0])
PatternComments = re.compile(r"/\*.*?\*/")
PatternCommentsPP = re.compile(r"//.*$", re.MULTILINE)
PatternPreprocessor = re.compile(r"^\s*#.*$", re.MULTILINE)
PatternNewLine = re.compile(r"\n")
PatternWhiteSpace = re.compile(r"\s")
PatternModule = re.compile(r".*?\bmodule\b(.*?){(.*)")
PatternInterface = re.compile(r".*?\binterface\b\s*(\[.*?\])?\s*(\w+)\s*{(.*?)}\s*;(.*)")
PatternMethod = re.compile(r".*?(\[.*?\])?\s*(\w+)([\[\]\s]*)\s+(\w+)\s*\((.*?)\)\s*;?(.*)")
PatternAttribute = re.compile(r".*?(\[.*?\])?\s*\battribute\s+(\w+)([\[\]\s]*)\s+(\w+)\s*;?(.*)")
PatternParameter = re.compile(r".*?(\w+)([\[\]\s]*)\s+(\w+)(\s+(\w+))?")
PatternExtendedAttribute = re.compile(r"\s*(\w+)\s*(=\s*(\w+))?\s*")
BuiltInTypes = "void any boolean int float string".split()
AnyTypes = "Object DOMObject".split()
main()