Nicolas Cannasse 19 years ago
parent
commit
27fffba26d
3 changed files with 583 additions and 0 deletions
  1. 210 0
      std/tools/haxedoc/Type.hx
  2. 364 0
      std/tools/haxedoc/XmlParser.hx
  3. 9 0
      std/tools/haxedoc/haxedoc.hxp

+ 210 - 0
std/tools/haxedoc/Type.hx

@@ -0,0 +1,210 @@
+package tools.haxedoc;
+
+typedef Path = String
+
+typedef Platforms = List<String>
+
+enum Type {
+	TUnknown;
+	TEnum( name : Path, params : List<Type> );
+	TClass( name : Path, params : List<Type> );
+	TTypedef( name : Path, params : List<Type> );
+	TFunction( args : List<{ name : String, opt : Bool, t : Type }>, ret : Type );
+	TAnonymous( fields : List<{ name : String, t : Type  }> );
+	TDynamic( ?t : Type );
+}
+
+typedef PathParams = {
+	var path : Path;
+	var params : List<Type>;
+}
+
+typedef TypeParams = Array<String> // no contraints
+
+enum Rights {
+	RNormal;
+	RNo;
+	RMethod( m : String );
+	RDynamic;
+	RF9Dynamic;
+}
+
+typedef ClassField = {
+	var name : String;
+	var type : Type;
+	var isPublic : Bool;
+	var doc : String;
+	var get : Rights;
+	var set : Rights;
+	var params : TypeParams;
+	var platforms : Platforms;
+}
+
+typedef TypeInfos = {
+	var path : Path;
+	var module : Path;
+	var params : TypeParams;
+	var doc : String;
+	var isPrivate : Bool;
+	var platforms : Platforms;
+}
+
+typedef Class = {> TypeInfos,
+	var isExtern : Bool;
+	var isInterface : Bool;
+	var superClass : PathParams;
+	var interfaces : List<PathParams>;
+	var fields : List<ClassField>;
+	var statics : List<ClassField>;
+	var dynamic : Type; // null
+}
+
+typedef EnumField = {
+	var name : String;
+	var args : List<{ name : String, opt : Bool, t : Type }>; // null
+	var doc : String;
+	var platforms : Platforms;
+}
+
+typedef Enum = {> TypeInfos,
+	var isExtern : Bool;
+	var constructors : List<EnumField>;
+}
+
+typedef Typedef = {> TypeInfos,
+	var type : Type;
+}
+
+enum TypeTree {
+	TPackage( name : String, subs : List<TypeTree> );
+	TClassdecl( c : Class );
+	TEnumdecl( e : Enum );
+	TTypedecl( t : Typedef );
+}
+
+typedef TypeRoot = List<TypeTree>
+
+class TypeApi {
+	
+	public static function typeInfos( t : TypeTree ) : TypeInfos {
+		var inf : TypeInfos;
+		switch( t ) {
+		case TClassdecl(c): inf = c;
+		case TEnumdecl(e): inf = e;
+		case TTypedecl(t): inf = t;
+		case TPackage(_,_): throw "Unexpected Package";
+		}
+		return inf;
+	}
+	
+	static function leq<T>( f : T -> T -> Bool, l1 : List<T>, l2 : List<T> ) {
+		var it = l2.iterator();
+		for( e1 in l1 ) {
+			if( !it.hasNext() )
+				return false;
+			var e2 = it.next();
+			if( !f(e1,e2) )
+				return false;
+		}
+		if( it.hasNext() )
+			return false;
+		return true;
+	}
+	
+	public static function rightsEq( r1 : Rights, r2 : Rights ) {
+		if( r1 == r2 )
+			return true;
+		switch( r1 ) {
+		case RMethod(m1):
+			switch( r2 ) {
+			case RMethod(m2):
+				return m1 == m2;
+			default:
+			}
+		default:
+		}
+		return false;
+	}
+	
+	public static function typeEq( t1 : Type, t2 : Type ) {
+		switch( t1 ) {
+		case TUnknown: return t2 == TUnknown;
+		case TEnum(name,params):
+			switch( t2 ) {
+			case TEnum(name2,params2):
+				return name == name2 && leq(typeEq,params,params2);
+			default:
+			}
+		case TClass(name,params):
+			switch( t2 ) {
+			case TClass(name2,params2):
+				return name == name2 && leq(typeEq,params,params2);
+			default:
+			}
+		case TTypedef(name,params):
+			switch( t2 ) {
+			case TTypedef(name2,params2):
+				return name == name2 && leq(typeEq,params,params2);
+			default:
+			}
+		case TFunction(args,ret):
+			switch( t2 ) {
+			case TFunction(args2,ret2):
+				return leq(function(a,b) {
+					return a.name == b.name && a.opt == b.opt && typeEq(a.t,b.t);
+				},args,args2) && typeEq(ret,ret2);
+			default:
+			}
+		case TAnonymous(fields):
+			switch( t2 ) {
+			case TAnonymous(fields2):
+				return leq(function(a,b) {
+					return a.name == b.name && typeEq(a.t,b.t);
+				},fields,fields2);
+			default:
+			}
+		case TDynamic(t):
+			switch( t2 ) {
+			case TDynamic(t2):
+				if( (t == null) != (t2 == null) )
+					return false;
+				return t == null || typeEq(t,t2);
+			default:
+			}
+		}
+		return false;
+	}
+	
+	public static function fieldEq( f1 : ClassField, f2 : ClassField ) {
+		if( f1.name != f2.name )
+			return false;
+		if( !typeEq(f1.type,f2.type) )
+			return false;
+		if( f1.isPublic != f2.isPublic )
+			return false;
+		if( f1.doc != f2.doc )
+			return false;
+		if( !rightsEq(f1.get,f2.get) )
+			return false;
+		if( !rightsEq(f1.set,f2.set) )
+			return false;
+		if( (f1.params == null) != (f2.params == null) )
+			return false;
+		if( f1.params != null && f1.params.join(":") != f2.params.join(":") )
+			return false;
+		return true;
+	}
+	
+	public static function constructorEq( c1 : EnumField, c2: EnumField ) {
+		if( c1.name != c2.name )
+			return false;
+		if( c1.doc != c2.doc )
+			return false;
+		if( (c1.args == null) != (c2.args == null) )
+			return false;
+		if( c1.args != null && !leq(function(a,b) { return a.name == b.name && a.opt == b.opt && typeEq(a.t,b.t); },c1.args,c2.args) )
+			return false;
+		return true;
+	}
+	
+}

