425 lines
12 KiB
Haxe
425 lines
12 KiB
Haxe
package hscript;
|
|
import hscript.Expr;
|
|
|
|
enum TType {
|
|
TMono;
|
|
TVoid;
|
|
TInt;
|
|
TFloat;
|
|
TBool;
|
|
TDynamic;
|
|
TParam( name : String );
|
|
TUnresolved( name : String );
|
|
TNull( t : TType );
|
|
TInst( c : CClass, args : Array<TType> );
|
|
TEnum( e : CEnum, args : Array<TType> );
|
|
TType( t : CTypedef, args : Array<TType> );
|
|
TFun( args : Array<{ name : String, opt : Bool, t : TType }>, ret : TType );
|
|
TAnon( fields : Array<{ name : String, opt : Bool, t : TType }> );
|
|
}
|
|
|
|
enum CTypedecl {
|
|
CTClass( c : CClass );
|
|
CTEnum( e : CEnum );
|
|
CTTypedef( t : CTypedef );
|
|
CTAlias( t : TType );
|
|
}
|
|
|
|
typedef CClass = {
|
|
var name : String;
|
|
var params : Array<TType>;
|
|
@:optional var superClass : TType;
|
|
@:optional var constructor : CField;
|
|
var fields : Map<String,CField>;
|
|
var statics : Map<String,CField>;
|
|
}
|
|
|
|
typedef CField = {
|
|
var isPublic : Bool;
|
|
var params : Array<TType>;
|
|
var name : String;
|
|
var t : TType;
|
|
}
|
|
|
|
typedef CEnum = {
|
|
var name : String;
|
|
var params : Array<TType>;
|
|
var constructors : Map<String,TType>;
|
|
}
|
|
|
|
typedef CTypedef = {
|
|
var name : String;
|
|
var params : Array<TType>;
|
|
var t : TType;
|
|
}
|
|
|
|
class Checker {
|
|
|
|
var types : Map<String,CTypedecl> = new Map();
|
|
var locals : Map<String,TType>;
|
|
var globals : Map<String,TType> = new Map();
|
|
var t_string : TType;
|
|
var localParams : Map<String,TType>;
|
|
public var allowAsync : Bool;
|
|
|
|
public function new() {
|
|
types = new Map();
|
|
types.set("Void",CTAlias(TVoid));
|
|
types.set("Int",CTAlias(TInt));
|
|
types.set("Float",CTAlias(TFloat));
|
|
types.set("Bool",CTAlias(TBool));
|
|
types.set("Dynamic",CTAlias(TDynamic));
|
|
}
|
|
|
|
public function addXmlApi( api : Xml ) {
|
|
var types = new haxe.rtti.XmlParser();
|
|
types.process(api, "");
|
|
var todo = [];
|
|
for( v in types.root )
|
|
addXmlType(v,todo);
|
|
for( f in todo )
|
|
f();
|
|
t_string = getType("String");
|
|
}
|
|
|
|
function addXmlType(x:haxe.rtti.CType.TypeTree,todo:Array<Void->Void>) {
|
|
switch (x) {
|
|
case TPackage(name, full, subs):
|
|
for( s in subs ) addXmlType(s,todo);
|
|
case TClassdecl(c):
|
|
if( types.exists(c.path) ) return;
|
|
var cl : CClass = {
|
|
name : c.path,
|
|
params : [],
|
|
fields : new Map(),
|
|
statics : new Map(),
|
|
};
|
|
for( p in c.params )
|
|
cl.params.push(TParam(p));
|
|
todo.push(function() {
|
|
localParams = [for( t in cl.params ) c.path+"."+typeStr(t) => t];
|
|
if( c.superClass != null )
|
|
cl.superClass = getType(c.superClass.path, [for( t in c.superClass.params ) makeXmlType(t)]);
|
|
var pkeys = [];
|
|
for( f in c.fields )
|
|
if( !f.isOverride ) {
|
|
var fl : CField = { isPublic : f.isPublic, params : [], name : f.name, t : null };
|
|
for( p in f.params ) {
|
|
var pt = TParam(p);
|
|
var key = f.name+"."+p;
|
|
pkeys.push(key);
|
|
fl.params.push(pt);
|
|
localParams.set(key, pt);
|
|
}
|
|
fl.t = makeXmlType(f.type);
|
|
while( pkeys.length > 0 )
|
|
localParams.remove(pkeys.pop());
|
|
if( fl.name == "new" )
|
|
cl.constructor = fl;
|
|
else
|
|
cl.fields.set(f.name, fl);
|
|
}
|
|
localParams = null;
|
|
});
|
|
types.set(cl.name, CTClass(cl));
|
|
case TEnumdecl(e):
|
|
if( types.exists(e.path) ) return;
|
|
var en : CEnum = {
|
|
name : e.path,
|
|
params : [],
|
|
constructors: new Map(),
|
|
};
|
|
for( p in e.params )
|
|
en.params.push(TParam(p));
|
|
todo.push(function() {
|
|
localParams = [for( t in en.params ) e.path+"."+typeStr(t) => t];
|
|
localParams = null;
|
|
});
|
|
types.set(en.name, CTEnum(en));
|
|
case TTypedecl(t):
|
|
if( types.exists(t.path) ) return;
|
|
var td : CTypedef = {
|
|
name : t.path,
|
|
params : [],
|
|
t : null,
|
|
};
|
|
for( p in t.params )
|
|
td.params.push(TParam(p));
|
|
todo.push(function() {
|
|
localParams = [for( pt in td.params ) t.path+"."+typeStr(pt) => pt];
|
|
td.t = makeXmlType(t.type);
|
|
localParams = null;
|
|
});
|
|
types.set(t.path, CTTypedef(td));
|
|
case TAbstractdecl(a):
|
|
if( types.exists(a.path) ) return;
|
|
var td : CTypedef = {
|
|
name : a.path,
|
|
params : [],
|
|
t : null,
|
|
};
|
|
for( p in a.params )
|
|
td.params.push(TParam(p));
|
|
todo.push(function() {
|
|
localParams = [for( t in td.params ) a.path+"."+typeStr(t) => t];
|
|
td.t = makeXmlType(a.athis);
|
|
localParams = null;
|
|
});
|
|
types.set(a.path, CTTypedef(td));
|
|
}
|
|
}
|
|
|
|
function makeXmlType( t : haxe.rtti.CType.CType ) : TType {
|
|
return switch (t) {
|
|
case CUnknown: TMono;
|
|
case CEnum(name, params): getType(name,[for( t in params ) makeXmlType(t)]);
|
|
case CClass(name, params): getType(name,[for( t in params ) makeXmlType(t)]);
|
|
case CTypedef(name, params): getType(name,[for( t in params ) makeXmlType(t)]);
|
|
case CFunction(args, ret): TFun([for( a in args ) { name : a.name, opt : a.opt, t : makeXmlType(a.t) }], makeXmlType(ret));
|
|
case CAnonymous(fields):
|
|
inline function isOpt(m:haxe.rtti.CType.MetaData) {
|
|
if( m == null ) return false;
|
|
var b = false;
|
|
for( m in m ) if( m.name == ":optional" ) { b = true; break; }
|
|
return b;
|
|
}
|
|
TAnon([for( f in fields ) { name : f.name, t : makeXmlType(f.type), opt : isOpt(f.meta) }]);
|
|
case CDynamic(t): TDynamic;
|
|
case CAbstract(name, params):
|
|
switch( name ) {
|
|
default:
|
|
getType(name,[for( t in params ) makeXmlType(t)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function resolveType( name : String, ?args : Array<TType> ) : TType {
|
|
if( name == "Null" ) {
|
|
if( args == null || args.length != 1 ) throw "Missing Null<T> parameter";
|
|
return TNull(args[0]);
|
|
}
|
|
var t = types.get(name);
|
|
if( t == null ) return null;
|
|
if( args == null ) args = [];
|
|
return switch( t ) {
|
|
case CTClass(c): TInst(c,args);
|
|
case CTEnum(e): TEnum(e,args);
|
|
case CTTypedef(t): TType(t,args);
|
|
case CTAlias(t): t;
|
|
}
|
|
}
|
|
|
|
function getType( name : String, ?args : Array<TType> ) : TType {
|
|
if( localParams != null ) {
|
|
var t = localParams.get(name);
|
|
if( t != null ) return t;
|
|
}
|
|
var t = resolveType(name,args);
|
|
if( t == null ) {
|
|
trace(name);
|
|
return TUnresolved(name);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
public function setGlobal( name : String, type : TType ) {
|
|
globals.set(name, type);
|
|
}
|
|
|
|
public function check( expr : Expr ) {
|
|
locals = new Map();
|
|
return typeExpr(expr);
|
|
}
|
|
|
|
inline function edef( e : Expr ) {
|
|
#if hscriptPos
|
|
return e.e;
|
|
#else
|
|
return e;
|
|
#end
|
|
}
|
|
|
|
inline function error( msg : String, curExpr : Expr ) {
|
|
var e = ECustom(msg);
|
|
#if hscriptPos var e = new Error(e, curExpr.pmin, curExpr.pmax, curExpr.origin, curExpr.line); #end
|
|
throw e;
|
|
}
|
|
|
|
function saveLocals() {
|
|
return [for( k in locals.keys() ) k => locals.get(k)];
|
|
}
|
|
|
|
function makeType( t : CType, e : Expr ) : TType {
|
|
return switch (t) {
|
|
case CTPath(path, params):
|
|
var ct = resolveType(path.join("."),[for( p in params ) makeType(t,e)]);
|
|
if( ct == null ) error("Unknown type "+path, e);
|
|
return ct;
|
|
case CTFun(args, ret):
|
|
var i = 0;
|
|
return TFun([for( a in args ) { name : "p"+(i++), opt : false, t : makeType(a,e) }], makeType(ret,e));
|
|
case CTAnon(fields):
|
|
return TAnon([for( f in fields ) { name : f.name, opt : false, t : makeType(f.t,e) }]);
|
|
case CTParent(t):
|
|
return makeType(t,e);
|
|
}
|
|
}
|
|
|
|
public function typeStr( t : TType ) {
|
|
inline function makeArgs(args:Array<TType>) return args.length==0 ? "" : "<"+[for( a in args ) typeStr(t)].join(",")+">";
|
|
return switch (t) {
|
|
case TInst(c, args): c.name + makeArgs(args);
|
|
case TEnum(e, args): e.name + makeArgs(args);
|
|
case TType(t, args): t.name + makeArgs(args);
|
|
case TFun(args, ret): "(" + [for( a in args ) (a.opt?"?":"")+a.name+":"+typeStr(a.t)].join(", ")+") -> "+typeStr(ret);
|
|
case TAnon(fields): "{" + [for( f in fields ) (f.opt?"?":"")+f.name+":"+typeStr(f.t)].join(", ")+"}";
|
|
case TParam(name): name;
|
|
case TNull(t): "Null<"+typeStr(t)+">";
|
|
case TUnresolved(name): "?"+name;
|
|
default: t.getName().substr(1);
|
|
}
|
|
}
|
|
|
|
function unify( t1 : TType, t2 : TType, e : Expr ) {
|
|
if( t1 == t2 )
|
|
return;
|
|
error(typeStr(t1)+" should be "+typeStr(t2),e);
|
|
}
|
|
|
|
function apply( t : TType, params : Array<TType>, args : Array<TType> ) {
|
|
if( args.length != params.length ) throw "Invalid number of type parameters";
|
|
if( args.length != 0 ) throw "TODO";
|
|
return t;
|
|
}
|
|
|
|
function follow( t : TType ) {
|
|
return switch( t ) {
|
|
case TType(t,args): apply(t.t, t.params, args);
|
|
case TNull(t): follow(t);
|
|
default: t;
|
|
}
|
|
}
|
|
|
|
function getField( t : TType, f : String, e : Expr ) {
|
|
switch( follow(t) ) {
|
|
case TInst(c, args):
|
|
var cf = c.fields.get(f);
|
|
if( cf == null && allowAsync ) {
|
|
cf = c.fields.get("a_"+f);
|
|
if( cf != null ) {
|
|
cf = { isPublic : cf.isPublic, params : cf.params, name : cf.name, t : unasync(cf.t) };
|
|
if( cf.t == null ) cf = null;
|
|
}
|
|
}
|
|
if( cf == null ) {
|
|
if( c.superClass == null ) return null;
|
|
var ft = getField(c.superClass, f, e);
|
|
if( ft != null ) ft = apply(ft, c.params, args);
|
|
return ft;
|
|
}
|
|
return apply(cf.t, c.params, args);
|
|
case TDynamic:
|
|
return TDynamic;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function unasync( t : TType ) : TType {
|
|
switch( follow(t) ) {
|
|
case TFun(args, ret) if( args.length > 0 ):
|
|
var rargs = args.copy();
|
|
switch( follow(rargs.shift().t) ) {
|
|
case TFun([r],_): return TFun(rargs,r.t);
|
|
default:
|
|
}
|
|
default:
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function typeExpr( expr : Expr ) : TType {
|
|
switch( edef(expr) ) {
|
|
case EConst(c):
|
|
return switch (c) {
|
|
case CInt(_): TInt;
|
|
case CFloat(_): TFloat;
|
|
case CString(_): t_string;
|
|
}
|
|
case EIdent(v):
|
|
var l = locals.get(v);
|
|
if( l != null ) return l;
|
|
var g = globals.get(v);
|
|
if( g != null ) return g;
|
|
if( allowAsync ) {
|
|
g = globals.get("a_"+v);
|
|
if( g != null ) g = unasync(g);
|
|
if( g != null ) return g;
|
|
}
|
|
error("Unknown identifier "+v, expr);
|
|
case EBlock(el):
|
|
var t = TVoid;
|
|
var locals = saveLocals();
|
|
for( e in el )
|
|
t = typeExpr(e);
|
|
this.locals = locals;
|
|
return t;
|
|
case EVar(n, t, init):
|
|
var vt = t == null ? TMono : makeType(t, expr);
|
|
if( init != null ) {
|
|
var et = typeExpr(init);
|
|
if( t == null ) vt = et else unify(et,vt, init);
|
|
}
|
|
locals.set(n, vt);
|
|
return TVoid;
|
|
case EParent(e):
|
|
return typeExpr(e);
|
|
case ECall(e, eparams):
|
|
var ft = typeExpr(e);
|
|
var params = [for( e in eparams ) typeExpr(e)];
|
|
switch( follow(ft) ) {
|
|
case TFun(args, ret):
|
|
for( i in 0...params.length ) {
|
|
var a = args[i];
|
|
if( a == null ) error("Too many arguments", eparams[i]);
|
|
unify(params[i], a.t, eparams[i]);
|
|
}
|
|
for( i in params.length...args.length )
|
|
if( !args[i].opt )
|
|
error("Missing argument '"+args[i].name+"'", expr);
|
|
return ret;
|
|
case TDynamic:
|
|
return TDynamic;
|
|
default:
|
|
error(typeStr(ft)+" cannot be called", e);
|
|
}
|
|
case EField(o, f):
|
|
var ot = typeExpr(o);
|
|
var ft = getField(ot, f, expr);
|
|
if( ft == null )
|
|
error(typeStr(ot)+" has no field "+f, expr);
|
|
return ft;
|
|
case EBinop(op, e1, e2):
|
|
case EUnop(op, prefix, e):
|
|
case EIf(cond, e1, e2):
|
|
case EWhile(cond, e):
|
|
case EFor(v, it, e):
|
|
case EBreak, EContinue:
|
|
return TVoid;
|
|
case EFunction(args, e, name, ret):
|
|
case EReturn(e):
|
|
case EArray(e, index):
|
|
case EArrayDecl(e):
|
|
case ENew(cl, params):
|
|
case EThrow(e):
|
|
case ETry(e, v, t, ecatch):
|
|
case EObject(fl):
|
|
case ETernary(cond, e1, e2):
|
|
case ESwitch(e, cases, defaultExpr):
|
|
case EDoWhile(cond, e):
|
|
case EMeta(name, args, e):
|
|
}
|
|
error("TODO "+edef(expr).getName(), expr);
|
|
}
|
|
|
|
} |