PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/src/sys/dotnet/fan/sys/Unit.cs

https://bitbucket.org/bedlaczech/fan-1.0
C# | 511 lines | 371 code | 73 blank | 67 comment | 103 complexity | b727864c49ca82d18a198ab4946b91f7 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. //
  2. // Copyright (c) 2008, Brian Frank and Andy Frank
  3. // Licensed under the Academic Free License version 3.0
  4. //
  5. // History:
  6. // 22 Dec 06 Andy Frank Ported from Java
  7. //
  8. using System;
  9. using System.Collections;
  10. using System.Text;
  11. namespace Fan.Sys
  12. {
  13. /// <summary>
  14. /// Unit.
  15. /// </summary>
  16. public sealed class Unit : FanObj
  17. {
  18. //////////////////////////////////////////////////////////////////////////
  19. // Database
  20. //////////////////////////////////////////////////////////////////////////
  21. public static Unit fromStr(string name) { return fromStr(name, true); }
  22. public static Unit fromStr(string name, bool check)
  23. {
  24. lock (m_byId)
  25. {
  26. Unit unit = (Unit)m_byId[name];
  27. if (unit != null || !check) return unit;
  28. throw Err.make("Unit not found: " + name).val;
  29. }
  30. }
  31. public static List list()
  32. {
  33. lock (m_list)
  34. {
  35. return m_list.dup().ro();
  36. }
  37. }
  38. public static List quantities()
  39. {
  40. return m_quantityNames;
  41. }
  42. public static List quantity(string quantity)
  43. {
  44. List list = (List)m_quantities[quantity];
  45. if (list == null) throw Err.make("Unknown unit database quantity: " + quantity).val;
  46. return list;
  47. }
  48. private static List loadDatabase()
  49. {
  50. InStream input = null;
  51. List quantityNames = new List(Sys.StrType);
  52. try
  53. {
  54. // parse etc/sys/units.fog as big serialized list which contains
  55. // lists for each quantity (first item being the name)
  56. String path = "etc/sys/units.txt";
  57. input = Env.cur().findFile(path).@in();
  58. // parse each line
  59. string curQuantityName = null;
  60. List curQuantityList = null;
  61. string line;
  62. while ((line = input.readLine()) != null)
  63. {
  64. // skip comment and blank lines
  65. line = line.Trim();
  66. if (line.StartsWith("//") || line.Length == 0) continue;
  67. // quanity sections delimited as "-- name (dim)"
  68. if (line.StartsWith("--"))
  69. {
  70. if (curQuantityName != null) m_quantities[curQuantityName] = curQuantityList.toImmutable();
  71. curQuantityName = line.Substring(2, line.IndexOf('(')-2).Trim();
  72. curQuantityList = new List(Sys.UnitType);
  73. quantityNames.add(curQuantityName);
  74. continue;
  75. }
  76. // must be a unit
  77. try
  78. {
  79. Unit unit = Unit.define(line);
  80. curQuantityList.add(unit);
  81. }
  82. catch (Exception e)
  83. {
  84. System.Console.WriteLine("WARNING: Init unit in etc/sys/units.txt: " + line);
  85. System.Console.WriteLine(" " + e);
  86. }
  87. }
  88. m_quantities[curQuantityName] = curQuantityList.toImmutable();
  89. }
  90. catch (Exception e)
  91. {
  92. try { input.close(); } catch (Exception) {}
  93. System.Console.WriteLine("WARNING: Cannot load lib/units.txt");
  94. Err.dumpStack(e);
  95. }
  96. return (List)quantityNames.toImmutable();
  97. }
  98. //////////////////////////////////////////////////////////////////////////
  99. // Parsing
  100. //////////////////////////////////////////////////////////////////////////
  101. public static Unit define(string str)
  102. {
  103. // parse
  104. Unit unit = null;
  105. try
  106. {
  107. unit = parseUnit(str);
  108. }
  109. catch (Exception)
  110. {
  111. throw ParseErr.make("Unit", str).val;
  112. }
  113. // register
  114. lock (m_byId)
  115. {
  116. // lookup by its ids
  117. for (int i=0; i<unit.m_ids.sz(); ++i)
  118. {
  119. string id = (string)unit.m_ids.get(i);
  120. if (m_byId[id] != null) throw Err.make("Unit id is already defined: " + id).val;
  121. }
  122. // this is a new definition
  123. for (int i=0; i<unit.m_ids.sz(); ++i)
  124. m_byId[(string)unit.m_ids.get(i)] = unit;
  125. m_list.add(unit);
  126. }
  127. return unit;
  128. }
  129. /**
  130. * Parse an un-interned unit:
  131. * unit := <name> [";" <symbol> [";" <dim> [";" <scale> [";" <offset>]]]]
  132. */
  133. private static Unit parseUnit(string s)
  134. {
  135. string idStrs = s;
  136. int c = s.IndexOf(';');
  137. if (c > 0) idStrs = s.Substring(0, c);
  138. List ids = FanStr.split(idStrs, Long.valueOf(','));
  139. if (c < 0) return new Unit(ids, m_dimensionless, 1, 0);
  140. string dim = s = s.Substring(c+1).Trim();
  141. c = s.IndexOf(';');
  142. if (c < 0) return new Unit(ids, parseDim(dim), 1, 0);
  143. dim = s.Substring(0, c).Trim();
  144. string scale = s = s.Substring(c+1).Trim();
  145. c = s.IndexOf(';');
  146. if (c < 0) return new Unit(ids, parseDim(dim), Double.parseDouble(scale), 0);
  147. scale = s.Substring(0, c).Trim();
  148. string offset = s.Substring(c+1).Trim();
  149. return new Unit(ids, parseDim(dim), Double.parseDouble(scale), Double.parseDouble(offset));
  150. }
  151. /**
  152. * Parse an dimension string and intern it:
  153. * dim := <ratio> ["*" <ratio>]*
  154. * ratio := <base> <exp>
  155. * base := "kg" | "m" | "sec" | "K" | "A" | "mol" | "cd"
  156. */
  157. private static Dimension parseDim(string s)
  158. {
  159. // handle empty string as dimensionless
  160. if (s.Length == 0) return m_dimensionless;
  161. // parse dimension
  162. Dimension dim = new Dimension();
  163. List ratios = FanStr.split(s, Long.valueOf((long)'*'), true);
  164. for (int i=0; i<ratios.sz(); ++i)
  165. {
  166. string r = (string)ratios.get(i);
  167. if (r.StartsWith("kg")) { dim.kg = SByte.Parse(r.Substring(2).Trim()); continue; }
  168. if (r.StartsWith("sec")) { dim.sec = SByte.Parse(r.Substring(3).Trim()); continue; }
  169. if (r.StartsWith("mol")) { dim.mol = SByte.Parse(r.Substring(3).Trim()); continue; }
  170. if (r.StartsWith("m")) { dim.m = SByte.Parse(r.Substring(1).Trim()); continue; }
  171. if (r.StartsWith("K")) { dim.K = SByte.Parse(r.Substring(1).Trim()); continue; }
  172. if (r.StartsWith("A")) { dim.A = SByte.Parse(r.Substring(1).Trim()); continue; }
  173. if (r.StartsWith("cd")) { dim.cd = SByte.Parse(r.Substring(2).Trim()); continue; }
  174. throw new Exception("Bad ratio '" + r + "'");
  175. }
  176. // intern
  177. return dim.intern();
  178. }
  179. /**
  180. * Private constructor.
  181. */
  182. private Unit(List ids, Dimension dim, double scale, double offset)
  183. {
  184. this.m_ids = checkIds(ids);
  185. this.m_dim = dim;
  186. this.m_scale = scale;
  187. this.m_offset = offset;
  188. }
  189. static List checkIds(List ids)
  190. {
  191. if (ids.sz() == 0) throw ParseErr.make("No unit ids defined").val;
  192. for (int i=0; i<ids.sz(); ++i) checkId((string)ids.get(i));
  193. return (List)ids.toImmutable();
  194. }
  195. static void checkId(string id)
  196. {
  197. if (id.Length == 0) throw ParseErr.make("Invalid unit id length 0").val;
  198. for (int i=0; i<id.Length; ++i)
  199. {
  200. int c = id[i];
  201. if (FanInt.isAlpha(c) || c == '_' || c == '%' || c == '$' || c == '/' || c > 128) continue;
  202. throw ParseErr.make("Invalid unit id " + id + " (invalid char '" + (char)c + "')").val;
  203. }
  204. }
  205. //////////////////////////////////////////////////////////////////////////
  206. // Identity
  207. //////////////////////////////////////////////////////////////////////////
  208. public override sealed bool Equals(object obj) { return this == obj; }
  209. public override sealed int GetHashCode() { return toStr().GetHashCode(); }
  210. public override sealed long hash() { return FanObj.hash(toStr()); }
  211. public override sealed Type @typeof() { return Sys.UnitType; }
  212. public override sealed string toStr() { return (string)m_ids.last(); }
  213. public List ids() { return m_ids; }
  214. public string name() { return (string)m_ids.first(); }
  215. public string symbol() { return (string)m_ids.last(); }
  216. public double scale() { return m_scale; }
  217. public double offset() { return m_offset; }
  218. public string definition()
  219. {
  220. StringBuilder s = new StringBuilder();
  221. for (int i=0; i<m_ids.sz(); ++i)
  222. {
  223. if (i > 0) s.Append(", ");
  224. s.Append((string)m_ids.get(i));
  225. }
  226. if (m_dim != m_dimensionless)
  227. {
  228. s.Append("; ").Append(m_dim);
  229. if (m_scale != 1.0 || m_offset != 0.0)
  230. {
  231. s.Append("; ").Append(m_scale);
  232. if (m_offset != 0.0) s.Append("; ").Append(m_offset);
  233. }
  234. }
  235. return s.ToString();
  236. }
  237. //////////////////////////////////////////////////////////////////////////
  238. // Dimension
  239. //////////////////////////////////////////////////////////////////////////
  240. public long kg() { return m_dim.kg; }
  241. public long m() { return m_dim.m; }
  242. public long sec() { return m_dim.sec; }
  243. public long K() { return m_dim.K; }
  244. public long A() { return m_dim.A; }
  245. public long mol() { return m_dim.mol; }
  246. public long cd() { return m_dim.cd; }
  247. internal class Dimension
  248. {
  249. public override int GetHashCode()
  250. {
  251. return (kg << 28) ^ (m << 23) ^ (sec << 18) ^
  252. (K << 13) ^ (A << 8) ^ (mol << 3) ^ cd;
  253. }
  254. public override bool Equals(object o)
  255. {
  256. Dimension x = (Dimension)o;
  257. return kg == x.kg && m == x.m && sec == x.sec && K == x.K &&
  258. A == x.A && mol == x.mol && cd == x.cd;
  259. }
  260. public override string ToString()
  261. {
  262. if (str == null)
  263. {
  264. StringBuilder s = new StringBuilder();
  265. append(s, "kg", kg); append(s, "m", m);
  266. append(s, "sec", sec); append(s, "K", K);
  267. append(s, "A", A); append(s, "mol", mol);
  268. append(s, "cd", cd);
  269. str = s.ToString();
  270. }
  271. return str;
  272. }
  273. private void append(StringBuilder s, string key, int val)
  274. {
  275. if (val == 0) return;
  276. if (s.Length > 0) s.Append('*');
  277. s.Append(key).Append(val);
  278. }
  279. public Dimension add(Dimension b)
  280. {
  281. Dimension r = new Dimension();
  282. r.kg = (sbyte)(kg + b.kg);
  283. r.m = (sbyte)(m + b.m);
  284. r.sec = (sbyte)(sec + b.sec);
  285. r.K = (sbyte)(K + b.K);
  286. r.A = (sbyte)(A + b.A);
  287. r.mol = (sbyte)(mol + b.mol);
  288. r.cd = (sbyte)(cd + b.cd);
  289. return r;
  290. }
  291. public Dimension subtract(Dimension b)
  292. {
  293. Dimension r = new Dimension();
  294. r.kg = (sbyte)(kg - b.kg);
  295. r.m = (sbyte)(m - b.m);
  296. r.sec = (sbyte)(sec - b.sec);
  297. r.K = (sbyte)(K - b.K);
  298. r.A = (sbyte)(A - b.A);
  299. r.mol = (sbyte)(mol - b.mol);
  300. r.cd = (sbyte)(cd - b.cd);
  301. return r;
  302. }
  303. public Dimension intern()
  304. {
  305. lock (m_dims)
  306. {
  307. Dimension cached = (Dimension)m_dims[this];
  308. if (cached != null) return cached;
  309. m_dims[this] = this;
  310. return this;
  311. }
  312. }
  313. internal string str;
  314. internal sbyte kg, m, sec, K, A, mol, cd;
  315. }
  316. //////////////////////////////////////////////////////////////////////////
  317. // Arithmetic
  318. //////////////////////////////////////////////////////////////////////////
  319. public Unit mult(Unit b)
  320. {
  321. lock (m_combos)
  322. {
  323. Combo key = new Combo(this, "*", b);
  324. Unit r = (Unit)m_combos[key];
  325. if (r == null)
  326. {
  327. r = findMult(this, b);
  328. m_combos[key] = r;
  329. }
  330. return r;
  331. }
  332. }
  333. private static Unit findMult(Unit a, Unit b)
  334. {
  335. // compute dim/scale of a * b
  336. Dimension dim = a.m_dim.add(b.m_dim).intern();
  337. double scale = a.m_scale * b.m_scale;
  338. // find all the matches
  339. Unit[] matches = match(dim, scale);
  340. if (matches.Length == 1) return matches[0];
  341. // right how our technique for resolving multiple matches is lame
  342. string expectedName = a.name() + "_" + b.name();
  343. for (int i=0; i<matches.Length; ++i)
  344. if (matches[i].name() == expectedName)
  345. return matches[i];
  346. // for now just give up
  347. throw Err.make("Cannot match to db: " + a + " * " + b).val;
  348. }
  349. public Unit div(Unit b)
  350. {
  351. lock (m_combos)
  352. {
  353. Combo key = new Combo(this, "/", b);
  354. Unit r = (Unit)m_combos[key];
  355. if (r == null)
  356. {
  357. r = findDiv(this, b);
  358. m_combos[key] = r;
  359. }
  360. return r;
  361. }
  362. }
  363. public Unit findDiv(Unit a, Unit b)
  364. {
  365. // compute dim/scale of a / b
  366. Dimension dim = a.m_dim.subtract(b.m_dim).intern();
  367. double scale = a.m_scale / b.m_scale;
  368. // find all the matches
  369. Unit[] matches = match(dim, scale);
  370. if (matches.Length == 1) return matches[0];
  371. // right how our technique for resolving multiple matches is lame
  372. string expectedName = a.name() + "_per_" + b.name();
  373. for (int i=0; i<matches.Length; ++i)
  374. if (matches[i].name().Contains(expectedName))
  375. return matches[i];
  376. // for now just give up
  377. throw Err.make("Cannot match to db: " + a + " / " + b).val;
  378. }
  379. private static Unit[] match(Dimension dim, double scale)
  380. {
  381. ArrayList acc = new ArrayList();
  382. lock (m_list)
  383. {
  384. for (int i=0; i<m_list.sz(); ++i)
  385. {
  386. Unit x = (Unit)m_list.get(i);
  387. if (x.m_dim == dim && approx(x.m_scale, scale))
  388. acc.Add(x);
  389. }
  390. }
  391. return (Unit[])acc.ToArray(System.Type.GetType("Fan.Sys.Unit"));
  392. }
  393. private static bool approx(double a, double b)
  394. {
  395. // pretty loose with our approximation because the database
  396. // doesn't have super great resolution for some normalizations
  397. if (a == b) return true;
  398. double t = Math.Min( Math.Abs(a/1e3), Math.Abs(b/1e3) );
  399. return Math.Abs(a - b) <= t;
  400. }
  401. internal class Combo
  402. {
  403. internal Combo(Unit a, String op, Unit b) { this.a = a; this.op = op; this.b = b; }
  404. public override int GetHashCode() { return a.GetHashCode() ^ op.GetHashCode() ^ (b.GetHashCode() << 13); }
  405. public override bool Equals(object that) { Combo x = (Combo)that; return a == x.a && op == x.op && b == x.b; }
  406. readonly Unit a;
  407. readonly string op;
  408. readonly Unit b;
  409. }
  410. //////////////////////////////////////////////////////////////////////////
  411. // Conversion
  412. //////////////////////////////////////////////////////////////////////////
  413. public double convertTo(double scalar, Unit to)
  414. {
  415. if (m_dim != to.m_dim) throw Err.make("Incovertable units: " + this + " and " + to).val;
  416. return ((scalar * this.m_scale + this.m_offset) - to.m_offset) / to.m_scale;
  417. }
  418. //////////////////////////////////////////////////////////////////////////
  419. // Fields
  420. //////////////////////////////////////////////////////////////////////////
  421. private static readonly List m_list = new List(Sys.UnitType);
  422. private static readonly Hashtable m_byId = new Hashtable(); // string id -> Unit
  423. private static readonly Hashtable m_dims = new Hashtable(); // Dimension -> Dimension
  424. private static readonly Hashtable m_quantities = new Hashtable(); // string -> List
  425. private static readonly List m_quantityNames;
  426. private static readonly Dimension m_dimensionless = new Dimension();
  427. private static readonly Hashtable m_combos = new Hashtable(); // Combo -> Unit
  428. static Unit()
  429. {
  430. m_dims[m_dimensionless] = m_dimensionless;
  431. m_quantityNames = loadDatabase();
  432. }
  433. private readonly List m_ids;
  434. private readonly double m_scale;
  435. private readonly double m_offset;
  436. private readonly Dimension m_dim;
  437. }
  438. }