ScriptEditor.hx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package hide.comp;
  2. typedef GlobalsDef = haxe.DynamicAccess<{
  3. var globals : haxe.DynamicAccess<String>;
  4. var context : String;
  5. var events : String;
  6. var cdbEnums : Array<String>;
  7. }>;
  8. class ScriptChecker {
  9. static var TYPES_SAVE = new Map();
  10. static var ERROR_SAVE = new Map();
  11. var ide : hide.Ide;
  12. public var checker : hscript.Checker;
  13. public function new( config : hide.Config, documentName : String, ?constants : Map<String,Dynamic> ) {
  14. checker = new hscript.Checker();
  15. ide = hide.Ide.inst;
  16. var files : Array<String> = config.get("script.api.files");
  17. if( files != null && files.length >= 0 ) {
  18. var types = TYPES_SAVE.get(files.join(";"));
  19. if( types == null ) {
  20. types = new hscript.Checker.CheckerTypes();
  21. for( f in files ) {
  22. // TODO : reload + recheck script when modified
  23. var content = try sys.io.File.getContent(ide.getPath(f)) catch( e : Dynamic ) { error(""+e); continue; };
  24. types.addXmlApi(Xml.parse(content).firstElement());
  25. }
  26. TYPES_SAVE.set(files.join(";"), types);
  27. }
  28. checker.types = types;
  29. }
  30. var parts = documentName.split(".");
  31. var cdbPack : String = config.get("script.cdbPackage");
  32. while( parts.length > 0 ) {
  33. var path = parts.join(".");
  34. parts.pop();
  35. var config = config.get("script.api");
  36. if( config == null ) continue;
  37. var api = (config : GlobalsDef).get(path);
  38. if( api == null ) continue;
  39. for( f in api.globals.keys() ) {
  40. var tname = api.globals.get(f);
  41. var t = checker.types.resolve(tname);
  42. if( t == null ) {
  43. var path = tname.split(".");
  44. var fields = [];
  45. while( path.length > 0 ) {
  46. var name = path.join(".");
  47. if( constants.exists(name) ) {
  48. var value : Dynamic = constants.get(name);
  49. for( f in fields )
  50. value = Reflect.field(value, f);
  51. t = typeFromValue(value);
  52. if( t == null ) t = TAnon([]);
  53. }
  54. fields.unshift(path.pop());
  55. }
  56. }
  57. if( t == null ) {
  58. error('Global type $tname not found in $files ($f)');
  59. continue;
  60. }
  61. checker.setGlobal(f, t);
  62. }
  63. if( api.context != null ) {
  64. var fields = getFields(api.context);
  65. for( f in fields )
  66. checker.setGlobal(f.name, f.t);
  67. }
  68. if( api.events != null ) {
  69. for( f in getFields(api.events) )
  70. checker.setEvent(f.name, f.t);
  71. }
  72. if( api.cdbEnums != null ) {
  73. for( c in api.cdbEnums ) {
  74. for( s in ide.database.sheets ) {
  75. if( s.name != c ) continue;
  76. var name = s.name.charAt(0).toUpperCase() + s.name.substr(1);
  77. var kname = name+"Kind";
  78. if( cdbPack != "" ) kname = cdbPack + "." + kname;
  79. var kind = checker.types.resolve(kname);
  80. if( kind == null )
  81. kind = TEnum({ name : kname, params : [], constructors : new Map() },[]);
  82. var cl : hscript.Checker.CClass = {
  83. name : name,
  84. params : [],
  85. fields : new Map(),
  86. statics : new Map()
  87. };
  88. for( o in s.all ) {
  89. var id = o.id;
  90. if( id == null || id == "" ) continue;
  91. cl.fields.set(id, { name : id, params : [], t : kind, isPublic: true });
  92. }
  93. checker.setGlobal(name, TInst(cl,[]));
  94. }
  95. }
  96. }
  97. }
  98. }
  99. function getFields( tpath : String ) {
  100. var t = checker.types.resolve(tpath);
  101. if( t == null )
  102. error("Missing type "+tpath);
  103. var fl = checker.getFields(t);
  104. if( fl == null )
  105. error(tpath+" context is not a class");
  106. return fl;
  107. }
  108. function error( msg : String ) {
  109. if( !ERROR_SAVE.exists(msg) ) {
  110. ERROR_SAVE.set(msg,true);
  111. ide.error(msg);
  112. }
  113. }
  114. function typeFromValue( value : Dynamic ) : hscript.Checker.TType {
  115. switch( std.Type.typeof(value) ) {
  116. case TNull:
  117. return null;
  118. case TInt:
  119. return TInt;
  120. case TFloat:
  121. return TFloat;
  122. case TBool:
  123. return TBool;
  124. case TObject:
  125. var fields = [];
  126. for( f in Reflect.fields(value) ) {
  127. var t = typeFromValue(Reflect.field(value,f));
  128. if( t == null ) continue;
  129. fields.push({ name : f, t : t, opt : false });
  130. }
  131. return TAnon(fields);
  132. case TClass(c):
  133. return checker.types.resolve(Type.getClassName(c),[]);
  134. case TEnum(e):
  135. return checker.types.resolve(Type.getEnumName(e),[]);
  136. case TFunction, TUnknown:
  137. }
  138. return null;
  139. }
  140. public function check( script : String, checkTypes = true ) {
  141. var parser = new hscript.Parser();
  142. parser.allowMetadata = true;
  143. parser.allowTypes = true;
  144. parser.allowJSON = true;
  145. try {
  146. var expr = parser.parseString(script, "");
  147. if( checkTypes ) {
  148. checker.allowAsync = true;
  149. checker.check(expr);
  150. }
  151. return null;
  152. } catch( e : hscript.Expr.Error ) {
  153. return e;
  154. }
  155. }
  156. }
  157. class ScriptEditor extends CodeEditor {
  158. static var INIT_DONE = false;
  159. var checker : ScriptChecker;
  160. var checkTypes : Bool;
  161. public function new( script : String, ?checker : ScriptChecker, ?parent : Element, ?root : Element ) {
  162. if( !INIT_DONE ) {
  163. INIT_DONE = true;
  164. (monaco.Languages : Dynamic).typescript.javascriptDefaults.setCompilerOptions({ noLib: true, allowNonTsExtensions: true }); // disable js stdlib completion
  165. monaco.Languages.registerCompletionItemProvider("javascript", {
  166. provideCompletionItems : function(model,position,_,_) {
  167. var comp : ScriptEditor = (model : Dynamic).__comp__;
  168. return comp.getCompletion(position);
  169. }
  170. });
  171. }
  172. super(script, "javascript", parent,root);
  173. if( checker == null ) {
  174. checker = new ScriptChecker(new hide.Config(),"");
  175. checkTypes = false;
  176. }
  177. this.checker = checker;
  178. onChanged = doCheckScript;
  179. haxe.Timer.delay(function() doCheckScript(), 0);
  180. }
  181. function getCompletion( position : monaco.Position ) : Array<monaco.Languages.CompletionItem> {
  182. var checker = checker.checker;
  183. var globals = checker.getGlobals();
  184. return [for( k in globals.keys() ) {
  185. var t = globals.get(k);
  186. if( StringTools.startsWith(k,"a_") ) {
  187. t = checker.unasync(t);
  188. k = k.substr(2);
  189. }
  190. var isFun = checker.follow(t).match(TFun(_));
  191. if( isFun ) {
  192. {
  193. kind : Field,
  194. label : k,
  195. detail : hscript.Checker.typeStr(t),
  196. commitCharacters: ["("],
  197. }
  198. } else {
  199. {
  200. kind : Field,
  201. label : k,
  202. detail : hscript.Checker.typeStr(t),
  203. }
  204. }
  205. }];
  206. }
  207. function doCheckScript() {
  208. var script = code;
  209. var error = checker.check(script, checkTypes);
  210. if( error == null )
  211. clearError();
  212. else
  213. setError(hscript.Printer.errorToString(error), error.line, error.pmin, error.pmax);
  214. }
  215. }