From ed78703090ff4ca1f93c5b9f4681eca533280e3d Mon Sep 17 00:00:00 2001 From: ncannasse Date: Wed, 12 Sep 2018 18:00:19 +0200 Subject: [PATCH] added type checker (partial) --- hscript/Checker.hx | 425 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 hscript/Checker.hx diff --git a/hscript/Checker.hx b/hscript/Checker.hx new file mode 100644 index 0000000..9feb43c --- /dev/null +++ b/hscript/Checker.hx @@ -0,0 +1,425 @@ +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 ); + TEnum( e : CEnum, args : Array ); + TType( t : CTypedef, args : Array ); + 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; + @:optional var superClass : TType; + @:optional var constructor : CField; + var fields : Map; + var statics : Map; +} + +typedef CField = { + var isPublic : Bool; + var params : Array; + var name : String; + var t : TType; +} + +typedef CEnum = { + var name : String; + var params : Array; + var constructors : Map; +} + +typedef CTypedef = { + var name : String; + var params : Array; + var t : TType; +} + +class Checker { + + var types : Map = new Map(); + var locals : Map; + var globals : Map = new Map(); + var t_string : TType; + var localParams : Map; + 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:ArrayVoid>) { + 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 { + if( name == "Null" ) { + if( args == null || args.length != 1 ) throw "Missing Null 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 { + 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) 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, args : Array ) { + 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); + } + +} \ No newline at end of file