|
| 1 | +package hscript; |
| 2 | + |
| 3 | +import haxe.macro.Context; |
| 4 | +import haxe.macro.Expr; |
| 5 | + |
| 6 | +#if !hscriptPos |
| 7 | +#error "LiveClass requires -D hscriptPos"; |
| 8 | +#end |
| 9 | + |
| 10 | +class LiveClass { |
| 11 | + |
| 12 | + @:persistent static var CONFIG : { api : String, srcPath : Array<String> } #if !macro = getMacroConfig() #end; |
| 13 | + |
| 14 | + static macro function getMacroConfig() { |
| 15 | + return macro $v{CONFIG}; |
| 16 | + } |
| 17 | + |
| 18 | + #if macro |
| 19 | + |
| 20 | + public static function enable( api : String, ?srcPath : Array<String> ) { |
| 21 | + if( api == null ) |
| 22 | + CONFIG = null; |
| 23 | + else |
| 24 | + CONFIG = { api : api, srcPath : srcPath ?? [".","src"] } |
| 25 | + } |
| 26 | + |
| 27 | + static function hasRet( e : Expr ) : Bool { |
| 28 | + var ret : Null<Bool> = null; |
| 29 | + function loopRec( e : Expr ) { |
| 30 | + switch( e.expr ) { |
| 31 | + case EReturn(e): |
| 32 | + ret = (e != null); |
| 33 | + case EFunction(_): |
| 34 | + default: |
| 35 | + if( ret != null ) return; |
| 36 | + haxe.macro.ExprTools.iter(e, loopRec); |
| 37 | + } |
| 38 | + } |
| 39 | + loopRec(e); |
| 40 | + return ret; |
| 41 | + } |
| 42 | + |
| 43 | + public static function build() { |
| 44 | + if( CONFIG == null ) |
| 45 | + return null; |
| 46 | + var fields = Context.getBuildFields(); |
| 47 | + var bit = 0; |
| 48 | + var idents = []; |
| 49 | + for( f in fields ) { |
| 50 | + switch( f.kind ) { |
| 51 | + case FFun(m) if( f.access.indexOf(AStatic) < 0 ): |
| 52 | + var hasRet = m.ret == null ? hasRet(m.expr) : !m.ret.match(TPath({ name : "Void "})); |
| 53 | + var eargs = { expr : EArrayDecl([for( a in m.args ) macro $i{a.name}]), pos : f.pos }; |
| 54 | + var interp = macro __INTERP.call(this, $v{bit},$eargs); |
| 55 | + var call = macro if( __INTERP_BITS & (1 << $v{bit}) != 0 ) ${hasRet ? macro return $interp : macro { $interp; return; }}; |
| 56 | + idents.push(f.name); |
| 57 | + switch( m.expr.expr ) { |
| 58 | + case EBlock(b): b.unshift(call); |
| 59 | + default: m.expr = macro { $call; ${m.expr}; }; |
| 60 | + } |
| 61 | + bit++; |
| 62 | + default: |
| 63 | + } |
| 64 | + } |
| 65 | + var pos = Context.currentPos(); |
| 66 | + var noCompletion : Metadata = [{ name : ":noCompletion", pos : pos }]; |
| 67 | + var cl = Context.getLocalClass().get(); |
| 68 | + var className = cl.name; |
| 69 | + var classFile = getClassFile(pos); |
| 70 | + fields.push({ |
| 71 | + name : "__interp_inst", |
| 72 | + pos : pos, |
| 73 | + access : [APrivate], |
| 74 | + meta : noCompletion, |
| 75 | + kind : FVar(macro : Dynamic), |
| 76 | + }); |
| 77 | + fields.push({ |
| 78 | + name : "__INTERP", |
| 79 | + pos : pos, |
| 80 | + access : [APrivate,AStatic], |
| 81 | + meta : noCompletion, |
| 82 | + kind : FVar(null,macro @:privateAccess new hscript.LiveClass.LiveClassRuntime($i{className}, $v{classFile}, $v{idents})), |
| 83 | + }); |
| 84 | + fields.push({ |
| 85 | + name : "__INTERP_BITS", |
| 86 | + pos : pos, |
| 87 | + access : [APrivate,AStatic], |
| 88 | + meta : noCompletion, |
| 89 | + kind : FVar(null, macro 0), |
| 90 | + }); |
| 91 | + return fields; |
| 92 | + } |
| 93 | + |
| 94 | + static function getClassFile( pos : Position ) { |
| 95 | + var filePath = Context.getPosInfos(pos).file.split("\\").join("/"); |
| 96 | + var classPath = Context.getClassPath(); |
| 97 | + classPath.push(Sys.getCwd()); |
| 98 | + classPath.sort((c1,c2) -> c2.length - c1.length); |
| 99 | + for( path in classPath ) { |
| 100 | + path = path.split("\\").join("/"); |
| 101 | + if( StringTools.startsWith(filePath,path) ) { |
| 102 | + filePath = filePath.substr(path.length); |
| 103 | + break; |
| 104 | + } |
| 105 | + } |
| 106 | + return filePath; |
| 107 | + } |
| 108 | + |
| 109 | + #elseif (sys || hxnodejs) |
| 110 | + |
| 111 | + // runtime |
| 112 | + |
| 113 | + public static function registerFile( file : String, onChange : Void -> Void ) { |
| 114 | + for( dir in CONFIG.srcPath ) { |
| 115 | + var path = dir+"/"+file; |
| 116 | + if( !sys.FileSystem.exists(path) ) continue; |
| 117 | + #if hl |
| 118 | + new hl.uv.Fs(null, path, function(ev) onChange()); |
| 119 | + #else |
| 120 | + throw "Not implemented for this platform"; |
| 121 | + #end |
| 122 | + return path; |
| 123 | + } |
| 124 | + return null; |
| 125 | + } |
| 126 | + |
| 127 | + static var types : hscript.Checker.CheckerTypes = null; |
| 128 | + public static function getTypes() { |
| 129 | + if( types != null ) |
| 130 | + return types; |
| 131 | + if( CONFIG == null ) throw "Checker types were not configured"; |
| 132 | + var xml = Xml.parse(sys.io.File.getContent(CONFIG.api)); |
| 133 | + types = new Checker.CheckerTypes(); |
| 134 | + types.addXmlApi(xml.firstElement()); |
| 135 | + return types; |
| 136 | + } |
| 137 | + |
| 138 | + #end |
| 139 | + |
| 140 | +} |
| 141 | + |
| 142 | +#if !macro |
| 143 | +class LiveClassRuntime { |
| 144 | + var cl : Class<Dynamic>; |
| 145 | + var type : hscript.Checker.TType; |
| 146 | + var className : String; |
| 147 | + var idents : Array<String>; |
| 148 | + var functions : Map<String,{ prev : String, value : hscript.Expr, index : Int }> = []; |
| 149 | + var newVars : Array<{ name : String, expr : hscript.Expr, type : Checker.TType }> = []; |
| 150 | + var compiledFields : Map<String,Bool>; |
| 151 | + var chk : Checker; |
| 152 | + var version = 0; |
| 153 | + public var path : String; |
| 154 | + public function new(cl, file, idents) { |
| 155 | + this.cl = cl; |
| 156 | + className = Type.getClassName(cl); |
| 157 | + this.idents = idents; |
| 158 | + this.path = LiveClass.registerFile(file, onChange); |
| 159 | + if( this.path != null ) haxe.Timer.delay(onChange,0); |
| 160 | + } |
| 161 | + |
| 162 | + function loadType() { |
| 163 | + type = LiveClass.getTypes().resolve(className); |
| 164 | + compiledFields = new Map(); |
| 165 | + switch( type ) { |
| 166 | + case TInst(c,_): |
| 167 | + for( f in c.fields ) |
| 168 | + compiledFields.set(f.name, true); |
| 169 | + default: |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + function onChange() { |
| 174 | + if( type == null ) |
| 175 | + loadType(); |
| 176 | + try { |
| 177 | + var content = sys.io.File.getContent(path); |
| 178 | + var parser = new hscript.Parser(); |
| 179 | + parser.allowTypes = true; |
| 180 | + parser.allowMetadata = true; |
| 181 | + parser.allowJSON = true; |
| 182 | + var defs = parser.parseModule(content,path); |
| 183 | + for( d in defs ) |
| 184 | + switch( d ) { |
| 185 | + case DClass(c) if( c.name == className.split(".").pop() ): |
| 186 | + var todo : Array<hscript.Expr> = []; |
| 187 | + var done = []; |
| 188 | + for( cf in c.fields ) { |
| 189 | + if( cf.access.indexOf(AStatic) >= 0 ) |
| 190 | + continue; |
| 191 | + switch( cf.kind ) { |
| 192 | + case KVar(v) if( !compiledFields.exists(cf.name) ): |
| 193 | + if( v.get != null || v.set != null ) |
| 194 | + continue; // New properties not supported |
| 195 | + todo.push({ e : EVar(cf.name,v.type,v.expr), pmin : 0, pmax : 0, line : 0, origin : null }); |
| 196 | + done.push(function(chk:Checker) { |
| 197 | + newVars.push({ name : cf.name, expr : v.expr, type : @:privateAccess chk.locals.get(cf.name) }); |
| 198 | + compiledFields.set(cf.name, true); |
| 199 | + }); |
| 200 | + case KFunction(f): |
| 201 | + var v = functions.get(cf.name); |
| 202 | + var code = hscript.Printer.toString(f.expr); |
| 203 | + if( v == null ) { |
| 204 | + v = { prev : code, value : null, index : idents.indexOf(cf.name) }; |
| 205 | + functions.set(cf.name, v); |
| 206 | + } else if( v.prev != code ) { |
| 207 | + var e : hscript.Expr = { e : EFunction(f.args,f.expr,cf.name), line : f.expr.line, pmin : f.expr.pmin, pmax : f.expr.pmax, origin : f.expr.origin }; |
| 208 | + todo.push(e); |
| 209 | + done.push(function(chk) { |
| 210 | + if( v.value == null && v.index >= 0 ) |
| 211 | + (cl:Dynamic).__INTERP_BITS |= 1 << v.index; |
| 212 | + v.value = e; |
| 213 | + v.prev = code; |
| 214 | + }); |
| 215 | + } |
| 216 | + default: |
| 217 | + } |
| 218 | + } |
| 219 | + if( todo.length > 0 ) { |
| 220 | + checkCode({ e : EBlock(todo), pmin : 0, pmax : 0, line : 0, origin : null }, done); |
| 221 | + version++; |
| 222 | + } |
| 223 | + default: |
| 224 | + } |
| 225 | + } catch( e : hscript.Expr.Error ) { |
| 226 | + log(Std.string(e)); |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + function checkCode( e : hscript.Expr, done : Array<Checker->Void> ) { |
| 231 | + var chk = new hscript.Checker(LiveClass.getTypes()); |
| 232 | + chk.allowNew = true; |
| 233 | + chk.allowPrivateAccess = true; |
| 234 | + chk.setGlobal("this", type); |
| 235 | + for( v in newVars ) |
| 236 | + chk.setGlobal(v.name, v.type); |
| 237 | + chk.check(e); |
| 238 | + for( f in done ) |
| 239 | + f(chk); |
| 240 | + return e; |
| 241 | + } |
| 242 | + |
| 243 | + static function log( msg : String ) { |
| 244 | + #if sys |
| 245 | + Sys.println(msg); |
| 246 | + #else |
| 247 | + trace(msg); |
| 248 | + #end |
| 249 | + } |
| 250 | + |
| 251 | + public function call( obj : Dynamic, id : Int, args : Array<Dynamic> ) : Dynamic { |
| 252 | + var interp : LiveClassInterp = obj.__interp_inst; |
| 253 | + if( interp == null ) { |
| 254 | + interp = new LiveClassInterp(); |
| 255 | + interp.variables.set("this", obj); |
| 256 | + obj.__interp_inst = interp; |
| 257 | + } |
| 258 | + if( interp.version != version ) { |
| 259 | + for( name => v in functions ) { |
| 260 | + if( v.value == null ) |
| 261 | + continue; |
| 262 | + interp.execute(v.value); |
| 263 | + if( v.index >= 0 ) |
| 264 | + interp.functions[v.index] = interp.variables.get(name); |
| 265 | + } |
| 266 | + interp.version = version; |
| 267 | + while( interp.newVarCount < newVars.length ) { |
| 268 | + var v = newVars[interp.newVarCount++]; |
| 269 | + interp.variables.set(v.name, v.expr == null ? null : interp.execute(v.expr)); |
| 270 | + } |
| 271 | + } |
| 272 | + return Reflect.callMethod(null,interp.functions[id],args); |
| 273 | + } |
| 274 | +} |
| 275 | + |
| 276 | +private class LiveClassInterp extends hscript.Interp { |
| 277 | + public var version = -1; |
| 278 | + public var newVarCount = 0; |
| 279 | + public var functions : Array<Dynamic> = []; |
| 280 | +} |
| 281 | +#end |
0 commit comments