PikaScript
PikaScriptImpl.h
1 
42 #ifndef PikaScriptImpl_h
43 #define PikaScriptImpl_h
44 
45 #include <math.h>
46 #include <time.h>
47 #include <string.h>
48 #include <algorithm>
49 #include <functional>
50 #include <iostream>
51 #include <fstream>
52 #include <limits>
53 #if !defined(PikaScript_h)
54 #include "PikaScript.h"
55 #endif
56 
57 namespace Pika {
58 
59 template<typename T> inline T mini(T a, T b) { return (a < b) ? a : b; }
60 template<typename T> inline T maxi(T a, T b) { return (a < b) ? b : a; }
61 
62 // Usually I am pretty militant against macros, but sorry, the following ones are just too handy.
63 
64 // FIX : is there *some* way to solve this without ugly macros?
65 #if (PIKA_UNICODE)
66  #define STR(s) L##s
67 #else
68  #define STR(x) x
69 #endif
70 #define TMPL template<class CFG>
71 #define T_TYPE(x) typename Script<CFG>::x
72 
73 /* --- Utility Routines --- */
74 
75 inline uint uintChar(char c) { return uchar(c); }
76 inline uint uintChar(wchar_t c) { return c; }
77 template<class C> std::basic_ostream<C>& xcout();
78 template<class C> std::basic_istream<C>& xcin();
79 template<> inline std::basic_ostream<char>& xcout() { return std::cout; }
80 template<> inline std::basic_ostream<wchar_t>& xcout() { return std::wcout; }
81 template<> inline std::basic_istream<char>& xcin() { return std::cin; }
82 template<> inline std::basic_istream<wchar_t>& xcin() { return std::wcin; }
83 template<> inline std::string toStdString(const std::string& s) { return s; }
84 
85 inline ulong shiftRight(ulong l, int r) { return l >> r; }
86 inline ulong shiftLeft(ulong l, int r) { return l << r; }
87 inline ulong bitAnd(ulong l, ulong r) { return l & r; }
88 inline ulong bitOr(ulong l, ulong r) { return l | r; }
89 inline ulong bitXor(ulong l, ulong r) { return l ^ r; }
90 
91 template<class C> inline bool isSymbolChar(C c) {
92  return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$';
93 }
94 
95 template<class C> inline bool maybeWhite(C c) { return (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '/'); }
96 
97 template<class S> std::string toStdString(const S& s) { return std::string(s.begin(), s.end()); }
98 template<> std::string toStdString(const std::string& s);
99 inline std::string& toStdString(std::string& s) { return s; }
100 
101 template<class S> ulong hexToLong(typename S::const_iterator& p, const typename S::const_iterator& e) {
102  assert(p <= e);
103  ulong l = 0;
104  for (; p < e && ((*p >= '0' && *p <= '9') || (*p >= 'A' && *p <= 'F') || (*p >= 'a' && *p <= 'f')); ++p)
105  l = (l << 4) + (*p <= '9' ? *p - '0' : (*p & ~0x20) - ('A' - 10));
106  return l;
107 }
108 
109 template<class S> long stringToLong(typename S::const_iterator& p, const typename S::const_iterator& e) {
110  assert(p <= e);
111  bool negative = (e - p > 1 && ((*p == '+' || *p == '-') && p[1] >= '0' && p[1] <= '9') ? (*p++ == '-') : false);
112  long l = 0;
113  for (; p < e && *p >= '0' && *p <= '9'; ++p) l = l * 10 + (*p - '0');
114  return negative ? -l : l;
115 }
116 
117 template<class S, class T> S intToString(T i, int radix, int minLength) {
118  assert(2 <= radix && radix <= 16);
119  assert(0 <= minLength && minLength <= static_cast<int>(sizeof (T) * 8));
120  typename S::value_type buffer[sizeof (T) * 8 + 1], * p = buffer + sizeof (T) * 8 + 1, * e = p - minLength;
121  for (T x = i; p > e || x != 0; x /= radix) {
122  assert(p >= buffer + 2);
123  *--p = STR("fedcba9876543210123456789abcdef")[15 + x % radix]; // Mirrored hex string to handle negative x.
124  }
125  if (std::numeric_limits<T>::is_signed && i < 0) *--p = '-';
126  return S(p, buffer + sizeof (T) * 8 + 1 - p);
127 }
128 
129 template<class S> double stringToDouble(typename S::const_iterator& p, const typename S::const_iterator& e) {
130  assert(p <= e);
131  double d = 0, sign = (e - p > 1 && (*p == '+' || *p == '-') ? (*p++ == '-' ? -1.0 : 1.0) : 1.0);
132  if (std::numeric_limits<double>::has_infinity && e - p >= 8 && equal(p, p + 8, STR("infinity"))) {
133  p += 8;
134  d = std::numeric_limits<double>::infinity();
135  } else if (p < e && *p >= '0' && *p <= '9') {
136  if (*p == '0') ++p; else do { d = d * 10.0 + (*p - '0'); } while (++p < e && *p >= '0' && *p <= '9');
137  if (e - p > 1 && *p == '.' && p[1] >= '0' && p[1] <= '9') {
138  ++p;
139  double f = 1.0;
140  do { d += (*p - '0') * (f *= 0.1); } while (++p < e && *p >= '0' && *p <= '9');
141  }
142  if (e - p > 1 && (*p == 'E' || *p == 'e')) {
143  typename S::const_iterator b = p;
144  d *= pow(10, double(stringToLong<S>(++p, e)));
145  if (p == b + 1) p = b;
146  }
147  }
148  return d * sign;
149 }
150 
151 template<class S> bool stringToDouble(const S& s, double& d) {
152  typename S::const_iterator b = s.begin(), e = s.end(), p = b;
153  d = stringToDouble<S>(p, e);
154  return (p != b && p >= e);
155 }
156 
157 template<class S> S doubleToString(double d, int precision) {
158  assert(1 <= precision && precision <= 24);
159  const double EPSILON = 1.0e-300, SMALL = 1.0e-5, LARGE = 1.0e+10;
160  double x = fabs(d), y = x;
161  if (y <= EPSILON) return S(STR("0"));
162  else if (precision >= 12 && y < LARGE && long(d) == d) return intToString<S, long>(long(d));
163  else if (std::numeric_limits<double>::has_infinity && x == std::numeric_limits<double>::infinity())
164  return d < 0 ? S(STR("-infinity")) : S(STR("+infinity"));
165  typename S::value_type buffer[32], * bp = buffer + 2, * dp = bp, * pp = dp + 1, * ep = pp + precision;
166  for (; x >= 10.0 && pp < ep; x *= 0.1) ++pp; // Normalize values > 10 and move period position.
167  if (pp >= ep || y <= SMALL || y >= LARGE) { // Exponential treatment of very small or large values.
168  double e = floor(log10(y) + 1.0e-10);
169  S exps(e >= 0 ? S(STR("e+")) : S(STR("e")));
170  exps += intToString<S, int>(int(e));
171  int maxp = 15; // Limit precision because of rounding errors in log10 etc
172  for (double f = fabs(e); f >= 8; f /= 10) --maxp;
173  return (doubleToString<S>(d * pow(0.1, e), mini(maxp, precision)) += exps);
174  }
175  for (; x < 1.0 && dp < buffer + 32; ++ep, x *= 10.0) { // For values < 1, spit out leading 0's and increase precision.
176  *dp++ = '0';
177  if (dp == pp) *dp++ = '9'; // Hop over period position (set to 9 to avoid when eliminating 9's).
178  }
179  for (; dp < ep; ) { // Exhaust all remaining digits of mantissa into buffer.
180  uint ix = uint(x);
181  *dp++ = ix + '0';
182  if (dp == pp) *dp++ = '9'; // Hop over period position (set to 9 to avoid when eliminating 9's).
183  x = (x - ix) * 10.0;
184  }
185  if (x >= 5) { // If remainder is >= 5, increment trailing 9's...
186  while (dp[-1] == '9') *--dp = '0';
187  if (dp == bp) *--bp = '1'; else dp[-1]++; // If we are at spare position, set to '1' and include, otherwise, increment last non-9.
188  }
189  *pp = '.';
190  if (ep > pp) while (ep[-1] == '0') --ep;
191  if (ep - 1 == pp) --ep;
192  if (d < 0) *--bp = '-';
193  return S(bp, ep - bp);
194 }
195 
196 const int ESCAPE_CODE_COUNT = 10;
197 
198 template<class S> S unescape(typename S::const_iterator& p, const typename S::const_iterator& e) {
199  assert(p <= e);
200  typedef typename S::value_type CHAR;
201  static const CHAR ESCAPE_CHARS[ESCAPE_CODE_COUNT] = { '\\', '\"', '\'', 'a', 'b', 'f', 'n', 'r', 't', 'v' };
202  static const CHAR ESCAPE_CODES[ESCAPE_CODE_COUNT] = { '\\', '\"', '\'', '\a', '\b', '\f', '\n', '\r', '\t', '\v' };
203  if (p >= e || (*p != '"' && *p != '\'')) throw Exception<S>(STR("Invalid string literal"));
204  S d;
205  typename S::const_iterator b = ++p;
206  if (p[-1] == '\'') while (e - (p = std::find(p, e, '\'')) > 1 && p[1] == '\'') { d += S(b, ++p); b = ++p; }
207  else while (p < e && *p != '\"') {
208  if (*p == '\\' && p + 1 < e) {
209  d += S(b, p);
210  const CHAR* f = std::find(ESCAPE_CHARS, ESCAPE_CHARS + ESCAPE_CODE_COUNT, *++p);
211  long l;
212  if (f != ESCAPE_CHARS + ESCAPE_CODE_COUNT) { ++p; l = ESCAPE_CODES[f - ESCAPE_CHARS]; }
213  else if (*p == 'x') { b = ++p; l = hexToLong<S>(p, (e - p > 2 ? p + 2 : e)); }
214  else if (*p == 'u') { b = ++p; l = hexToLong<S>(p, (e - p > 4 ? p + 4 : e)); }
215  else { b = p; l = stringToLong<S>(p, e); }
216  if (p == b) throw Exception<S>(STR("Invalid escape character"));
217  b = p;
218  d += CHAR(l);
219  } else ++p;
220  }
221  if (p >= e) throw Exception<S>(STR("Unterminated string"));
222  return (d += S(b, p++));
223 }
224 
225 template<class S> S escape(const S& s) {
226  typedef typename S::value_type CHAR;
227  static const CHAR ESCAPE_CHARS[ESCAPE_CODE_COUNT] = { '\\', '\"', '\'', 'a', 'b', 'f', 'n', 'r', 't', 'v' };
228  static const CHAR ESCAPE_CODES[ESCAPE_CODE_COUNT] = { '\\', '\"', '\'', '\a', '\b', '\f', '\n', '\r', '\t', '\v' };
229  typename S::const_iterator b = s.begin(), e = s.end();
230  bool needToBackUp = false; // If we need to re-escape with " " and string contained " or \ we need to start over from the beginning.
231  for (; b < e && *b >= 32 && *b <= 126 && *b != '\''; ++b) needToBackUp = needToBackUp || (*b == '\\' || *b == '\"');
232  if (b >= e) return ((S(STR("'")) += s) += '\'');
233  if (needToBackUp) b = s.begin();
234  typename S::const_iterator l = s.begin();
235  S d = S(STR("\""));
236  while (true) {
237  while (b < e && *b >= 32 && *b <= 126 && *b != '\\' && *b != '\"') ++b;
238  d += S(l, b);
239  if (b >= e) break;
240  const CHAR* f = std::find(ESCAPE_CODES, ESCAPE_CODES + ESCAPE_CODE_COUNT, *b);
241  if (f != ESCAPE_CODES + ESCAPE_CODE_COUNT) (d += '\\') += ESCAPE_CHARS[f - ESCAPE_CODES];
242  else if (uintChar(*b) == uintChar(*b)) (d += STR("\\x")) += intToString<S>(uintChar(*b), 16, 2);
243  else (d += STR("\\u")) += intToString<S>(uintChar(*b), 16, 4);
244  l = ++b;
245  }
246  return (d += '\"');
247 }
248 
249 /* --- STLValue --- */
250 
251 template<class S> STLValue<S>::operator bool() const {
252  if (S(*this) == STR("false")) return false;
253  else if (S(*this) == STR("true")) return true;
254  else throw Exception<S>(S(STR("Invalid boolean: ")) += escape(S(*this)));
255 }
256 
257 template<class S> STLValue<S>::operator long() const {
258  typename S::const_iterator p = S::begin();
259  long y = stringToLong<S>(p, S::end());
260  if (p == S::begin() || p < S::end()) throw Exception<S>(S(STR("Invalid integer: ")) += escape(S(*this)));
261  return y;
262 }
263 
264 template<class S> STLValue<S>::operator double() const {
265  double d;
266  if (!stringToDouble(*this, d)) throw Exception<S>(S(STR("Invalid number: ")) += escape(S(*this)));
267  return d;
268 }
269 
270 template<class S> bool STLValue<S>::operator<(const STLValue& r) const {
271  double lv, rv;
272  bool lnum = stringToDouble(*this, lv), rnum = stringToDouble(r, rv);
273  return (lnum == rnum ? (lnum ? lv < rv : (const S&)(*this) < (const S&)(r)) : lnum);
274 }
275 
276 template<class S> bool STLValue<S>::operator==(const STLValue& r) const {
277  double lv, rv;
278  bool lnum = stringToDouble(*this, lv), rnum = stringToDouble(r, rv);
279  return (lnum == rnum && (lnum ? lv == rv : (const S&)(*this) == (const S&)(r)));
280 }
281 
282 template<class S> const STLValue<S> STLValue<S>::operator[](const STLValue& i) const {
283  typename S::const_iterator b = S::begin(), p = S::end();
284  if (p > b) switch (*(p - 1)) {
285  case '$': if (--p == b) break; /* else continue */
286  case '^': while (p > b && *(p - 1) == '^') --p; if (p == b) break; /* else continue */
287  case ':': if (p - 1 > b && *(p - 1) == ':' && *b == ':' && std::find(b + 1, p, ':') == p - 1) p = b; break;
288  }
289  return (p == b) ? S(*this) + S(i) : (S(*this) + STR('.')) += S(i);
290 }
291 
292 /* --- Frame --- */
293 
294 TMPL Script<CFG>::Frame::Frame(Variables& vars, Root& root, Frame* previous) : vars(vars), root(root)
295  , previous(previous), closure(this), label(previous == 0 ? String(STR("::")) : root.generateLabel()) { }
296 
297 TMPL T_TYPE(Value) Script<CFG>::Frame::rvalue(const XValue& v, bool fallback) {
298  return !v.first ? v.second : get(v.second, fallback);
299 }
300 
301 TMPL const T_TYPE(Value)& Script<CFG>::Frame::lvalue(const XValue& v) {
302  if (!v.first) throw Xception(STR("Invalid lvalue"));
303  return v.second;
304 }
305 
306 TMPL T_TYPE(Frame*) Script<CFG>::Frame::resolveFrame(StringIt& p, const StringIt& e) const {
307  assert(p <= e);
308  Frame* f = const_cast<Frame*>(this);
309  if (p < e && *p == ':') {
310  StringIt n = std::find(p + 1, e, ':');
311  if (n >= e) throw Xception(String(STR("Invalid identifier: ")) += escape(String(p, e)));
312  if (n - p > 1) {
313  String s(p, n + 1);
314  while (f->label != s)
315  if ((f = f->previous) == 0) throw Xception(String(STR("Frame does not exist: ")) += escape(s));
316  } else f = &root;
317  p = n + 1;
318  }
319  for (; p < e && *p == '^'; ++p)
320  if ((f = f->previous) == 0) throw Xception(STR("Frame does not exist"));
321  if (p >= e || *p != '$') f = f->closure;
322  return f;
323 }
324 
325 TMPL std::pair< T_TYPE(Frame*), T_TYPE(String) > Script<CFG>::Frame::resolveFrame(const String& identifier) const {
326  switch (identifier[0]) {
327  default: return std::pair<Frame*, String>(closure, identifier);
328  case '$': return std::pair<Frame*, String>(const_cast<Frame*>(this), identifier);
329  case ':': case '^': {
330  StringIt b = const_cast<const String&>(identifier).begin(), e = const_cast<const String&>(identifier).end();
331  Frame* frame = resolveFrame(b, e);
332  return std::pair<Frame*, String>(frame, String(b, e));
333  }
334  }
335 }
336 
337 TMPL T_TYPE(Value) Script<CFG>::Frame::get(const String& identifier, bool fallback) const {
338  Value temp;
339  std::pair<Frame*, String> fs = resolveFrame(identifier);
340  if (fs.first->vars.lookup(fs.second, temp)) return temp;
341  else if (fallback && isSymbolChar(identifier[0]) && root.vars.lookup(fs.second, temp)) return temp;
342  else throw Xception(String(STR("Undefined: ")) += escape(identifier));
343 }
344 
345 TMPL T_TYPE(Value) Script<CFG>::Frame::getOptional(const String& identifier, const Value& defaultValue) const {
346  std::pair<Frame*, String> fs = resolveFrame(identifier);
347  Value temp;
348  return fs.first->vars.lookup(fs.second, temp) ? temp : defaultValue;
349 }
350 
351 TMPL const T_TYPE(Value)& Script<CFG>::Frame::set(const String& identifier, const Value& v) {
352  std::pair<Frame*, String> fs = resolveFrame(identifier);
353  if (!fs.first->vars.assign(fs.second, v)) throw Xception(String(STR("Cannot modify: ")) += escape(identifier));
354  return v;
355 }
356 
357 TMPL T_TYPE(Value) Script<CFG>::Frame::reference(const String& identifier) const {
358  std::pair<Frame*, String> fs = resolveFrame(identifier);
359  return fs.first->label + fs.second;
360 }
361 
362 TMPL T_TYPE(Value) Script<CFG>::Frame::execute(const String& body) {
363  StringIt b = body.begin(), e = body.end();
364  switch (b < e ? *b : 0) {
365  case '{': return evaluate(body); // <-- function
366  case '>': closure = resolveFrame(++b, e); // <-- lambda
367  return evaluate(String(b, e));
368  case '<': if (--e - ++b > 0) { // <-- native
369  Frame* nativeFrame = (*b == ':' ? resolveFrame(b, e) : &root);
370  Native* native = nativeFrame->vars.lookupNative(String(b, e));
371  if (native != 0) return native->pikaCall(*this);
372  }
373  throw Xception(String(STR("Unknown native function: ")) += escape(body));
374  default: throw Xception(String(STR("Illegal call on: ")) + escape(body));
375  }
376 }
377 
378 TMPL void Script<CFG>::Frame::white(StringIt& p, const StringIt& e) {
379  assert(p <= e);
380  while (p < e) {
381  switch (*p) {
382  case ' ': case '\t': case '\r': case '\n': ++p; break;
383  case '/': if (p + 1 < e && p[1] == '/') {
384  static const Char END_CHARS[] = { '\r', '\n' };
385  p = find_first_of(p += 2, e, END_CHARS, END_CHARS + 2);
386  break;
387  } else if (p + 1 < e && p[1] == '*') {
388  static const Char END_CHARS[] = { '*', '/' };
389  p = search(p += 2, e, END_CHARS, END_CHARS + 2);
390  if (p >= e) throw Xception(STR("Missing '*/'"));
391  p += 2;
392  break;
393  } /* else continue */
394  default: return;
395  }
396  }
397 }
398 
399 TMPL bool Script<CFG>::Frame::token(StringIt& p, const StringIt& e, const Char* token) {
400  assert(p <= e);
401  StringIt t = p + 1;
402  while (*token != 0 && t < e && *t == *token) { ++t; ++token; }
403  if (*token == 0 && (t >= e || !isSymbolChar(*t))) {
404  if ((p = t) < e && maybeWhite(*p)) white(p, e);
405  return true;
406  } else return false;
407 }
408 
409 TMPL template<class F> bool Script<CFG>::Frame::binaryOp(StringIt& p, const StringIt& e, XValue& v, bool dry
410  , Precedence thres, int hop, Precedence prec, F op) {
411  assert(p <= e);
412  assert(hop >= 0);
413  if (thres >= prec) return false;
414  XValue r;
415  expr(p += hop, e, r, false, dry, prec);
416  if (!dry) v = XValue(false, op(rvalue(v), rvalue(r)));
417  return true;
418 }
419 
420 TMPL template<class F> bool Script<CFG>::Frame::assignableOp(StringIt& p, const StringIt& e, XValue& v, bool dry
421  , Precedence thres, int hop, Precedence prec, F op) {
422  assert(p <= e);
423  assert(hop >= 0);
424  if (p + hop >= e || p[hop] != '=') return binaryOp(p, e, v, dry, thres, hop, prec, op);
425  if (thres > ASSIGN) return false;
426  XValue r; // <-- operate and assign
427  expr(p += hop + 1, e, r, false, dry, ASSIGN);
428  if (!dry) v = XValue(false, set(lvalue(v), op(rvalue(v, false), rvalue(r))));
429  return true;
430 }
431 
432 TMPL template<class F> bool Script<CFG>::Frame::addSubOp(StringIt& p, const StringIt& e, XValue& v, bool dry
433  , Precedence thres, const F& f) {
434  assert(p <= e);
435  if (p + 1 >= e || p[1] != *p) return assignableOp(p, e, v, dry, thres, 1, ADD_SUB, f);
436  else if (thres >= POSTFIX) return false;
437  else if (!dry) {
438  Value r = rvalue(v, false); // <-- post inc/dec
439  set(lvalue(v), f(long(r), 1));
440  v = XValue(false, r);
441  }
442  p += 2;
443  return true;
444 }
445 
446 TMPL template<class E, class I, class S> bool Script<CFG>::Frame::lgtOp(StringIt& p, const StringIt& e, XValue& v
447  , bool dry, Precedence thres, const E& excl, const I& incl, S shift) {
448  assert(p <= e);
449  assert(shift >= 0);
450  if (p + 1 < e && p[1] == *p) return assignableOp(p, e, v, dry, thres, 2, SHIFT, shift); // <-- shift
451  else if (p + 1 < e && p[1] == '=') return binaryOp(p, e, v, dry, thres, 2, COMPARE, incl); // <-- less/greater or equal
452  else return binaryOp(p, e, v, dry, thres, 1, COMPARE, excl); // <-- less/greater
453 }
454 
455 TMPL bool Script<CFG>::Frame::pre(StringIt& p, const StringIt& e, XValue& v, bool dry) {
456  assert(p <= e);
457  StringIt b = p;
458  switch (p < e ? *p : 0) {
459  case 0: return false;
460  case '!': expr(++p, e, v, false, dry, PREFIX); if (!dry) v = XValue(false, !rvalue(v)); return true; // <-- logical not
461  case '~': expr(++p, e, v, false, dry, PREFIX); if (!dry) v = XValue(false, ~ulong(rvalue(v))); return true; // <-- bitwise not
462  case '(': termExpr(++p, e, v, false, dry, BRACKETS, ')'); return true; // <-- parenthesis
463  case ':': if (p + 1 < e && p[1] == ':') p += 2; break; // <-- root
464  case '^': while (++p < e && *p == '^'); break; // <-- frame peek
465  case '@': expr(++p, e, v, false, dry, PREFIX); if (!dry) v = XValue(false, reference(lvalue(v))); return true;// <-- reference
466  case '[': termExpr(++p, e, v, false, dry, BRACKETS, ']'); if (!dry) v = XValue(true, rvalue(v)); return true; // <-- indirection
467  case '<': p = std::find(p, e, '>'); if (p < e) ++p; if (!dry) v = XValue(false, String(b, p)); return true; // <-- native literal
468  case '\'': case '"': { Value s(unescape<String>(p, e)); if (!dry) v = XValue(false, s); } return true; // <-- string literal
469  case 'e': if (token(p, e, STR("lse"))) throw Xception(STR("Unexpected 'else' (preceded by ';'?)")); break; // <-- error on unexpected else
470  case 't': if (token(p, e, STR("rue"))) { if (!dry) v = XValue(false, true); return true; } break; // <-- true literal
471  case 'v': if (token(p, e, STR("oid"))) { if (!dry) v = XValue(false, Value()); return true; } break; // <-- void literal
472 
473  case '>': if (++p < e && maybeWhite(*p)) white(p, e); // <-- lambda
474  b = p;
475  expr(p, e, v, false, true, STATEMENT);
476  if (!dry) v = XValue(false, (String(STR(">")) += closure->label) += String(b, p));
477  return true;
478 
479  case '{': do { expr(++p, e, v, true, dry, STATEMENT); } while (p < e && *p == ';'); // <-- compound
480  if (p >= e) throw Xception(STR("Missing '}'"));
481  if (*p != '}') throw Xception(STR("Syntax error (missing ';')?"));
482  ++p;
483  return true;
484 
485  case '+': case '-':
486  if (token(p, e, STR("infinity"))) p = b + 1; /* and continue to stringToDouble */ // <-- infinity literal
487  else if (++p >= e) return false;
488  else if (*p == *b) {
489  expr(++p, e, v, false, dry, PREFIX); // <-- pre inc/dec
490  if (!dry) v = XValue(false, set(lvalue(v), long(rvalue(v, false)) + (*b == '-' ? -1 : 1)));
491  return true;
492  } else if (*p < '0' || *p > '9') {
493  expr(p, e, v, false, dry, PREFIX); // <-- positive / negative
494  if (!dry) v = XValue(false, *b == '-' ? -double(rvalue(v)) : double(rvalue(v)));
495  return true;
496  } /* else continue */
497 
498  case '0': if (p + 1 < e && p[1] == 'x') {
499  ulong l = hexToLong<String>(p += 2, e); // <-- hexadecimal literal
500  if (p == b + 2) throw Xception(STR("Invalid hexadecimal number"));
501  if (!dry) v = XValue(false, *b == '-' ? -long(l) : l);
502  return true;
503  } /* else continue */
504 
505  case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { // <-- numeric literal
506  double d = stringToDouble<String>(p, e);
507  if (!dry) v = XValue(false, *b == '-' ? -d : d);
508  }
509  return true;
510 
511  case 'f': if (token(p, e, STR("alse"))) { if (!dry) v = XValue(false, false); return true; } // <-- false literal
512  else if (token(p, e, STR("or"))) {
513  if (p >= e || *p != '(') throw Xception(STR("Expected '('")); // <-- for
514  XValue xv;
515  termExpr(++p, e, xv, true, dry, ARGUMENT, ';');
516  StringIt cp = p;
517  termExpr(p, e, xv, true, dry, ARGUMENT, ';');
518  StringIt ip = p;
519  termExpr(p, e, xv, true, true, ARGUMENT, ')');
520  StringIt bp = p;
521  bool cb = !dry && rvalue(xv);
522  do {
523  expr(p = bp, e, v, true, !cb, BODY);
524  if (cb) {
525  if (root.doTrace(TRACE_LOOP)) tick(p, v, TRACE_LOOP, true);
526  StringIt ep = p;
527  expr(p = ip, e, xv, true, false, ARGUMENT);
528  expr(p = cp, e, xv, true, false, ARGUMENT);
529  p = ep;
530  cb = rvalue(xv);
531  }
532  } while (cb);
533  if (!dry && root.doTrace(TRACE_LOOP)) tick(p, v, TRACE_LOOP, false);
534  return true;
535  } else if (token(p, e, STR("unction"))) {
536  if (p >= e || *p != '{') throw Xception(STR("Expected '{'"));
537  b = p;
538  expr(p, e, v, false, true, DEFINITION); // <-- function
539  if (!dry) v = XValue(false, String(b, p));
540  return true;
541  }
542  break;
543 
544  case 'i': if (p + 1 < e && token(p, e, STR("f"))) {
545  if (p >= e || *p != '(') throw Xception(STR("Expected '('")); // <-- if
546  XValue c;
547  termExpr(++p, e, c, false, dry, ARGUMENT, ')');
548  bool b = dry || rvalue(c);
549  expr(p, e, v, false, dry || !b, BODY);
550  if (p < e && *p == 'e' && token(p, e, STR("lse"))) expr(p, e, v, false, dry || b, BODY); // <-- else
551  return true;
552  }
553  break;
554  }
555  while (p < e && isSymbolChar(*p)) ++p;
556  if (b != p && !dry) v = XValue(true, String(b, p));
557  return (b != p);
558 }
559 
560 TMPL bool Script<CFG>::Frame::post(StringIt& p, const StringIt& e, XValue& v, bool dry, Precedence thres) {
561  assert(p <= e);
562  switch (p < e ? *p : 0) {
563  case 0: return false;
564  case ' ': case '\t': case '\r': case '\n':
565  if (thres < DEFINITION) { Char c = *p; while (++p < e && *p == c); return true; } break; // <-- white spaces
566  case '/': if (thres < DEFINITION && p + 1 < e && (p[1] == '/' || p[1] == '*')) { white(p, e); return true; } // <-- comment
567  return assignableOp(p, e, v, dry, thres, 1, MUL_DIV, std::divides<double>()); // <-- divide
568  case '+': return addSubOp(p, e, v, dry, thres, std::plus<double>()); // <-- add
569  case '-': return addSubOp(p, e, v, dry, thres, std::minus<double>()); // <-- subtract
570  case '#': return assignableOp(p, e, v, dry, thres, 1, CONCAT, std::plus<String>()); // <-- concat
571  case '*': return assignableOp(p, e, v, dry, thres, 1, MUL_DIV, std::multiplies<double>()); // <-- multipy
572  case '\\': return assignableOp(p, e, v, dry, thres, 1, MUL_DIV, std::divides<long>()); // <-- integer division
573  case '%': return assignableOp(p, e, v, dry, thres, 1, MUL_DIV, std::ptr_fun<double, double>(fmod)); // <-- modulus
574  case '^': return assignableOp(p, e, v, dry, thres, 1, BIT_XOR, bitXor); // <-- xor
575  case '<': return lgtOp(p, e, v, dry, thres, std::less<Value>(), std::less_equal<Value>(), shiftLeft); // <-- shift left
576  case '>': return lgtOp(p, e, v, dry, thres, std::greater<Value>(), std::greater_equal<Value>(), shiftRight); // <-- shift right
577 
578  case '!': if (e - p > 2 && p[2] == '=' && p[1] == '=')
579  return binaryOp(p, e, v, dry, thres, 3, EQUALITY, std::not_equal_to<String>()); // <-- literal not equals
580  else if (p + 1 < e && p[1] == '=')
581  return binaryOp(p, e, v, dry, thres, 2, EQUALITY, std::not_equal_to<Value>()); // <-- not equals
582  break;
583 
584  case '=': if (e - p > 2 && p[2] == '=' && p[1] == '=')
585  return binaryOp(p, e, v, dry, thres, 3, EQUALITY, std::equal_to<String>()); // <-- literal equals
586  else if (p + 1 < e && p[1] == '=')
587  return binaryOp(p, e, v, dry, thres, 2, EQUALITY, std::equal_to<Value>()); // <-- equals
588  else if (thres <= ASSIGN) {
589  XValue r; // <-- assign
590  expr(++p, e, r, false, dry, ASSIGN);
591  if (!dry) v = XValue(false, set(lvalue(v), rvalue(r)));
592  return true;
593  }
594  break;
595 
596  case '&': if (p + 1 < e && p[1] != '&') return assignableOp(p, e, v, dry, thres, 1, BIT_AND, bitAnd); // <-- bitwise and
597  else if (thres < LOGICAL_AND) {
598  bool l = !dry && rvalue(v); // <-- logical and
599  expr(p += 2, e, v, false, !l, LOGICAL_AND);
600  if (!dry) v = XValue(false, l && rvalue(v));
601  return true;
602  }
603  break;
604 
605  case '|': if (p + 1 < e && p[1] != '|') return assignableOp(p, e, v, dry, thres, 1, BIT_OR, bitOr); // <-- bitwise or
606  else if (thres < LOGICAL_OR) {
607  bool l = dry || rvalue(v); // <-- logical or
608  expr(p += 2, e, v, false, l, LOGICAL_OR);
609  if (!dry) v = XValue(false, l || rvalue(v));
610  return true;
611  }
612  break;
613 
614  case '.': { // <-- member
615  if (++p < e && maybeWhite(*p)) white(p, e);
616  StringIt b = p;
617  while (p < e && isSymbolChar(*p)) ++p;
618  if (!dry) v = XValue(true, lvalue(v)[String(b, p)]);
619  return true;
620  }
621 
622  case '[': if (thres < POSTFIX) { // <-- subscript
623  XValue element;
624  termExpr(++p, e, element, false, dry, BRACKETS, ']');
625  if (!dry) v = XValue(true, lvalue(v)[rvalue(element)]);
626  return true;
627  }
628  break;
629 
630  case '{': if (thres < POSTFIX) { // <-- substring
631  XValue index;
632  bool gotIndex = expr(++p, e, index, true, dry, BRACKETS);
633  if (p >= e || (*p != ':' && *p != '}')) throw Xception(STR("Expected '}' or ':'"));
634  if (*p++ == ':') {
635  XValue count;
636  bool gotCount = termExpr(p, e, count, true, dry, BRACKETS, '}');
637  if (!dry) {
638  String s = rvalue(v);
639  long i = !gotIndex ? 0L : long(rvalue(index));
640  long n = gotCount ? long(rvalue(count)) + mini(i, 0L) : String::npos;
641  v = XValue(false, i <= long(s.size()) && (!gotCount || n >= 0L)
642  ? Value(s.substr(maxi(i, 0L), n)) : Value());
643  }
644  } else if (gotIndex && !dry) {
645  String s = rvalue(v);
646  long i = long(rvalue(index));
647  v = XValue(false, i >= 0L && i <= long(s.size()) ? Value(s.substr(i, 1)) : Value());
648  } else if (!dry) throw Xception(STR("Syntax error"));
649  return true;
650  }
651  break;
652 
653  case '(': if (thres < POSTFIX) { // <-- call
654  typename CFG::Locals locals;
655  Frame calleeFrame(locals, root, this);
656  long n = 0;
657  do {
658  if (++p < e && maybeWhite(*p)) white(p, e);
659  if (p < e && *p == ')' && n == 0) break;
660  XValue arg;
661  if (expr(p, e, arg, true, dry, ARGUMENT) && !dry)
662  locals.assign(String(STR("$")) += intToString<String>(n), rvalue(arg));
663  ++n;
664  } while (p < e && *p == ',');
665  if (p >= e || *p != ')') throw Xception(STR("Expected ',' or ')'"));
666  ++p;
667  if (!dry) {
668  locals.assign(STR("$n"), n);
669  if (v.first) locals.assign(STR("$callee"), v.second);
670  v = XValue(false, calleeFrame.execute(rvalue(v)));
671  }
672  return true;
673  }
674  break;
675  }
676  return false;
677 }
678 
679 TMPL void Script<CFG>::Frame::tick(const StringIt& p, const XValue& v, Precedence thres, bool exit) {
680  root.trace(*this, *source, p - source->begin(), v.first, v.second, thres, exit);
681 }
682 
683 TMPL bool Script<CFG>::Frame::expr(StringIt& p, const StringIt& e, XValue& v, bool emptyOk, bool dry, Precedence thres) {
684  assert(p <= e);
685  if (p < e && maybeWhite(*p)) white(p, e);
686  if (!dry && root.doTrace(thres)) tick(p, v, thres, false);
687  if (pre(p, e, v, dry)) {
688  while (post(p, e, v, dry, thres));
689  if (!dry && root.doTrace(thres)) tick(p, v, thres, true);
690  return true;
691  } else if (!emptyOk) throw Xception(STR("Syntax error"));
692  return false;
693 }
694 
695 TMPL bool Script<CFG>::Frame::termExpr(StringIt& p, const StringIt& e, XValue& v, bool emptyOk, bool dry
696  , Precedence thres, Char term) {
697  assert(p <= e);
698  bool nonEmpty = expr(p, e, v, emptyOk, dry, thres);
699  if (p >= e || *p != term) throw Xception((String(STR("Missing '")) += String(&term, 1)) += '\'');
700  ++p;
701  return nonEmpty;
702 }
703 
704 TMPL T_TYPE(Value) Script<CFG>::Frame::evaluate(const String source) {
705  XValue v;
706  const String* oldSource = this->source;
707  this->source = &source;
708  try {
709  StringIt p = source.begin(), e = source.end();
710  if (root.doTrace(TRACE_CALL)) tick(p, v, TRACE_CALL, false);
711  try {
712  try {
713  while (p < e) {
714  expr(p, e, v, true, false, STATEMENT);
715  if (p < e) {
716  if (*p != ';') throw Xception(STR("Syntax error"));
717  ++p;
718  }
719  }
720  v = XValue(false, rvalue(v));
721  } catch (const Xception& x) {
722  if (root.doTrace(TRACE_ERROR)) tick(p, XValue(false, x.getError()), TRACE_ERROR, previous == 0);
723  throw;
724  } catch (const std::exception& x) {
725  if (root.doTrace(TRACE_ERROR)) {
726  const char* s = x.what();
727  String err = String(std::basic_string<Char>(s, s + strlen(s)));
728  tick(p, XValue(false, err), TRACE_ERROR, previous == 0);
729  }
730  throw;
731  } catch (...) {
732  if (root.doTrace(TRACE_ERROR))
733  tick(p, XValue(false, STR("Unknown exception")), TRACE_ERROR, previous == 0);
734  throw;
735  }
736  } catch (...) {
737  if (root.doTrace(TRACE_CALL)) tick(p, v, TRACE_CALL, true);
738  throw;
739  }
740  if (root.doTrace(TRACE_CALL)) tick(p, v, TRACE_CALL, true);
741  } catch (...) {
742  this->source = oldSource;
743  throw;
744  }
745  this->source = oldSource;
746  return v.second;
747 }
748 
749 TMPL T_TYPE(StringIt) Script<CFG>::Frame::parse(const StringIt& begin, const StringIt& end, bool literal) {
750  assert(begin <= end);
751  StringIt p = begin, e = end;
752  XValue dummy;
753  if (!literal) expr(p, end, dummy, true, true, STATEMENT);
754  else switch (p < e ? *p : 0) {
755  case 'f': if (!token(p, e, STR("alse")) && token(p, e, STR("unction"))) pre(p = begin, e, dummy, true); break;
756  case 't': token(p, e, STR("rue")); break;
757  case 'v': token(p, e, STR("oid")); break;
758  case '+': case '-': if (token(p, e, STR("infinity")) || p + 1 >= e || p[1] < '0' || p[1] > '9') break;
759  case '<': case '>': case '0': case '\'': case '"': case '1': case '2': case '3': case '4': case '5': case '6':
760  case '7': case '8': case '9': pre(p, e, dummy, true); break;
761  }
762  return p;
763 }
764 
765 TMPL T_TYPE(Value) Script<CFG>::Frame::call(const String& callee, const Value& body, long argc, const Value* argv) {
766  assert(argc >= 0);
767  typename CFG::Locals locals;
768  Frame calleeFrame(locals, root, this);
769  locals.assign(STR("$n"), argc);
770  for (long i = 0; i < argc; ++i) locals.assign(String(STR("$")) += intToString<String>(i), argv[i]);
771  if (!callee.empty()) locals.assign(STR("$callee"), callee);
772  return calleeFrame.execute(body.empty() ? get(callee, true) : body);
773 }
774 
775 TMPL void Script<CFG>::Frame::registerNative(const String& identifier, Native* native) {
776  std::pair<Frame*, String> p = resolveFrame(identifier);
777  if (!p.first->vars.assignNative(p.second, native))
778  throw Xception(String(STR("Cannot register native: ")) += escape(identifier));
779  if (native != 0)
780  p.first->set(p.second, (String(STR("<")) += (p.first == &root ? p.second : p.first->label + p.second)) += '>');
781 }
782 
783 /* --- Root --- */
784 
785 TMPL Script<CFG>::Root::Root(Variables& vars) : Frame(vars, *this, 0), traceLevel(NO_TRACE), isInsideTracer(false)
786  , autoLabelStart(autoLabel + 29) {
787  std::fill_n(autoLabel, 32, ':');
788 }
789 
790 TMPL T_TYPE(String) Script<CFG>::Root::generateLabel() {
791  Char* b = autoLabelStart, * p = autoLabel + 30;
792  while (*--p == 'z');
793  switch (*p) {
794  case ':': *p = '1'; *--b = ':'; autoLabelStart = b; break;
795  case '9': *p = 'A'; break;
796  case 'Z': *p = 'a'; break;
797  default: (*p)++; break;
798  }
799  for (++p; *p != ':'; ++p) *p = '0';
800  return String(const_cast<const Char*>(b), autoLabel + 31 - b);
801 }
802 
803 TMPL void Script<CFG>::Root::setTracer(Precedence traceLevel, const Value& tracerFunction) throw() {
804  this->traceLevel = traceLevel;
805  this->tracerFunction = tracerFunction;
806 }
807 
808 TMPL void Script<CFG>::Root::trace(Frame& frame, const String& source, SizeType offset, bool lvalue, const Value& value
809  , Precedence level, bool exit) {
810  if (!tracerFunction.isVoid() && !isInsideTracer) {
811  try {
812  isInsideTracer = true;
813  Value argv[6] = { source, static_cast<ulong>(offset), lvalue, value, int(level), exit };
814  frame.call(String(), tracerFunction, 6, argv);
815  isInsideTracer = false;
816  } catch (...) {
817  isInsideTracer = false;
818  setTracer(NO_TRACE, Value()); // Turn off tracing on uncaught exceptions.
819  throw;
820  }
821  }
822 }
823 
824 /* --- STLVariables --- */
825 
826 TMPL bool Script<CFG>::STLVariables::lookup(const String& symbol, Value& result) {
827  typename VariableMap::const_iterator it = vars.find(symbol);
828  if (it == vars.end()) return false;
829  result = it->second;
830  return true;
831 }
832 
833 TMPL void Script<CFG>::STLVariables::list(const String& key, typename Variables::VarList& list) {
834  for (typename VariableMap::const_iterator it = vars.lower_bound(key)
835  ; it != vars.end() && it->first.substr(0, key.size()) == key; ++it) list.push_back(*it);
836 }
837 
838 TMPL T_TYPE(Native*) Script<CFG>::STLVariables::lookupNative(const String& identifier) {
839  typename NativeMap::iterator it = natives.find(identifier);
840  return (it == natives.end() ? 0 : it->second);
841 }
842 
843 TMPL bool Script<CFG>::STLVariables::assignNative(const String& identifier, Native* native) {
844  typename NativeMap::iterator it = natives.insert(typename NativeMap::value_type(identifier, (Native*)(0))).first;
845  if (it->second != native) delete it->second;
846  it->second = native;
847  return true;
848 }
849 
850 TMPL Script<CFG>::STLVariables::~STLVariables() {
851  for (typename NativeMap::iterator it = natives.begin(); it != natives.end(); ++it) delete it->second;
852 }
853 
854 TMPL std::pair<T_TYPE(Value), T_TYPE(String)> Script<CFG>::getThisAndMethod(Frame& f) {
855  const String fn = f.get(STR("$callee"));
856  StringIt it = std::find(fn.rbegin(), fn.rend(), '.').base();
857  if (it <= fn.begin()) throw Xception(STR("Non-method call"));
858  return std::pair<Value, String>(f.getPrevious().reference(String(fn.begin(), it - 1)), String(it, fn.end()));
859 }
860 
861 /* --- Standard Library --- */
862 
863 TMPL T_TYPE(Value) Script<CFG>::lib::elevate(Frame& f) { return f.execute(f.get(getThisAndMethod(f).first, true)); }
864 TMPL ulong Script<CFG>::lib::length(const String& s) { return static_cast<ulong>(s.size()); }
865 TMPL T_TYPE(String) Script<CFG>::lib::lower(String s) { transform(s.begin(), s.end(), s.begin(), ::tolower); return s; }
866 TMPL void Script<CFG>::lib::print(const String& s) { xcout<Char>() << std::basic_string<Char>(s) << std::endl; }
867 TMPL double Script<CFG>::lib::random(double m) { return m * rand() / double(RAND_MAX); }
868 TMPL T_TYPE(String) Script<CFG>::lib::reverse(String s) { std::reverse(s.begin(), s.end()); return s; }
869 TMPL void Script<CFG>::lib::thrower(const String& s) { throw Xception(s); }
870 TMPL T_TYPE(Value) Script<CFG>::lib::time(const Frame&) { return double(::time(0)); }
871 TMPL T_TYPE(String) Script<CFG>::lib::upper(String s) { transform(s.begin(), s.end(), s.begin(), ::toupper); return s; }
872 
873 TMPL T_TYPE(String) Script<CFG>::lib::character(double d) {
874  if (uintChar(Char(d)) != d) throw Xception(String(STR("Illegal character code: ")) += doubleToString<String>(d));
875  return String(1, Char(d));
876 }
877 
878 TMPL uint Script<CFG>::lib::ordinal(const String& s) {
879  if (s.size() != 1) throw Xception(String(STR("Value is not single character: ")) += escape(s));
880  return uintChar(s[0]);
881 }
882 
883 TMPL bool Script<CFG>::lib::deleter(const Frame& f) {
884  Value x = f.get(STR("$0"));
885  std::pair<Frame*, String> fs = f.getPrevious().resolveFrame(x);
886  return fs.first->getVariables().erase(fs.second);
887 }
888 
889 TMPL T_TYPE(Value) Script<CFG>::lib::evaluate(const Frame& f) {
890  return f.resolveFrame(f.getOptional(STR("$1"))).first->evaluate(f.get(STR("$0")));
891 }
892 
893 TMPL bool Script<CFG>::lib::exists(const Frame& f) {
894  Value x = f.get(STR("$0"));
895  Value result;
896  std::pair<Frame*, String> fs = f.getPrevious().resolveFrame(x);
897  return fs.first->getVariables().lookup(fs.second, result);
898 }
899 
900 TMPL ulong Script<CFG>::lib::find(const String& a, const String& b) {
901  return static_cast<ulong>(find_first_of(a.begin(), a.end(), b.begin(), b.end()) - a.begin());
902 }
903 
904 TMPL void Script<CFG>::lib::foreach(Frame& f) {
905  Value arg1 = f.get(STR("$1"));
906  std::pair<Frame*, String> fs = f.getPrevious().resolveFrame(f.get(STR("$0"))[Value()]);
907  typename Variables::VarList list;
908  fs.first->getVariables().list(fs.second, list);
909  for (typename Variables::VarList::const_iterator it = list.begin(); it != list.end(); ++it) {
910  Value argv[3] = { fs.first->reference(it->first), it->first.substr(fs.second.size()), it->second };
911  f.call(String(), arg1, 3, argv);
912  }
913 }
914 
915 TMPL T_TYPE(String) Script<CFG>::lib::input(const String& prompt) {
916  xcout<Char>() << std::basic_string<Char>(prompt);
917  std::basic_string<Char> s;
918  std::basic_istream<Char>& instream = xcin<Char>();
919  if (instream.eof()) throw Xception(STR("Unexpected end of input file"));
920  if (!instream.good()) throw Xception(STR("Input file error"));
921  getline(instream, s);
922  return s;
923 }
924 
925 TMPL T_TYPE(Value) Script<CFG>::lib::invoke(Frame& f) {
926  Value source = f.get(STR("$2")), arg4 = f.getOptional(STR("$4"));
927  long offset = long(f.getOptional(STR("$3"), 0));
928  long n = arg4.isVoid() ? long(f.get(source[String(STR("n"))])) - offset : long(arg4);
929  if (n < 0) throw Xception(STR("Too few array elements"));
930  std::vector<Value> a(n);
931  for (long i = 0; i < long(a.size()); ++i) a[i] = f.get(source[i + offset]);
932  return f.call(f.getOptional(STR("$0")), f.getOptional(STR("$1")), long(a.size()), a.empty() ? 0 : &a[0]);
933 }
934 
935 TMPL T_TYPE(String) Script<CFG>::lib::load(const String& file) {
936  std::basic_ifstream<Char> instream(toStdString(file).c_str()); // Sorry, can't pass a wchar_t filename. MSVC supports it, but it is non-standard. So we convert to a std::string to be on the safe side.
937  if (!instream.good()) throw Xception(String(STR("Cannot open file for reading: ")) += escape(file));
938  String chars;
939  while (!instream.eof()) {
940  if (!instream.good()) throw Xception(String(STR("Error reading from file: ")) += escape(file));
941  Char buffer[4096];
942  instream.read(buffer, 4096);
943  chars += String(buffer, static_cast<typename String::size_type>(instream.gcount()));
944  }
945  return chars;
946 }
947 
948 TMPL ulong Script<CFG>::lib::mismatch(const String& a, const String& b) {
949  if (a.size() > b.size()) return static_cast<ulong>(std::mismatch(b.begin(), b.end(), a.begin()).first - b.begin());
950  else return static_cast<ulong>(std::mismatch(a.begin(), a.end(), b.begin()).first - a.begin());
951 }
952 
953 TMPL ulong Script<CFG>::lib::parse(Frame& f) {
954  const String source = f.get(STR("$0"));
955  return static_cast<ulong>(f.parse(source.begin(), source.end(), f.get(STR("$1"))) - source.begin());
956 }
957 
958 TMPL T_TYPE(String) Script<CFG>::lib::radix(const Frame& f) {
959  int radix = f.get(STR("$1"));
960  if (radix < 2 || radix > 16) throw Xception(String(STR("Radix out of range: ")) += intToString<String>(radix));
961  int minLength = f.getOptional(STR("$2"), 1);
962  if (minLength < 0 || minLength > int(sizeof (int) * 8))
963  throw Xception(String(STR("Minimum length out of range: ")) += intToString<String>(minLength));
964  return intToString<String, ulong>(f.get(STR("$0")), f.get(STR("$1")), minLength);
965 }
966 
967 TMPL void Script<CFG>::lib::save(const String& file, const String& chars) {
968  std::basic_ofstream<Char> outstream(toStdString(file).c_str()); // Sorry, can't pass a wchar_t filename. MSVC supports it, but it is non-standard. So we convert to a std::string to be on the safe side.
969  if (!outstream.good()) throw Xception(String(STR("Cannot open file for writing: ")) += escape(file));
970  outstream.write(chars.data(), chars.size());
971  if (!outstream.good()) throw Xception(String(STR("Error writing to file: ")) += escape(file));
972 }
973 
974 TMPL ulong Script<CFG>::lib::search(const String& a, const String& b) {
975  return static_cast<ulong>(std::search(a.begin(), a.end(), b.begin(), b.end()) - a.begin());
976 }
977 
978 TMPL ulong Script<CFG>::lib::span(const String& a, const String& b) {
979  typename String::const_iterator it;
980  for (it = a.begin(); it != a.end() && std::find(b.begin(), b.end(), *it) != b.end(); ++it);
981  return static_cast<ulong>(it - a.begin());
982 }
983 
984 TMPL T_TYPE(String) Script<CFG>::lib::precision(const Frame& f) {
985  return doubleToString<String>(f.get(STR("$0")), mini(maxi(int(f.get(STR("$1"))), 1), 16));
986 }
987 
988 TMPL int Script<CFG>::lib::system(const String& command) {
989  int xc = (command.empty() ? -1 : ::system(toStdString(command).c_str()));
990  if (xc < 0) throw Xception(String(STR("Error executing system command: ")) += escape(command));
991  return xc;
992 }
993 
994 TMPL void Script<CFG>::lib::trace(const Frame& f) {
995  f.getRoot().setTracer(Precedence(int(f.getOptional(STR("$1"), int(TRACE_CALL)))), f.getOptional(STR("$0")));
996 }
997 
998 TMPL T_TYPE(Value) Script<CFG>::lib::tryer(Frame& f) {
999  try { f.call(String(), f.get(STR("$0")), 0); } catch (const Xception& x) { return x.getError(); }
1000  return Value();
1001 }
1002 
1003 TMPL void Script<CFG>::addLibraryNatives(Frame& f, bool includeIO) {
1004  f.set(STR("VERSION"), PIKA_SCRIPT_VERSION);
1005  f.set(STR("run"), STR(">::evaluate((>{ $s = load($0); if ($s{:2} == '#!') $s{find($s, \"\\n\"):} })($0), @$)")); // Note: we need this as a "bootstrap" to include 'stdlib.pika'.
1006  f.registerNative(STR("abs"), (double (*)(double))(fabs));
1007  f.registerNative(STR("acos"), (double (*)(double))(acos));
1008  f.registerNative(STR("asin"), (double (*)(double))(asin));
1009  f.registerNative(STR("atan"), (double (*)(double))(atan));
1010  f.registerNative(STR("atan2"), (double (*)(double, double))(atan2));
1011  f.registerNative(STR("ceil"), (double (*)(double))(ceil));
1012  f.registerNative(STR("char"), lib::character);
1013  f.registerNative(STR("cos"), (double (*)(double))(cos));
1014  f.registerNative(STR("cosh"), (double (*)(double))(cosh));
1015  f.registerNative(STR("delete"), lib::deleter);
1016  f.registerNative(STR("escape"), (String (*)(const String&))(escape));
1017  f.registerNative(STR("exists"), lib::exists);
1018  f.registerNative(STR("elevate"), lib::elevate);
1019  f.registerNative(STR("evaluate"), lib::evaluate);
1020  f.registerNative(STR("exp"), (double (*)(double))(exp));
1021  f.registerNative(STR("find"), lib::find);
1022  f.registerNative(STR("floor"), (double (*)(double))(floor));
1023  f.registerNative(STR("foreach"), lib::foreach);
1024  f.set(STR("include"), STR(">::if (!exists(@::included[$0])) { invoke('run',, @$); ::included[$0] = true }")); // Note: we need this as a "bootstrap" to include 'stdlib.pika'.
1025  if (includeIO) f.registerNative(STR("input"), lib::input);
1026  f.registerNative(STR("invoke"), lib::invoke);
1027  f.registerNative(STR("length"), lib::length);
1028  f.registerNative(STR("log"), (double (*)(double))(log));
1029  f.registerNative(STR("log10"), (double (*)(double))(log10));
1030  if (includeIO) f.registerNative(STR("load"), lib::load);
1031  f.registerNative(STR("lower"), lib::lower);
1032  f.registerNative(STR("mismatch"), lib::mismatch);
1033  f.registerNative(STR("ordinal"), lib::ordinal);
1034  f.registerNative(STR("pow"), (double (*)(double, double))(pow));
1035  f.registerNative(STR("parse"), lib::parse);
1036  f.registerNative(STR("precision"), lib::precision);
1037  if (includeIO) f.registerNative(STR("print"), lib::print);
1038  f.registerNative(STR("radix"), lib::radix);
1039  f.registerNative(STR("random"), lib::random);
1040  f.registerNative(STR("reverse"), lib::reverse);
1041  f.registerNative(STR("sin"), (double (*)(double))(sin));
1042  f.registerNative(STR("sinh"), (double (*)(double))(sinh));
1043  if (includeIO) f.registerNative(STR("save"), lib::save);
1044  f.registerNative(STR("search"), lib::search);
1045  f.registerNative(STR("span"), lib::span);
1046  f.registerNative(STR("sqrt"), (double (*)(double))(sqrt));
1047  if (includeIO) f.registerNative(STR("system"), lib::system);
1048  f.registerNative(STR("tan"), (double (*)(double))(tan));
1049  f.registerNative(STR("tanh"), (double (*)(double))(tanh));
1050  f.registerNative(STR("time"), lib::time);
1051  f.registerNative(STR("throw"), lib::thrower);
1052  f.registerNative(STR("trace"), lib::trace);
1053  f.registerNative(STR("try"), lib::tryer);
1054  f.registerNative(STR("upper"), lib::upper);
1055 }
1056 
1059 
1060 #undef TMPL
1061 #undef T_TYPE
1062 #undef STR
1063 
1064 } // namespace Pika
1065 
1066 #endif
x=y x*=y x/=y x\=y x%=y x+=y x-=y x<<=y x>>=y x#=y x&=y x^=y x|=y
Definition: PikaScript.h:238
x===y x==y x!==y x!=y
Definition: PikaScript.h:244
static void addLibraryNatives(Frame &frame, bool includeIO=true)
Registers the standard library native functions to frame. If includeIO is false, 'load', 'save', 'input', 'print' and 'system' will not be registered. Please, refer to the PikaScript standard library reference guide for more info on individual native functions.
String::value_type Char
The character type for all strings (defined by the string class). E.g. char.
Definition: PikaScript.h:270
The PikaScript exception class.
Definition: PikaScript.h:135
virtual ~Variables()
Destructor.
S unescape(typename S::const_iterator &p, const typename S::const_iterator &e)
Converts a string that is either enclosed in single (' ') or double (" ") quotes. ...
String::const_iterator StringIt
The const_iterator of the string is used so frequently it deserves its own typedef.
Definition: PikaScript.h:272
Value get(const String &identifier, bool fallback=false) const
Gets a variable value.
bool operator<(const STLValue &r) const
Less than comparison operator.
Config::Value Value
The class used for all values and variables (defined by the configuration meta-class). E.g. STLValue.
Definition: PikaScript.h:268
std::pair< Frame *, String > resolveFrame(const String &identifier) const
Resolves the frame for identifier and returns it together with identifier stripped of any prefixed "f...
xy x>=y
Definition: PikaScript.h:245
if () x, for () x
Definition: PikaScript.h:235
The Root is the first Frame you instantiate.
Definition: PikaScript.h:403
S escape(const S &s)
Depending on the contents of the source string s it is encoded either in single (' ') or double (" ")...
x*y x/y x xy
Definition: PikaScript.h:249
Precedence
Precedence levels are used both internally for the parser and externally for the tracing mechanism...
Definition: PikaScript.h:229
(x) [x]
Definition: PikaScript.h:237
Value reference(const String &identifier) const
Creates a reference to the variable identified by identifier by prefixing it with a "frame label"...
x+y x-y
Definition: PikaScript.h:248
used only for tracing with tick()
Definition: PikaScript.h:230
S intToString(T i, int radix=10, int minLength=1)
Converts the integer i to a string with a radix and minimum length of your choice.
void registerNative(const String &identifier, Native *native)
Registers the native function (or object) native with identifier in the appropriate variable space (d...
double stringToDouble(typename S::const_iterator &p, const typename S::const_iterator &e)
Converts a string in scientific e notation (e.g. -12.34e-3) to a double floating point value...
The execution context and interpreter for PikaScript.
Definition: PikaScript.h:311
ulong hexToLong(typename S::const_iterator &p, const typename S::const_iterator &e)
Converts a string in hexadecimal form to an ulong integer.
x() x.y x[y] x{y} x++ x–
Definition: PikaScript.h:251
Value::String String
The class used for strings (defined by the string class). E.g. std::string.
Definition: PikaScript.h:269
const STLValue operator[](const STLValue &i) const
The subscript operator returns the concatenation of the value with the dot (.) separator (if necessar...
function { }
Definition: PikaScript.h:252
String::size_type SizeType
The length type for all strings (defined by the string class). E.g. size_t.
Definition: PikaScript.h:271
Script is a meta-class that groups all the core classes of the PikaScript interpreter together (excep...
Definition: PikaScript.h:266
STLValue is the reference implementation of a PikaScript variable.
Definition: PikaScript.h:173
static std::pair< Value, String > getThisAndMethod(Frame &frame)
getThisAndMethod splits the $callee variable of frame into object ("this") and method.
Value call(const String &callee, const Value &body, long argc, const Value *argv=0)
Calls a Pika function (by setting up a new "sub-frame" and executing the function body)...
bool operator==(const STLValue &r) const
Equality operator.
used only for tracing with tick()
Definition: PikaScript.h:233
Exception< String > Xception
The exception type.
Definition: PikaScript.h:273
const Value & set(const String &identifier, const Value &v)
Sets a variable value.
used only for tracing with tick()
Definition: PikaScript.h:232
Native is the base class for the native functions and objects that can be accessed from PikaScript...
Definition: PikaScript.h:459
long stringToLong(typename S::const_iterator &p, const typename S::const_iterator &e)
Converts a string in decimal form to a signed long integer.
virtual void trace(Frame &frame, const String &source, SizeType offset, bool lvalue, const Value &value, Precedence level, bool exit)
Overload this member function if you want to customize the tracing mechanism in PikaScript.
virtual void setTracer(Precedence traceLevel, const Value &tracerFunction)
Called by the standard library function "trace" to assign a PikaScript tracer function and a trace le...
Frame & getPrevious() const
Returns a reference to the previous frame (i.e. the frame of the caller of this frame). Must not be called on the root frame (will assert).
Definition: PikaScript.h:321
S doubleToString(double d, int precision=14)
Converts the double d to a string (in scientific e notation, e.g. -12.34e-3).
static Value elevate(Frame &frame)
Used to "aggregate" different method calls into a single function call.
Frame(Variables &vars, Root &root, Frame *previous)
Constructs the Frame and associates it with the variable space vars.
used only for tracing with tick()
Definition: PikaScript.h:231
Variables is an abstract base class which implements the interface to the variable space that a Frame...
Definition: PikaScript.h:291
std::string toStdString(const S &s)
Converts the string s to a standard C++ string.
@x !x ~x +x -x ++x –x
Definition: PikaScript.h:250