+ 364 - 0
std/tools/haxedoc/XmlParser.hx

@@ -0,0 +1,364 @@
+package tools.haxedoc;
+import tools.haxedoc.Type;
+import haxe.xml.Fast;
+
+class XmlParser {
+	
+	public var root : TypeRoot;
+	var defplat : Platforms;
+	
+	public function new() {
+		root = new List();
+	}
+	
+	public function process( x : Xml, platform : String ) {
+		defplat = new List();
+		defplat.add(platform);		
+		xroot(new Fast(x),platform);
+	}
+	
+	function mergeClasses( c : Class, c2 : Class, platform ) {		
+		// todo : compare supers & interfaces		
+		if( c.isInterface != c2.isInterface || c.isExtern != c2.isExtern )
+			return false;
+		c.platforms.add(platform);
+		
+		for( f2 in c2.fields ) {
+			var found = null;
+			for( f in c.fields )
+				if( TypeApi.fieldEq(f,f2) ) {
+					found = f;
+					break;
+				}
+			if( found == null )
+				c.fields.add(f2);
+			else
+				found.platforms.add(platform);
+		}
+		for( f2 in c2.statics ) {
+			var found = null;
+			for( f in c.statics )
+				if( TypeApi.fieldEq(f,f2) ) {
+					found = f;
+					break;
+				}
+			if( found == null )
+				c.statics.add(f2);
+			else
+				found.platforms.add(platform);
+		}
+		return true;
+	}
+
+	function mergeEnums( e : Enum, e2 : Enum, platform ) {
+		if( e.isExtern != e2.isExtern )
+			return false;
+		e.platforms.add(platform);
+		for( c2 in e2.constructors ) {
+			var found = null;
+			for( c in e.constructors )
+				if( TypeApi.constructorEq(c,c2) ) {
+					found = c;
+					break;
+				}
+			if( found == null )
+				return false; // don't allow by-platform constructor ?
+			found.platforms.add(platform);
+		}
+		return true;
+	}
+
+	function mergeTypedefs( t : Typedef, t2 : Typedef, platform ) {
+		if( !TypeApi.typeEq(t.type,t2.type) )
+			return false;
+		t.platforms.add(platform);
+		return true;
+	}
+	
+	function merge( t : TypeTree, platform ) {		
+		var inf = TypeApi.typeInfos(t);
+		var pack = inf.path.split(".");
+		var cur = root;
+		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:
+				}
+			if( !found ) {
+				var pk = new List();
+				cur.add(TPackage(p,pk));
+				cur = pk;
+			}
+		}
+		var prev = null;
+		for( ct in cur ) {
+			var tinf = try TypeApi.typeInfos(ct) catch( e : Dynamic ) continue;
+			// compare params ?
+			if( tinf.path == inf.path ) {
+				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,platform) )
+								return;
+						default:
+						}
+					case TEnumdecl(e):
+						switch( t ) {
+						case TEnumdecl(e2):
+							if( mergeEnums(e,e2,platform) )
+								return;
+						default:
+						}
+					case TTypedecl(td):
+						switch( t ) {
+						case TTypedecl(td2):
+							if( mergeTypedefs(td,td2,platform) )
+								return;
+						default:
+						}
+					case TPackage(_,_):
+					}
+				// we already have a mapping, but which is incompatible
+				throw "Incompatibilities between "+tinf.path+" in "+tinf.platforms.join(",")+" and "+platform;
+			}
+		}
+		cur.add(t);
+	}
+	
+	function mkPath( p : String ) : Path {
+		return p;
+	}
+	
+	function mkTypeParams( p : String ) : TypeParams {
+		return p.split(":");
+	}
+	
+	function mkRights( r : String ) : Rights {
+		return switch( r ) {
+		case "null": RNo;
+		case "dynamic": RDynamic;
+		case "f9dynamic": RF9Dynamic;
+		default: RMethod(r);
+		}
+	}
+	
+	function xerror( c : Fast ) : Dynamic {
+		return throw "Invalid "+c.name;
+	}
+	
+	function xroot( x : Fast, platform ) {		
+		for( c in x.elements )
+			switch( c.name ) {
+			case "class":
+				var cl = xclass(c);
+				merge(TClassdecl(cl),platform);
+			case "enum":
+				var e = xenum(c);
+				merge(TEnumdecl(e),platform);
+			case "typedef":
+				var td = xtypedef(c);
+				merge(TTypedecl(td),platform);
+			default:
+				xerror(c);
+			}
+	}
+	
+	function xpath( x : Fast ) : PathParams {
+		var path = mkPath(x.att.path);
+		var params = new List();
+		for( c in x.elements )
+			params.add(xtype(c));
+		return {
+			path : path,
+			params : params,
+		};
+	}
+	
+	function xclass( x : Fast ) : Class {
+		var csuper = null;
+		var doc = null;
+		var dynamic = null;
+		var interfaces = new List();		
+		var fields = new List();
+		var statics = new List();
+		for( c in x.elements )
+			switch( c.name ) {
+			case "haxe_doc": doc = c.innerData;
+			case "extends": csuper = xpath(c);
+			case "implements": interfaces.add(xpath(c));
+			case "haxe_dynamic": dynamic = xtype(new Fast(c.x.firstElement()));
+			default:
+				if( c.x.exists("static") )
+					statics.add(xclassfield(c));
+				else
+					fields.add(xclassfield(c));
+			}
+		return {
+			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"),
+			isInterface : x.x.exists("interface"),
+			params : mkTypeParams(x.att.params),
+			superClass : csuper,
+			interfaces : interfaces,
+			fields : fields,
+			statics : statics,
+			dynamic : dynamic,
+			platforms : defplat,
+		};
+	}
+	
+	function xclassfield( x : Fast ) : ClassField {
+		var e = x.elements;
+		var t = xtype(e.next());
+		var doc = null;
+		for( c in e )
+			switch( c.name ) {
+			case "haxe_doc": doc = c.innerData;
+			default: xerror(c);			
+			}
+		return {
+			name : x.name,
+			type : t,
+			isPublic : x.x.exists("public"),
+			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 null,
+			platforms : defplat,
+		};
+	}
+
+	function xenum( x : Fast ) : Enum {
+		var cl = new List();
+		var doc = null;
+		for( c in x.elements )
+			if( c.name == "haxe_doc" )
+				doc = c.innerData;
+			else
+				cl.add(xenumfield(c));
+		return {
+			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,
+		};
+	}
+	
+	function xenumfield( x : Fast ) : EnumField {
+		var args = null;
+		var xdoc = x.x.elementsNamed("haxe_doc").next();
+		if( x.has.a ) {
+			var names = x.att.a.split(":");
+			var elts = x.elements;
+			args = new List();
+			for( c in names ) {
+				var opt = false;
+				if( c.charAt(0) == "?" ) {
+					opt = true;
+					c = c.substr(1);
+				}
+				args.add({
+					name : c,
+					opt : opt,
+					t : xtype(elts.next()),
+				});
+			}
+		}
+		return {
+			name : x.name,
+			args : args,
+			doc : if( xdoc == null ) null else new Fast(xdoc).innerData,
+			platforms : defplat,
+		};
+	}
+
+	function xtypedef( x : Fast ) : Typedef {
+		var doc = null;
+		var t = null;
+		for( c in x.elements )
+			if( c.name == "haxe_doc" )
+				doc = c.innerData;
+			else
+				t = xtype(c);
+		return {
+			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,
+			platforms : defplat,
+		};
+	}
+	
+	function xtype( x : Fast ) : Type {
+		return switch( x.name ) {
+		case "unknown":
+			TUnknown;
+		case "e":
+			TEnum(mkPath(x.att.path),xtypeparams(x));
+		case "c":
+			TClass(mkPath(x.att.path),xtypeparams(x));
+		case "t":
+			TTypedef(mkPath(x.att.path),xtypeparams(x));
+		case "f":
+			var args = new List();
+			var aname = x.att.a.split(":");
+			var elts = x.elements;
+			for( a in aname ) {
+				var opt = false;
+				if( a.charAt(0) == "?" ) {
+					opt = true;
+					a = a.substr(1);
+				}
+				args.add({
+					name : a,
+					opt : opt,
+					t : xtype(elts.next()),
+				});
+			}
+			TFunction(args,xtype(elts.next()));
+		case "a":
+			var fields = new List();
+			for( f in x.elements )
+				fields.add({
+					name : f.name,
+					t : xtype(new Fast(f.x.firstElement())),
+				});
+			TAnonymous(fields);
+		case "d":
+			var t = null;
+			var tx = x.x.firstElement();			
+			if( tx != null )
+				t = xtype(new Fast(tx));
+			TDynamic(t);
+		default:
+			xerror(x);
+		}
+	}
+	
+	function xtypeparams( x : Fast ) : List<Type> {
+		var p = new List();
+		for( c in x.elements )
+			p.add(xtype(c));
+		return p;
+	}
+
+}

+ 9 - 0
std/tools/haxedoc/haxedoc.hxp

@@ -0,0 +1,9 @@
+<haxe selected="0">
+  <output name="haxedoc" mode="neko" out="haxedoc.n" class="tools.haxedoc.Main" lib="" cmd="" main="True" debug="False">-cmd nekotools boot haxedoc.n
+tools.haxedoc.XmlParser</output>
+  <files path="/">
+    <file path="Main.hx" />
+    <file path="Type.hx" />
+    <file path="XmlParser.hx" />
+  </files>
+</haxe>