638 lines
16 KiB
Haxe
638 lines
16 KiB
Haxe
/*
|
|
* Copyright (C)2005-2019 Haxe Foundation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
package haxe.rtti;
|
|
|
|
import haxe.rtti.CType;
|
|
import haxe.xml.Access;
|
|
|
|
/**
|
|
XmlParser processes the runtime type information (RTTI) which
|
|
is stored as a XML string in a static field `__rtti`.
|
|
|
|
@see <https://haxe.org/manual/cr-rtti.html>
|
|
**/
|
|
class XmlParser {
|
|
public var root:TypeRoot;
|
|
|
|
var curplatform:String;
|
|
|
|
public function new() {
|
|
root = new Array();
|
|
}
|
|
|
|
public function sort(?l:TypeRoot) {
|
|
if (l == null)
|
|
l = root;
|
|
l.sort(function(e1, e2) {
|
|
var n1 = switch e1 {
|
|
case TPackage(p, _, _): " " + p;
|
|
default: TypeApi.typeInfos(e1).path;
|
|
};
|
|
var n2 = switch e2 {
|
|
case TPackage(p, _, _): " " + p;
|
|
default: TypeApi.typeInfos(e2).path;
|
|
};
|
|
if (n1 > n2)
|
|
return 1;
|
|
return -1;
|
|
});
|
|
for (x in l)
|
|
switch (x) {
|
|
case TPackage(_, _, l):
|
|
sort(l);
|
|
case TClassdecl(c):
|
|
sortFields(c.fields);
|
|
sortFields(c.statics);
|
|
case TEnumdecl(_):
|
|
case TAbstractdecl(_):
|
|
case TTypedecl(_):
|
|
}
|
|
}
|
|
|
|
function sortFields(a:Array<ClassField>) {
|
|
a.sort(function(f1:ClassField, f2:ClassField) {
|
|
var v1 = TypeApi.isVar(f1.type);
|
|
var v2 = TypeApi.isVar(f2.type);
|
|
if (v1 && !v2)
|
|
return -1;
|
|
if (v2 && !v1)
|
|
return 1;
|
|
if (f1.name == "new")
|
|
return -1;
|
|
if (f2.name == "new")
|
|
return 1;
|
|
if (f1.name > f2.name)
|
|
return 1;
|
|
return -1;
|
|
});
|
|
}
|
|
|
|
public function process(x:Xml, platform:String) {
|
|
curplatform = platform;
|
|
xroot(new Access(x));
|
|
}
|
|
|
|
// merge inline and not inline
|
|
function mergeRights(f1:ClassField, f2:ClassField) {
|
|
if (f1.get == RInline && f1.set == RNo && f2.get == RNormal && f2.set == RMethod) {
|
|
f1.get = RNormal;
|
|
f1.set = RMethod;
|
|
return true;
|
|
}
|
|
return Type.enumEq(f1.get, f2.get) && Type.enumEq(f1.set, f2.set);
|
|
}
|
|
|
|
function mergeDoc(f1:ClassField, f2:ClassField) {
|
|
if (f1.doc == null)
|
|
f1.doc = f2.doc;
|
|
else if (f2.doc == null)
|
|
f2.doc = f1.doc;
|
|
return true;
|
|
}
|
|
|
|
function mergeFields(f:ClassField, f2:ClassField) {
|
|
return TypeApi.fieldEq(f, f2)
|
|
|| (f.name == f2.name && (mergeRights(f, f2) || mergeRights(f2, f)) && mergeDoc(f, f2) && TypeApi.fieldEq(f, f2));
|
|
}
|
|
|
|
public dynamic function newField(c:Classdef, f:ClassField) {}
|
|
|
|
function mergeClasses(c:Classdef, c2:Classdef) {
|
|
// todo : compare supers & interfaces
|
|
if (c.isInterface != c2.isInterface)
|
|
return false;
|
|
if (curplatform != null)
|
|
c.platforms.push(curplatform);
|
|
if (c.isExtern != c2.isExtern)
|
|
c.isExtern = false;
|
|
|
|
for (f2 in c2.fields) {
|
|
var found = null;
|
|
for (f in c.fields)
|
|
if (mergeFields(f, f2)) {
|
|
found = f;
|
|
break;
|
|
}
|
|
if (found == null) {
|
|
newField(c, f2);
|
|
c.fields.push(f2);
|
|
} else if (curplatform != null)
|
|
found.platforms.push(curplatform);
|
|
}
|
|
for (f2 in c2.statics) {
|
|
var found = null;
|
|
for (f in c.statics)
|
|
if (mergeFields(f, f2)) {
|
|
found = f;
|
|
break;
|
|
}
|
|
if (found == null) {
|
|
newField(c, f2);
|
|
c.statics.push(f2);
|
|
} else if (curplatform != null)
|
|
found.platforms.push(curplatform);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function mergeEnums(e:Enumdef, e2:Enumdef) {
|
|
if (e.isExtern != e2.isExtern)
|
|
return false;
|
|
if (curplatform != null)
|
|
e.platforms.push(curplatform);
|
|
for (c2 in e2.constructors) {
|
|
var found = null;
|
|
for (c in e.constructors)
|
|
if (TypeApi.constructorEq(c, c2)) {
|
|
found = c;
|
|
break;
|
|
}
|
|
if (found == null)
|
|
e.constructors.push(c2);
|
|
else if (curplatform != null)
|
|
found.platforms.push(curplatform);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function mergeTypedefs(t:Typedef, t2:Typedef) {
|
|
if (curplatform == null)
|
|
return false;
|
|
t.platforms.push(curplatform);
|
|
t.types.set(curplatform, t2.type);
|
|
return true;
|
|
}
|
|
|
|
function mergeAbstracts(a:Abstractdef, a2:Abstractdef) {
|
|
if (curplatform == null)
|
|
return false;
|
|
if (a.to.length != a2.to.length || a.from.length != a2.from.length)
|
|
return false;
|
|
for (i in 0...a.to.length)
|
|
if (!TypeApi.typeEq(a.to[i].t, a2.to[i].t))
|
|
return false;
|
|
for (i in 0...a.from.length)
|
|
if (!TypeApi.typeEq(a.from[i].t, a2.from[i].t))
|
|
return false;
|
|
if (a2.impl != null)
|
|
mergeClasses(a.impl, a2.impl);
|
|
a.platforms.push(curplatform);
|
|
return true;
|
|
}
|
|
|
|
function merge(t:TypeTree) {
|
|
var inf = TypeApi.typeInfos(t);
|
|
var pack = inf.path.split(".");
|
|
var cur = root;
|
|
var curpack = new Array();
|
|
pack.pop();
|
|
for (p in pack) {
|
|
var found = false;
|
|
for (pk in cur)
|
|
switch (pk) {
|
|
case TPackage(pname, _, subs):
|
|
if (pname == p) {
|
|
found = true;
|
|
cur = subs;
|
|
break;
|
|
}
|
|
default:
|
|
}
|
|
curpack.push(p);
|
|
if (!found) {
|
|
var pk = new Array();
|
|
cur.push(TPackage(p, curpack.join("."), pk));
|
|
cur = pk;
|
|
}
|
|
}
|
|
for (ct in cur) {
|
|
if (ct.match(TPackage(_)))
|
|
continue;
|
|
var tinf = TypeApi.typeInfos(ct);
|
|
|
|
// compare params ?
|
|
if (tinf.path == inf.path) {
|
|
var sameType = true;
|
|
if ((tinf.doc == null) != (inf.doc == null)) {
|
|
if (inf.doc == null)
|
|
inf.doc = tinf.doc;
|
|
else
|
|
tinf.doc = inf.doc;
|
|
}
|
|
if (tinf.path == "haxe._Int64.NativeInt64")
|
|
continue;
|
|
if (tinf.module == inf.module && tinf.doc == inf.doc && tinf.isPrivate == inf.isPrivate)
|
|
switch (ct) {
|
|
case TClassdecl(c):
|
|
switch (t) {
|
|
case TClassdecl(c2):
|
|
if (mergeClasses(c, c2))
|
|
return;
|
|
default:
|
|
sameType = false;
|
|
}
|
|
case TEnumdecl(e):
|
|
switch (t) {
|
|
case TEnumdecl(e2):
|
|
if (mergeEnums(e, e2))
|
|
return;
|
|
default:
|
|
sameType = false;
|
|
}
|
|
case TTypedecl(td):
|
|
switch (t) {
|
|
case TTypedecl(td2):
|
|
if (mergeTypedefs(td, td2))
|
|
return;
|
|
default:
|
|
}
|
|
case TAbstractdecl(a):
|
|
switch (t) {
|
|
case TAbstractdecl(a2):
|
|
if (mergeAbstracts(a, a2))
|
|
return;
|
|
default:
|
|
sameType = false;
|
|
}
|
|
case TPackage(_, _, _):
|
|
sameType = false;
|
|
}
|
|
// we already have a mapping, but which is incompatible
|
|
var msg = if (tinf.module != inf.module) "module " + inf.module + " should be " + tinf.module; else if (tinf.doc != inf.doc)
|
|
"documentation is different";
|
|
else if (tinf.isPrivate != inf.isPrivate)
|
|
"private flag is different";
|
|
else if (!sameType)
|
|
"type kind is different";
|
|
else
|
|
"could not merge definition";
|
|
throw "Incompatibilities between " + tinf.path + " in " + tinf.platforms.join(",") + " and " + curplatform + " (" + msg + ")";
|
|
}
|
|
}
|
|
cur.push(t);
|
|
}
|
|
|
|
function mkPath(p:String):Path {
|
|
return p;
|
|
}
|
|
|
|
function mkTypeParams(p:String):TypeParams {
|
|
var pl = p.split(":");
|
|
if (pl[0] == "")
|
|
return new Array();
|
|
return pl;
|
|
}
|
|
|
|
function mkRights(r:String):Rights {
|
|
return switch (r) {
|
|
case "null": RNo;
|
|
case "method": RMethod;
|
|
case "dynamic": RDynamic;
|
|
case "inline": RInline;
|
|
default: RCall(r);
|
|
}
|
|
}
|
|
|
|
function xerror(c:Access):Dynamic {
|
|
return throw "Invalid " + c.name;
|
|
}
|
|
|
|
function xroot(x:Access) {
|
|
for (c in x.x.elements())
|
|
merge(processElement(c));
|
|
}
|
|
|
|
public function processElement(x:Xml) {
|
|
var c = new haxe.xml.Access(x);
|
|
return switch (c.name) {
|
|
case "class": TClassdecl(xclass(c));
|
|
case "enum": TEnumdecl(xenum(c));
|
|
case "typedef": TTypedecl(xtypedef(c));
|
|
case "abstract": TAbstractdecl(xabstract(c));
|
|
default: xerror(c);
|
|
}
|
|
}
|
|
|
|
function xmeta(x:Access):MetaData {
|
|
var ml = [];
|
|
for (m in x.nodes.m) {
|
|
var pl = [];
|
|
for (p in m.nodes.e)
|
|
pl.push(p.innerHTML);
|
|
ml.push({name: m.att.n, params: pl});
|
|
}
|
|
return ml;
|
|
}
|
|
|
|
function xoverloads(x:Access):Array<ClassField> {
|
|
var l = new Array();
|
|
for (m in x.elements) {
|
|
l.push(xclassfield(m));
|
|
}
|
|
return l;
|
|
}
|
|
|
|
function xpath(x:Access):PathParams {
|
|
var path = mkPath(x.att.path);
|
|
var params = new Array();
|
|
for (c in x.elements)
|
|
params.push(xtype(c));
|
|
return {
|
|
path: path,
|
|
params: params,
|
|
};
|
|
}
|
|
|
|
function xclass(x:Access):Classdef {
|
|
var csuper = null;
|
|
var doc = null;
|
|
var tdynamic = null;
|
|
var interfaces = new Array();
|
|
var fields = new Array();
|
|
var statics = new Array();
|
|
var meta = [];
|
|
var isInterface = x.x.exists("interface");
|
|
for (c in x.elements)
|
|
switch (c.name) {
|
|
case "haxe_doc":
|
|
doc = c.innerData;
|
|
case "extends":
|
|
if (isInterface) {
|
|
interfaces.push(xpath(c));
|
|
} else {
|
|
csuper = xpath(c);
|
|
}
|
|
case "implements":
|
|
interfaces.push(xpath(c));
|
|
case "haxe_dynamic":
|
|
tdynamic = xtype(new Access(c.x.firstElement()));
|
|
case "meta":
|
|
meta = xmeta(c);
|
|
default:
|
|
if (c.x.exists("static"))
|
|
statics.push(xclassfield(c));
|
|
else
|
|
fields.push(xclassfield(c));
|
|
}
|
|
return {
|
|
file: if (x.has.file) x.att.file else null,
|
|
path: mkPath(x.att.path),
|
|
module: if (x.has.module) mkPath(x.att.module) else null,
|
|
doc: doc,
|
|
isPrivate: x.x.exists("private"),
|
|
isExtern: x.x.exists("extern"),
|
|
isFinal: x.x.exists("final"),
|
|
isInterface: isInterface,
|
|
params: mkTypeParams(x.att.params),
|
|
superClass: csuper,
|
|
interfaces: interfaces,
|
|
fields: fields,
|
|
statics: statics,
|
|
tdynamic: tdynamic,
|
|
platforms: defplat(),
|
|
meta: meta,
|
|
};
|
|
}
|
|
|
|
function xclassfield(x:Access, ?defPublic = false):ClassField {
|
|
var e = x.elements;
|
|
var t = xtype(e.next());
|
|
var doc = null;
|
|
var meta = [];
|
|
var overloads = null;
|
|
for (c in e)
|
|
switch (c.name) {
|
|
case "haxe_doc":
|
|
doc = c.innerData;
|
|
case "meta":
|
|
meta = xmeta(c);
|
|
case "overloads":
|
|
overloads = xoverloads(c);
|
|
default:
|
|
xerror(c);
|
|
}
|
|
return {
|
|
name:x.name, type:t, isPublic:x.x.exists("public") || defPublic, isFinal:x.x.exists("final"), isOverride:x.x.exists("override"),
|
|
line:if (x.has.line) Std.parseInt(x.att.line) else null, doc:doc, get:if (x.has.get) mkRights(x.att.get) else RNormal, set:if (x.has.set)
|
|
mkRights(x.att.set)
|
|
else
|
|
RNormal, params:if (x.has.params) mkTypeParams(x.att.params) else [], platforms:defplat(), meta:meta, overloads:overloads, expr:if (x.has.expr)
|
|
x.att.expr
|
|
else
|
|
null
|
|
};
|
|
}
|
|
|
|
function xenum(x:Access):Enumdef {
|
|
var cl = new Array();
|
|
var doc = null;
|
|
var meta = [];
|
|
for (c in x.elements)
|
|
if (c.name == "haxe_doc")
|
|
doc = c.innerData;
|
|
else if (c.name == "meta")
|
|
meta = xmeta(c);
|
|
else
|
|
cl.push(xenumfield(c));
|
|
return {
|
|
file: if (x.has.file) x.att.file else null,
|
|
path: mkPath(x.att.path),
|
|
module: if (x.has.module) mkPath(x.att.module) else null,
|
|
doc: doc,
|
|
isPrivate: x.x.exists("private"),
|
|
isExtern: x.x.exists("extern"),
|
|
params: mkTypeParams(x.att.params),
|
|
constructors: cl,
|
|
platforms: defplat(),
|
|
meta: meta,
|
|
};
|
|
}
|
|
|
|
function xenumfield(x:Access):EnumField {
|
|
var args = null;
|
|
var docElements = x.x.elementsNamed("haxe_doc");
|
|
var xdoc = if (docElements.hasNext()) docElements.next() else null;
|
|
var meta = if (x.hasNode.meta) xmeta(x.node.meta) else [];
|
|
if (x.has.a) {
|
|
var names = x.att.a.split(":");
|
|
var elts = x.elements;
|
|
args = new Array();
|
|
for (c in names) {
|
|
var opt = false;
|
|
if (c.charAt(0) == "?") {
|
|
opt = true;
|
|
c = c.substr(1);
|
|
}
|
|
args.push({
|
|
name: c,
|
|
opt: opt,
|
|
t: xtype(elts.next()),
|
|
});
|
|
}
|
|
}
|
|
return {
|
|
name: x.name,
|
|
args: args,
|
|
doc: if (xdoc == null) null else new Access(xdoc).innerData,
|
|
meta: meta,
|
|
platforms: defplat(),
|
|
};
|
|
}
|
|
|
|
function xabstract(x:Access):Abstractdef {
|
|
var doc = null, impl = null, athis = null;
|
|
var meta = [], to = [], from = [];
|
|
for (c in x.elements)
|
|
switch (c.name) {
|
|
case "haxe_doc":
|
|
doc = c.innerData;
|
|
case "meta":
|
|
meta = xmeta(c);
|
|
case "to":
|
|
for (t in c.elements)
|
|
to.push({t: xtype(new Access(t.x.firstElement())), field: t.has.field ? t.att.field : null});
|
|
case "from":
|
|
for (t in c.elements)
|
|
from.push({t: xtype(new Access(t.x.firstElement())), field: t.has.field ? t.att.field : null});
|
|
case "impl":
|
|
impl = xclass(c.node.resolve("class"));
|
|
case "this":
|
|
athis = xtype(new Access(c.x.firstElement()));
|
|
default:
|
|
xerror(c);
|
|
}
|
|
return {
|
|
file: if (x.has.file) x.att.file else null,
|
|
path: mkPath(x.att.path),
|
|
module: if (x.has.module) mkPath(x.att.module) else null,
|
|
doc: doc,
|
|
isPrivate: x.x.exists("private"),
|
|
params: mkTypeParams(x.att.params),
|
|
platforms: defplat(),
|
|
meta: meta,
|
|
athis: athis,
|
|
to: to,
|
|
from: from,
|
|
impl: impl
|
|
};
|
|
}
|
|
|
|
function xtypedef(x:Access):Typedef {
|
|
var doc = null;
|
|
var t = null;
|
|
var meta = [];
|
|
for (c in x.elements)
|
|
if (c.name == "haxe_doc")
|
|
doc = c.innerData;
|
|
else if (c.name == "meta")
|
|
meta = xmeta(c);
|
|
else
|
|
t = xtype(c);
|
|
var types = new haxe.ds.StringMap();
|
|
if (curplatform != null)
|
|
types.set(curplatform, t);
|
|
return {
|
|
file: if (x.has.file) x.att.file else null,
|
|
path: mkPath(x.att.path),
|
|
module: if (x.has.module) mkPath(x.att.module) else null,
|
|
doc: doc,
|
|
isPrivate: x.x.exists("private"),
|
|
params: mkTypeParams(x.att.params),
|
|
type: t,
|
|
types: types,
|
|
platforms: defplat(),
|
|
meta: meta,
|
|
};
|
|
}
|
|
|
|
function xtype(x:Access):CType {
|
|
return switch (x.name) {
|
|
case "unknown":
|
|
CUnknown;
|
|
case "e":
|
|
CEnum(mkPath(x.att.path), xtypeparams(x));
|
|
case "c":
|
|
CClass(mkPath(x.att.path), xtypeparams(x));
|
|
case "t":
|
|
CTypedef(mkPath(x.att.path), xtypeparams(x));
|
|
case "x":
|
|
CAbstract(mkPath(x.att.path), xtypeparams(x));
|
|
case "f":
|
|
var args = new Array();
|
|
var aname = x.att.a.split(":");
|
|
var eargs = aname.iterator();
|
|
var evalues = x.has.v ? x.att.v.split(":").iterator() : null;
|
|
for (e in x.elements) {
|
|
var opt = false;
|
|
var a = eargs.hasNext() ? eargs.next() : null;
|
|
if (a == null)
|
|
a = "";
|
|
if (a.charAt(0) == "?") {
|
|
opt = true;
|
|
a = a.substr(1);
|
|
}
|
|
var v = evalues == null || !evalues.hasNext() ? null : evalues.next();
|
|
args.push({
|
|
name: a,
|
|
opt: opt,
|
|
t: xtype(e),
|
|
value: v == "" ? null : v
|
|
});
|
|
}
|
|
var ret = args[args.length - 1];
|
|
args.remove(ret);
|
|
CFunction(args, ret.t);
|
|
case "a":
|
|
var fields = new Array();
|
|
for (f in x.elements) {
|
|
var f = xclassfield(f, true);
|
|
f.platforms = new Array(); // platforms selection are on the type itself, not on fields
|
|
fields.push(f);
|
|
}
|
|
CAnonymous(fields);
|
|
case "d":
|
|
var t = null;
|
|
var tx = x.x.firstElement();
|
|
if (tx != null)
|
|
t = xtype(new Access(tx));
|
|
CDynamic(t);
|
|
default:
|
|
xerror(x);
|
|
}
|
|
}
|
|
|
|
function xtypeparams(x:Access):Array<CType> {
|
|
var p = new Array();
|
|
for (c in x.elements)
|
|
p.push(xtype(c));
|
|
return p;
|
|
}
|
|
|
|
function defplat() {
|
|
var l = new Array();
|
|
if (curplatform != null)
|
|
l.push(curplatform);
|
|
return l;
|
|
}
|
|
}
|