Formulas.hx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. package hide.comp.cdb;
  2. import hscript.Checker;
  3. typedef Formula = { name : String, type : String, call : Dynamic -> Null<Float> }
  4. class Formulas {
  5. var ide : hide.Ide;
  6. var editor : Editor;
  7. var formulasFile : String;
  8. var formulas : Array<Formula> = [];
  9. var fmap : Map<String, Map<String, Formula>> = [];
  10. public function new( editor : Editor ) {
  11. ide = hide.Ide.inst;
  12. this.editor = editor;
  13. formulasFile = editor.config.get("cdb.formulasFile");
  14. ide.fileWatcher.register(formulasFile, reloadFile, @:privateAccess editor.searchBox /* hack to handle end-of-life */);
  15. load();
  16. }
  17. function reloadFile() {
  18. load();
  19. evaluateAll();
  20. editor.save();
  21. Editor.refreshAll();
  22. }
  23. public function evaluateAll( ?sheet : cdb.Sheet ) {
  24. var maps = new Map();
  25. for( s in editor.base.sheets ) {
  26. if( sheet != null && sheet != s ) continue;
  27. var forms = fmap.get(s.name);
  28. if( forms == null ) continue;
  29. var columns = [];
  30. for( c in s.columns )
  31. if( c.type == TInt || c.type == TFloat ) {
  32. var def = editor.getColumnProps(c).formula;
  33. columns.push({ name : c.name, def : def, opt : c.opt });
  34. }
  35. for( o in s.getLines() ) {
  36. var omapped : Dynamic = null;
  37. for( c in columns ) {
  38. var fname : String = Reflect.field(o,c.name+"__f");
  39. if( fname == null ) fname = c.def;
  40. if( fname == null ) continue;
  41. if( omapped == null )
  42. omapped = remap(o, s, maps);
  43. var v = try forms.get(fname).call(omapped) catch( e : Dynamic ) Math.NaN;
  44. if( v == null && c.opt )
  45. Reflect.deleteField(o, c.name);
  46. else {
  47. if( v == null || !Std.is(v, Float) ) v = Math.NaN;
  48. Reflect.setField(o, c.name, v);
  49. }
  50. }
  51. }
  52. }
  53. }
  54. function remap( o : Dynamic, s : cdb.Sheet, maps : Map<String,Dynamic> ) : Dynamic {
  55. var id = s.idCol != null ? Reflect.field(o, s.idCol.name) : null;
  56. var m = if( id != null ) maps.get(s.name+":"+id) else null;
  57. if( m != null )
  58. return m;
  59. m = {};
  60. if( id != null )
  61. maps.set(s.name+":"+id, m);
  62. for( c in s.columns ) {
  63. var v : Dynamic = Reflect.field(o, c.name);
  64. if( v == null ) continue;
  65. switch( c.type ) {
  66. case TRef(other):
  67. var sother = editor.base.getSheet(other);
  68. var o2 = sother.index.get(v);
  69. if( o2 == null ) continue;
  70. v = remap(o2.obj, sother, maps);
  71. case TProperties:
  72. v = remap(v, s.getSub(c), maps);
  73. case TList:
  74. var sub = s.getSub(c);
  75. v = [for( o in (v:Array<Dynamic>) ) remap(o, sub, maps)];
  76. //case TEnum(values) -- TODO later (remap String?)
  77. //case TFlags(values) -- TODO later
  78. default:
  79. }
  80. Reflect.setField(m, c.name, v);
  81. }
  82. return m;
  83. }
  84. function load() {
  85. var code = try sys.io.File.getContent(ide.getPath(formulasFile)) catch( e : Dynamic ) return;
  86. var parser = new hscript.Parser();
  87. parser.allowTypes = true;
  88. var expr = try parser.parseString(code) catch( e : Dynamic ) return;
  89. var sheetNames = new Map();
  90. for( s in editor.base.sheets )
  91. if( s.idCol != null )
  92. sheetNames.set(getTypeName(s), true);
  93. function replaceRec( e : hscript.Expr ) {
  94. switch( e.e ) {
  95. case EField({ e : EIdent(s) }, name) if( sheetNames.exists(s) ):
  96. e.e = EConst(CString(name)); // replace for faster eval
  97. default:
  98. hscript.Tools.iter(e, replaceRec);
  99. }
  100. }
  101. replaceRec(expr);
  102. formulas = [];
  103. fmap = new Map();
  104. var interp = new hscript.Interp();
  105. interp.variables.set("Math", Math);
  106. try interp.execute(expr) catch( e : hscript.Expr.Error ) {
  107. ide.error(formulasFile+": "+e.toString());
  108. return;
  109. }
  110. function browseRec(expr:hscript.Expr) {
  111. switch( expr.e ) {
  112. case EBlock(el):
  113. for( e in el )
  114. browseRec(e);
  115. case EFunction([{ t : CTPath([t]) }],_, name) if( name != null && t != null ):
  116. var value = interp.variables.get(name);
  117. if( value == null ) return;
  118. var sname = typeNameToSheet(t);
  119. var tmap = fmap.get(sname);
  120. if( tmap == null ) {
  121. tmap = new Map();
  122. fmap.set(sname, tmap);
  123. }
  124. var f : Formula = { name : name, type : t, call : value };
  125. tmap.set(name, f);
  126. formulas.push(f);
  127. default:
  128. }
  129. }
  130. browseRec(expr);
  131. }
  132. public function getList( s : cdb.Sheet ) : Array<Formula> {
  133. var type = getTypeName(s);
  134. return [for( f in formulas ) if( f.type == type ) f];
  135. }
  136. public function get( c : Cell ) : Null<Formula> {
  137. var tmap = fmap.get(c.table.sheet.name);
  138. if( tmap == null )
  139. return null;
  140. var f = Reflect.field(c.line.obj, c.column.name+"__f");
  141. if( f == null && c.column.editor != null ) {
  142. f = (c.column.editor:Editor.EditorColumnProps).formula;
  143. if( f == null ) return null;
  144. }
  145. return tmap.get(f);
  146. }
  147. public function set( c : Cell, f : Formula ) {
  148. setForValue(c.line.obj, c.table.getRealSheet(), c.column, f == null ? null : f.name);
  149. }
  150. public inline function has(c:Cell) {
  151. return Reflect.field(c.line.obj, c.column.name+"__f") != null || (c.column.editor != null && (c.column.editor:Editor.EditorColumnProps).formula != null);
  152. }
  153. public function removeFromValue( obj : Dynamic, c : cdb.Data.Column ) {
  154. Reflect.deleteField(obj, c.name+"__f");
  155. }
  156. public function setForValue( obj : Dynamic, sheet : cdb.Sheet, c : cdb.Data.Column, fname : String ) {
  157. var field = c.name+"__f";
  158. var fdef = editor.getColumnProps(c).formula;
  159. if( fname != null && fdef == fname )
  160. fname = null;
  161. if( fname == null ) {
  162. Reflect.deleteField(obj, field);
  163. var def = editor.base.getDefault(c,sheet);
  164. if( fdef != null ) {
  165. var tmap = fmap.get(sheet.name);
  166. def = try tmap.get(fdef).call(obj) catch( e : Dynamic ) Math.NaN;
  167. }
  168. if( def == null ) Reflect.deleteField(obj, c.name) else Reflect.setField(obj, c.name, def);
  169. } else
  170. Reflect.setField(obj, field, fname);
  171. }
  172. function getFormulaNameFromValue( obj : Dynamic, c : cdb.Data.Column ) {
  173. return Reflect.field(obj, c.name+"__f");
  174. }
  175. public static function getTypeName( s : cdb.Sheet ) {
  176. var name = s.name.split("@").join("_");
  177. name = name.charAt(0).toUpperCase() + name.substr(1);
  178. return name;
  179. }
  180. function typeNameToSheet( t : String ) {
  181. for( s in editor.base.sheets )
  182. if( getTypeName(s) == t )
  183. return s.name;
  184. return t;
  185. }
  186. public function createNew( c : Cell, ?onCreated : Formula -> Void ) {
  187. var name = ide.ask("Formula name");
  188. if( name == null ) return;
  189. var t = getTypeName(c.table.sheet);
  190. edit('function $name( v : $t ) {\n}\n');
  191. }
  192. public function edit( ?insert : String ) {
  193. var fullPath = ide.getPath(formulasFile);
  194. var created = false;
  195. if( !sys.FileSystem.exists(fullPath) ) {
  196. sys.io.File.saveContent(fullPath,"");
  197. created = true;
  198. }
  199. ide.open("hide.comp.cdb.FormulasView",{ path : formulasFile }, (v) -> {
  200. var script = @:privateAccess cast(v, hide.view.Script).script;
  201. if( insert != null ) {
  202. if( !created ) insert = "\n\n"+insert;
  203. script.setCode(script.code + insert);
  204. }
  205. });
  206. }
  207. }
  208. class FormulasView extends hide.view.Script {
  209. override function getScriptChecker() {
  210. var check = new hide.comp.ScriptEditor.ScriptChecker(config,"cdb formula");
  211. check.checker.allowAsync = false;
  212. var skind = new Map();
  213. for( s in ide.database.sheets ) {
  214. if( s.idCol != null )
  215. skind.set(s.name, check.addCDBEnum(s.name.split("@").join(".")));
  216. }
  217. var mfields = new Map<String,CField>();
  218. inline function field(name,t,r,?r2) {
  219. mfields.set(name, {
  220. name : name,
  221. t : r2 == null ? TFun([{ name : "v", t : t, opt : false }], r) : TFun([{ name : "v1", t: t, opt : false },{ name : "v2", t : r, opt : false }],r2),
  222. isPublic : true,
  223. complete : true,
  224. canWrite : false,
  225. params : [],
  226. });
  227. }
  228. field("ceil",TFloat,TInt);
  229. field("floor",TFloat,TInt);
  230. field("round",TFloat,TInt);
  231. field("sqrt",TFloat,TInt);
  232. field("abs",TFloat,TFloat);
  233. field("pow",TFloat,TFloat,TFloat);
  234. @:privateAccess check.checker.setGlobal("Math", TInst({
  235. name : "Math",
  236. fields : mfields,
  237. statics : [],
  238. params : [],
  239. },[]));
  240. var tstring = check.checker.types.resolve("String");
  241. var cdefs = new Map();
  242. for( s in ide.database.sheets ) {
  243. var cdef : CClass = {
  244. name : Formulas.getTypeName(s),
  245. fields : [],
  246. statics : [],
  247. params : [],
  248. };
  249. cdefs.set(s.name, cdef);
  250. }
  251. for( s in ide.database.sheets ) {
  252. var cdef = cdefs.get(s.name);
  253. for( c in s.columns ) {
  254. var t = switch( c.type ) {
  255. case TId: skind.get(s.name);
  256. case TInt, TColor, TEnum(_), TFlags(_): TInt;
  257. case TFloat: TFloat;
  258. case TBool: TBool;
  259. case TDynamic: TDynamic;
  260. case TRef(other): TInst(cdefs.get(other),[]);
  261. case TCustom(_), TImage, TLayer(_), TTileLayer, TTilePos: null;
  262. case TList, TProperties:
  263. var t = TInst(cdefs.get(s.name+"@"+c.name),[]);
  264. c.type == TList ? @:privateAccess check.checker.types.getType("Array",[t]) : t;
  265. case TString, TFile:
  266. tstring;
  267. }
  268. if( t == null ) continue;
  269. cdef.fields.set(c.name, { t : t, name : c.name, isPublic : true, complete : true, canWrite : false, params : [] });
  270. }
  271. @:privateAccess check.checker.types.types.set(cdef.name, CTClass(cdef));
  272. }
  273. return check;
  274. }
  275. static var _ = hide.ui.View.register(FormulasView);
  276. }