123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- package hide.comp;
- typedef GlobalsDef = haxe.DynamicAccess<{
- var globals : haxe.DynamicAccess<String>;
- var context : String;
- var contexts : Array<String>;
- var events : String;
- var evalTo : String;
- var allowGlobalsDefine : Bool;
- var cdbEnums : Array<String>;
- }>;
- class ScriptChecker {
- static var TYPES_SAVE = new Map();
- static var ERROR_SAVE = new Map();
- static var TYPE_CHECK_HOOKS : Array<ScriptChecker->Void> = [];
- var ide : hide.Ide;
- var apiFiles : Array<String>;
- var checkEvents : Bool;
- public var config : hide.Config;
- public var documentName : String;
- public var constants : Map<String,Dynamic>;
- public var evalTo : String;
- public var checker(default,null) : hscript.Checker;
- public function new( config : hide.Config, documentName : String, ?constants : Map<String,Dynamic> ) {
- this.config = config;
- this.documentName = documentName;
- this.constants = constants == null ? new Map() : constants;
- ide = hide.Ide.inst;
- apiFiles = config.get("script.api.files");
- reload();
- }
- public function reload() {
- checker = new hscript.Checker();
- checker.allowAsync = true;
- if( apiFiles != null && apiFiles.length >= 0 ) {
- var types = TYPES_SAVE.get(apiFiles.join(";"));
- if( types == null ) {
- types = new hscript.Checker.CheckerTypes();
- for( f in apiFiles ) {
- var content = try sys.io.File.getContent(ide.getPath(f)) catch( e : Dynamic ) { error(""+e); continue; };
- types.addXmlApi(Xml.parse(content).firstElement());
- }
- TYPES_SAVE.set(apiFiles.join(";"), types);
- }
- checker.types = types;
- }
- var parts = documentName.split(".");
- var apis = [];
- while( parts.length > 0 ) {
- var path = parts.join(".");
- parts.pop();
- var config = config.get("script.api");
- if( config == null ) continue;
- var api = (config : GlobalsDef).get(path);
- if( api == null ) {
- path = ~/\[group=[^\]]+?\]/g.replace(path,"");
- api = (config : GlobalsDef).get(path);
- }
- if( api != null )
- apis.unshift(api);
- }
- var cdbPack : String = config.get("script.cdbPackage");
- var contexts = [];
- var allowGlobalsDefine = false;
- checkEvents = false;
- for( api in apis ) {
- for( f in api.globals.keys() ) {
- var tname = api.globals.get(f);
- if( tname == null ) {
- checker.removeGlobal(f);
- continue;
- }
- var isClass = tname.charCodeAt(0) == '#'.code;
- if( isClass ) tname = tname.substr(1);
- var t = checker.types.resolve(tname);
- if( t == null ) {
- var path = tname.split(".");
- var fields = [];
- while( path.length > 0 ) {
- var name = path.join(".");
- if( constants.exists(name) ) {
- var value : Dynamic = constants.get(name);
- for( f in fields )
- value = Reflect.field(value, f);
- t = typeFromValue(value);
- if( t == null ) t = TAnon([]);
- }
- fields.unshift(path.pop());
- }
- }
- if( t == null ) {
- error('Global type $tname not found in $apiFiles ($f)');
- continue;
- }
- if( isClass ) {
- switch( t ) {
- case TEnum(e,_):
- t = TAnon([for( c in e.constructors ) { name : c.name, opt : false, t : c.args == null ? t : TFun(c.args,t) }]);
- default:
- error('Cannot process class type $tname');
- }
- }
- checker.setGlobal(f, t);
- }
- if( api.context != null )
- contexts = [api.context];
- if( api.contexts != null )
- contexts = api.contexts;
- if( api.allowGlobalsDefine != null )
- allowGlobalsDefine = api.allowGlobalsDefine;
- if( api.events != null ) {
- for( f in getFields(api.events) )
- checker.setEvent(f.name, f.t);
- checkEvents = true;
- }
- if( api.cdbEnums != null ) {
- for( c in api.cdbEnums )
- addCDBEnum(c, cdbPack);
- }
- if( api.evalTo != null )
- this.evalTo = api.evalTo;
- }
- for( context in contexts ) {
- var ctx = checker.types.resolve(context);
- if( ctx == null )
- error(context+" is not defined");
- else {
- switch( ctx ) {
- case TInst(c,_):
- var cc = c;
- while( true ) {
- for( f in cc.fields ) if( f.t.match(TFun(_)) ) f.isPublic = true; // allow access to private methods
- if( cc.superClass == null ) break;
- cc = switch( cc.superClass ) {
- case TInst(c,_): c;
- default: throw "assert";
- }
- }
- checker.setGlobals(c);
- default: error(context+" is not a class");
- }
- }
- }
- checker.allowUntypedMeta = true;
- checker.allowGlobalsDefine = allowGlobalsDefine;
- for( c in TYPE_CHECK_HOOKS )
- c(this);
- }
- function getFields( tpath : String ) {
- var t = checker.types.resolve(tpath);
- if( t == null )
- error("Missing type "+tpath);
- var fl = checker.getFields(t);
- if( fl == null )
- error(tpath+" is not a class");
- return fl;
- }
- function error( msg : String ) {
- if( !ERROR_SAVE.exists(msg) ) {
- ERROR_SAVE.set(msg,true);
- ide.error(msg);
- }
- }
- public function addCDBEnum( name : String, ?cdbPack : String ) {
- var path = name.split(".");
- var sname = path.join("@");
- var objPath = null;
- if( path.length > 1 ) { // might be a scoped id
- var objID = this.constants.get("cdb.objID");
- objPath = objID == null ? [] : objID.split(":");
- }
- for( s in ide.database.sheets ) {
- if( s.name != sname ) continue;
- var name = path[path.length - 1];
- name = name.charAt(0).toUpperCase() + name.substr(1);
- var kname = path.join("_")+"Kind";
- kname = kname.charAt(0).toUpperCase() + kname.substr(1);
- if( cdbPack != "" ) kname = cdbPack + "." + kname;
- var kind = checker.types.resolve(kname);
- if( kind == null )
- kind = TEnum({ name : kname, params : [], constructors : [] },[]);
- var cl : hscript.Checker.CClass = {
- name : name,
- params : [],
- fields : new Map(),
- statics : new Map()
- };
- var refPath = s.idCol.scope == null ? null : objPath.slice(0, s.idCol.scope).join(":")+":";
- for( o in s.all ) {
- var id = o.id;
- if( id == null || id == "" ) continue;
- if( refPath != null ) {
- if( !StringTools.startsWith(id, refPath) ) continue;
- id = id.substr(refPath.length);
- }
- cl.fields.set(id, { name : id, params : [], canWrite : false, t : kind, isPublic: true, complete : true });
- }
- checker.setGlobal(name, TInst(cl,[]));
- return kind;
- }
- return null;
- }
- function typeFromValue( value : Dynamic ) : hscript.Checker.TType {
- switch( std.Type.typeof(value) ) {
- case TNull:
- return null;
- case TInt:
- return TInt;
- case TFloat:
- return TFloat;
- case TBool:
- return TBool;
- case TObject:
- var fields = [];
- for( f in Reflect.fields(value) ) {
- var t = typeFromValue(Reflect.field(value,f));
- if( t == null ) continue;
- fields.push({ name : f, t : t, opt : false });
- }
- return TAnon(fields);
- case TClass(c):
- return checker.types.resolve(Type.getClassName(c),[]);
- case TEnum(e):
- return checker.types.resolve(Type.getEnumName(e),[]);
- case TFunction, TUnknown:
- }
- return null;
- }
- public function makeParser() {
- var parser = new hscript.Parser();
- parser.allowMetadata = true;
- parser.allowTypes = true;
- parser.allowJSON = true;
- return parser;
- }
- public function getCompletion( script : String ) {
- var parser = makeParser();
- parser.resumeErrors = true;
- var expr = parser.parseString(script,""); // should not error
- try {
- var et = checker.check(expr,null,true);
- return null;
- } catch( e : hscript.Checker.Completion ) {
- // ignore
- return e.t;
- }
- }
- public function check( script : String, checkTypes = true ) {
- var parser = makeParser();
- try {
- var expr = parser.parseString(script, "");
- if( checkEvents ) {
- function checkRec(e:hscript.Expr) {
- switch( e.e ) {
- case EFunction(_,_,name,_):
- if( name != null && StringTools.startsWith(name,"on") && name.charCodeAt(2) > 'A'.code && name.charCodeAt(2) < 'Z'.code && @:privateAccess !checker.events.exists(name) )
- @:privateAccess checker.error('Unknown event $name', e);
- default:
- hscript.Tools.iter(e, checkRec);
- }
- }
- checkRec(expr);
- }
- if( checkTypes ) {
- var et = checker.check(expr);
- if( evalTo != null ) {
- var t = checker.types.resolve(evalTo);
- if( t == null ) {
- error('EvalTo type $evalTo not found');
- return null;
- }
- checker.unify(et, t, expr);
- }
- }
- return null;
- } catch( e : hscript.Expr.Error ) {
- return e;
- }
- }
- }
- class ScriptEditor extends CodeEditor {
- static var INIT_DONE = false;
- var checker : ScriptChecker;
- var checkTypes : Bool;
- public function new( script : String, ?checker : ScriptChecker, ?parent : Element, ?root : Element, ?lang ) {
- if( !INIT_DONE ) {
- INIT_DONE = true;
- (monaco.Languages : Dynamic).typescript.javascriptDefaults.setCompilerOptions({ noLib: true, allowNonTsExtensions: true }); // disable js stdlib completion
- monaco.Languages.registerCompletionItemProvider("javascript", {
- triggerCharacters : ["."],
- provideCompletionItems : function(model,position,_,_) {
- var comp : ScriptEditor = (model : Dynamic).__comp__;
- var code = model.getValueInRange({startLineNumber: 1, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column});
- return comp.getCompletion(code.length);
- }
- });
- }
- super(script, lang, parent,root);
- if( checker == null ) {
- checker = new ScriptChecker(new hide.Config(),"");
- checkTypes = false;
- } else {
- var files = @:privateAccess checker.apiFiles;
- if( files != null ) {
- for( f in files )
- ide.fileWatcher.register(f, function() {
- @:privateAccess ScriptChecker.TYPES_SAVE = [];
- haxe.Timer.delay(function() { try checker.reload() catch( e : Dynamic ) {}; doCheckScript(); }, 100);
- }, root);
- }
- }
- this.checker = checker;
- onChanged = doCheckScript;
- haxe.Timer.delay(function() doCheckScript(), 0);
- }
- function getCompletion( position : Int ) : Array<monaco.Languages.CompletionItem> {
- var script = code.substr(0,position);
- var vars = checker.checker.getGlobals();
- if( script.charCodeAt(script.length-1) == ".".code ) {
- vars = [];
- var t = checker.getCompletion(script);
- if( t != null ) {
- var fields = checker.checker.getFields(t);
- for( f in fields )
- vars.set(f.name, f.t);
- }
- }
- var checker = checker.checker;
- return [for( k in vars.keys() ) {
- var t = vars.get(k);
- if( StringTools.startsWith(k,"a_") ) {
- var t2 = checker.unasync(t);
- if( t2 != null ) {
- t = t2;
- k = k.substr(2);
- }
- }
- var isFun = checker.follow(t).match(TFun(_));
- if( isFun ) {
- {
- kind : Method,
- label : k,
- detail : hscript.Checker.typeStr(t),
- commitCharacters: ["("],
- }
- } else {
- {
- kind : Field,
- label : k,
- detail : hscript.Checker.typeStr(t),
- }
- }
- }];
- }
- public function doCheckScript() {
- var script = code;
- var error = checker.check(script, checkTypes);
- if( error == null )
- clearError();
- else
- setError(hscript.Printer.errorToString(error), error.line, error.pmin, error.pmax);
- }
- public static function register( cl : Class<Dynamic> ) : Bool {
- @:privateAccess ScriptChecker.TYPE_CHECK_HOOKS.push(function(checker) {
- Type.createInstance(cl,[checker]);
- });
- return true;
- }
- }
|