Newer
Older
dub_jkp / source / dub / internal / sdlang / lexer.d
@Sönke Ludwig Sönke Ludwig on 9 Jun 2015 59 KB Add latest sdlang-d sources.
  1. // SDLang-D
  2. // Written in the D programming language.
  3.  
  4. module dub.internal.sdlang.lexer;
  5.  
  6. version (Have_sdlang_d) public import sdlang.lexer;
  7. else:
  8.  
  9. import std.algorithm;
  10. import std.array;
  11. import std.base64;
  12. import std.bigint;
  13. import std.conv;
  14. import std.datetime;
  15. import std.file;
  16. import std.stream : ByteOrderMarks, BOM;
  17. import std.traits;
  18. import std.typecons;
  19. import std.uni;
  20. import std.utf;
  21. import std.variant;
  22.  
  23. import dub.internal.sdlang.exception;
  24. import dub.internal.sdlang.symbol;
  25. import dub.internal.sdlang.token;
  26. import dub.internal.sdlang.util;
  27.  
  28. alias dub.internal.sdlang.util.startsWith startsWith;
  29.  
  30. Token[] lexFile(string filename)
  31. {
  32. auto source = cast(string)read(filename);
  33. return lexSource(source, filename);
  34. }
  35.  
  36. Token[] lexSource(string source, string filename=null)
  37. {
  38. auto lexer = scoped!Lexer(source, filename);
  39. // Can't use 'std.array.array(Range)' because 'lexer' is scoped
  40. // and therefore cannot have its reference copied.
  41. Appender!(Token[]) tokens;
  42. foreach(tok; lexer)
  43. tokens.put(tok);
  44.  
  45. return tokens.data;
  46. }
  47.  
  48. // Kind of a poor-man's yield, but fast.
  49. // Only to be used inside Lexer.popFront (and Lexer.this).
  50. private template accept(string symbolName)
  51. {
  52. static assert(symbolName != "Value", "Value symbols must also take a value.");
  53. enum accept = acceptImpl!(symbolName, "null");
  54. }
  55. private template accept(string symbolName, string value)
  56. {
  57. static assert(symbolName == "Value", "Only a Value symbol can take a value.");
  58. enum accept = acceptImpl!(symbolName, value);
  59. }
  60. private template accept(string symbolName, string value, string startLocation, string endLocation)
  61. {
  62. static assert(symbolName == "Value", "Only a Value symbol can take a value.");
  63. enum accept = ("
  64. {
  65. _front = makeToken!"~symbolName.stringof~";
  66. _front.value = "~value~";
  67. _front.location = "~(startLocation==""? "tokenStart" : startLocation)~";
  68. _front.data = source[
  69. "~(startLocation==""? "tokenStart.index" : startLocation)~"
  70. ..
  71. "~(endLocation==""? "location.index" : endLocation)~"
  72. ];
  73. return;
  74. }
  75. ").replace("\n", "");
  76. }
  77. private template acceptImpl(string symbolName, string value)
  78. {
  79. enum acceptImpl = ("
  80. {
  81. _front = makeToken!"~symbolName.stringof~";
  82. _front.value = "~value~";
  83. return;
  84. }
  85. ").replace("\n", "");
  86. }
  87.  
  88. class Lexer
  89. {
  90. string source;
  91. string filename;
  92. Location location; /// Location of current character in source
  93.  
  94. private dchar ch; // Current character
  95. private dchar nextCh; // Lookahead character
  96. private size_t nextPos; // Position of lookahead character (an index into source)
  97. private bool hasNextCh; // If false, then there's no more lookahead, just EOF
  98. private size_t posAfterLookahead; // Position after lookahead character (an index into source)
  99.  
  100. private Location tokenStart; // The starting location of the token being lexed
  101. // Length so far of the token being lexed, not including current char
  102. private size_t tokenLength; // Length in UTF-8 code units
  103. private size_t tokenLength32; // Length in UTF-32 code units
  104. // Slight kludge:
  105. // If a numeric fragment is found after a Date (separated by arbitrary
  106. // whitespace), it could be the "hours" part of a DateTime, or it could
  107. // be a separate numeric literal that simply follows a plain Date. If the
  108. // latter, then the Date must be emitted, but numeric fragment that was
  109. // found after it needs to be saved for the the lexer's next iteration.
  110. //
  111. // It's a slight kludge, and could instead be implemented as a slightly
  112. // kludgey parser hack, but it's the only situation where SDL's lexing
  113. // needs to lookahead more than one character, so this is good enough.
  114. private struct LookaheadTokenInfo
  115. {
  116. bool exists = false;
  117. string numericFragment = "";
  118. bool isNegative = false;
  119. Location tokenStart;
  120. }
  121. private LookaheadTokenInfo lookaheadTokenInfo;
  122. this(string source=null, string filename=null)
  123. {
  124. this.filename = filename;
  125. this.source = source;
  126. _front = Token(symbol!"Error", Location());
  127. lookaheadTokenInfo = LookaheadTokenInfo.init;
  128.  
  129. if( source.startsWith( ByteOrderMarks[BOM.UTF8] ) )
  130. {
  131. source = source[ ByteOrderMarks[BOM.UTF8].length .. $ ];
  132. this.source = source;
  133. }
  134. foreach(bom; ByteOrderMarks)
  135. if( source.startsWith(bom) )
  136. error(Location(filename,0,0,0), "SDL spec only supports UTF-8, not UTF-16 or UTF-32");
  137. if(source == "")
  138. mixin(accept!"EOF");
  139. // Prime everything
  140. hasNextCh = true;
  141. nextCh = source.decode(posAfterLookahead);
  142. advanceChar(ErrorOnEOF.Yes);
  143. location = Location(filename, 0, 0, 0);
  144. popFront();
  145. }
  146. @property bool empty()
  147. {
  148. return _front.symbol == symbol!"EOF";
  149. }
  150. Token _front;
  151. @property Token front()
  152. {
  153. return _front;
  154. }
  155.  
  156. @property bool isEOF()
  157. {
  158. return location.index == source.length && !lookaheadTokenInfo.exists;
  159. }
  160.  
  161. private void error(string msg)
  162. {
  163. error(location, msg);
  164. }
  165.  
  166. private void error(Location loc, string msg)
  167. {
  168. throw new SDLangParseException(loc, "Error: "~msg);
  169. }
  170.  
  171. private Token makeToken(string symbolName)()
  172. {
  173. auto tok = Token(symbol!symbolName, tokenStart);
  174. tok.data = tokenData;
  175. return tok;
  176. }
  177. private @property string tokenData()
  178. {
  179. return source[ tokenStart.index .. location.index ];
  180. }
  181. /// Check the lookahead character
  182. private bool lookahead(dchar ch)
  183. {
  184. return hasNextCh && nextCh == ch;
  185. }
  186.  
  187. private bool isNewline(dchar ch)
  188. {
  189. return ch == '\n' || ch == '\r' || ch == lineSep || ch == paraSep;
  190. }
  191.  
  192. private bool isAtNewline()
  193. {
  194. return
  195. ch == '\n' || ch == lineSep || ch == paraSep ||
  196. (ch == '\r' && lookahead('\n'));
  197. }
  198.  
  199. /// Is 'ch' a valid base 64 character?
  200. private bool isBase64(dchar ch)
  201. {
  202. if(ch >= 'A' && ch <= 'Z')
  203. return true;
  204.  
  205. if(ch >= 'a' && ch <= 'z')
  206. return true;
  207.  
  208. if(ch >= '0' && ch <= '9')
  209. return true;
  210. return ch == '+' || ch == '/' || ch == '=';
  211. }
  212. /// Is the current character one that's allowed
  213. /// immediately *after* an int/float literal?
  214. private bool isEndOfNumber()
  215. {
  216. if(isEOF)
  217. return true;
  218. return !isDigit(ch) && ch != ':' && ch != '_' && !isAlpha(ch);
  219. }
  220. /// Is current character the last one in an ident?
  221. private bool isEndOfIdentCached = false;
  222. private bool _isEndOfIdent;
  223. private bool isEndOfIdent()
  224. {
  225. if(!isEndOfIdentCached)
  226. {
  227. if(!hasNextCh)
  228. _isEndOfIdent = true;
  229. else
  230. _isEndOfIdent = !isIdentChar(nextCh);
  231. isEndOfIdentCached = true;
  232. }
  233. return _isEndOfIdent;
  234. }
  235.  
  236. /// Is 'ch' a character that's allowed *somewhere* in an identifier?
  237. private bool isIdentChar(dchar ch)
  238. {
  239. if(isAlpha(ch))
  240. return true;
  241. else if(isNumber(ch))
  242. return true;
  243. else
  244. return
  245. ch == '-' ||
  246. ch == '_' ||
  247. ch == '.' ||
  248. ch == '$';
  249. }
  250.  
  251. private bool isDigit(dchar ch)
  252. {
  253. return ch >= '0' && ch <= '9';
  254. }
  255. private enum KeywordResult
  256. {
  257. Accept, // Keyword is matched
  258. Continue, // Keyword is not matched *yet*
  259. Failed, // Keyword doesn't match
  260. }
  261. private KeywordResult checkKeyword(dstring keyword32)
  262. {
  263. // Still within length of keyword
  264. if(tokenLength32 < keyword32.length)
  265. {
  266. if(ch == keyword32[tokenLength32])
  267. return KeywordResult.Continue;
  268. else
  269. return KeywordResult.Failed;
  270. }
  271.  
  272. // At position after keyword
  273. else if(tokenLength32 == keyword32.length)
  274. {
  275. if(isEOF || !isIdentChar(ch))
  276. {
  277. debug assert(tokenData == to!string(keyword32));
  278. return KeywordResult.Accept;
  279. }
  280. else
  281. return KeywordResult.Failed;
  282. }
  283.  
  284. assert(0, "Fell off end of keyword to check");
  285. }
  286.  
  287. enum ErrorOnEOF { No, Yes }
  288.  
  289. /// Advance one code point.
  290. /// Returns false if EOF was reached
  291. private void advanceChar(ErrorOnEOF errorOnEOF)
  292. {
  293. if(isAtNewline())
  294. {
  295. location.line++;
  296. location.col = 0;
  297. }
  298. else
  299. location.col++;
  300.  
  301. location.index = nextPos;
  302.  
  303. nextPos = posAfterLookahead;
  304. ch = nextCh;
  305.  
  306. if(!hasNextCh)
  307. {
  308. if(errorOnEOF == ErrorOnEOF.Yes)
  309. error("Unexpected end of file");
  310.  
  311. return;
  312. }
  313.  
  314. tokenLength32++;
  315. tokenLength = location.index - tokenStart.index;
  316.  
  317. if(nextPos == source.length)
  318. {
  319. nextCh = dchar.init;
  320. hasNextCh = false;
  321. return;
  322. }
  323. nextCh = source.decode(posAfterLookahead);
  324. isEndOfIdentCached = false;
  325. }
  326.  
  327. void popFront()
  328. {
  329. // -- Main Lexer -------------
  330.  
  331. eatWhite();
  332.  
  333. if(isEOF)
  334. mixin(accept!"EOF");
  335. tokenStart = location;
  336. tokenLength = 0;
  337. tokenLength32 = 0;
  338. isEndOfIdentCached = false;
  339. if(lookaheadTokenInfo.exists)
  340. {
  341. tokenStart = lookaheadTokenInfo.tokenStart;
  342.  
  343. auto prevLATokenInfo = lookaheadTokenInfo;
  344. lookaheadTokenInfo = LookaheadTokenInfo.init;
  345. lexNumeric(prevLATokenInfo);
  346. return;
  347. }
  348. if(ch == '=')
  349. {
  350. advanceChar(ErrorOnEOF.No);
  351. mixin(accept!"=");
  352. }
  353. else if(ch == '{')
  354. {
  355. advanceChar(ErrorOnEOF.No);
  356. mixin(accept!"{");
  357. }
  358. else if(ch == '}')
  359. {
  360. advanceChar(ErrorOnEOF.No);
  361. mixin(accept!"}");
  362. }
  363. else if(ch == ':')
  364. {
  365. advanceChar(ErrorOnEOF.No);
  366. mixin(accept!":");
  367. }
  368. else if(ch == ';' || isAtNewline())
  369. {
  370. advanceChar(ErrorOnEOF.No);
  371. mixin(accept!"EOL");
  372. }
  373. else if(isAlpha(ch) || ch == '_')
  374. lexIdentKeyword();
  375.  
  376. else if(ch == '"')
  377. lexRegularString();
  378.  
  379. else if(ch == '`')
  380. lexRawString();
  381. else if(ch == '\'')
  382. lexCharacter();
  383.  
  384. else if(ch == '[')
  385. lexBinary();
  386.  
  387. else if(ch == '-' || ch == '.' || isDigit(ch))
  388. lexNumeric();
  389.  
  390. else
  391. {
  392. advanceChar(ErrorOnEOF.No);
  393. error("Syntax error");
  394. }
  395. }
  396.  
  397. /// Lex Ident or Keyword
  398. private void lexIdentKeyword()
  399. {
  400. assert(isAlpha(ch) || ch == '_');
  401. // Keyword
  402. struct Key
  403. {
  404. dstring name;
  405. Value value;
  406. bool failed = false;
  407. }
  408. static Key[5] keywords;
  409. static keywordsInited = false;
  410. if(!keywordsInited)
  411. {
  412. // Value (as a std.variant-based type) can't be statically inited
  413. keywords[0] = Key("true", Value(true ));
  414. keywords[1] = Key("false", Value(false));
  415. keywords[2] = Key("on", Value(true ));
  416. keywords[3] = Key("off", Value(false));
  417. keywords[4] = Key("null", Value(null ));
  418. keywordsInited = true;
  419. }
  420. foreach(ref key; keywords)
  421. key.failed = false;
  422. auto numKeys = keywords.length;
  423.  
  424. do
  425. {
  426. foreach(ref key; keywords)
  427. if(!key.failed)
  428. {
  429. final switch(checkKeyword(key.name))
  430. {
  431. case KeywordResult.Accept:
  432. mixin(accept!("Value", "key.value"));
  433. case KeywordResult.Continue:
  434. break;
  435. case KeywordResult.Failed:
  436. key.failed = true;
  437. numKeys--;
  438. break;
  439. }
  440. }
  441.  
  442. if(numKeys == 0)
  443. {
  444. lexIdent();
  445. return;
  446. }
  447.  
  448. advanceChar(ErrorOnEOF.No);
  449.  
  450. } while(!isEOF);
  451.  
  452. foreach(ref key; keywords)
  453. if(!key.failed)
  454. if(key.name.length == tokenLength32+1)
  455. mixin(accept!("Value", "key.value"));
  456.  
  457. mixin(accept!"Ident");
  458. }
  459.  
  460. /// Lex Ident
  461. private void lexIdent()
  462. {
  463. if(tokenLength == 0)
  464. assert(isAlpha(ch) || ch == '_');
  465. while(!isEOF && isIdentChar(ch))
  466. advanceChar(ErrorOnEOF.No);
  467.  
  468. mixin(accept!"Ident");
  469. }
  470. /// Lex regular string
  471. private void lexRegularString()
  472. {
  473. assert(ch == '"');
  474.  
  475. Appender!string buf;
  476. size_t spanStart = nextPos;
  477. // Doesn't include current character
  478. void updateBuf()
  479. {
  480. if(location.index == spanStart)
  481. return;
  482.  
  483. buf.put( source[spanStart..location.index] );
  484. }
  485. do
  486. {
  487. advanceChar(ErrorOnEOF.Yes);
  488.  
  489. if(ch == '\\')
  490. {
  491. updateBuf();
  492.  
  493. bool wasEscSequence = true;
  494. if(hasNextCh)
  495. {
  496. switch(nextCh)
  497. {
  498. case 'n': buf.put('\n'); break;
  499. case 'r': buf.put('\r'); break;
  500. case 't': buf.put('\t'); break;
  501. case '"': buf.put('\"'); break;
  502. case '\\': buf.put('\\'); break;
  503. default: wasEscSequence = false; break;
  504. }
  505. }
  506. if(wasEscSequence)
  507. {
  508. advanceChar(ErrorOnEOF.Yes);
  509. spanStart = nextPos;
  510. }
  511. else
  512. {
  513. eatWhite(false);
  514. spanStart = location.index;
  515. }
  516. }
  517.  
  518. else if(isNewline(ch))
  519. error("Unescaped newlines are only allowed in raw strings, not regular strings.");
  520.  
  521. } while(ch != '"');
  522. updateBuf();
  523. advanceChar(ErrorOnEOF.No); // Skip closing double-quote
  524. mixin(accept!("Value", "buf.data"));
  525. }
  526.  
  527. /// Lex raw string
  528. private void lexRawString()
  529. {
  530. assert(ch == '`');
  531. do
  532. advanceChar(ErrorOnEOF.Yes);
  533. while(ch != '`');
  534. advanceChar(ErrorOnEOF.No); // Skip closing back-tick
  535. mixin(accept!("Value", "tokenData[1..$-1]"));
  536. }
  537. /// Lex character literal
  538. private void lexCharacter()
  539. {
  540. assert(ch == '\'');
  541. advanceChar(ErrorOnEOF.Yes); // Skip opening single-quote
  542. dchar value;
  543. if(ch == '\\')
  544. {
  545. advanceChar(ErrorOnEOF.Yes); // Skip escape backslash
  546. switch(ch)
  547. {
  548. case 'n': value = '\n'; break;
  549. case 'r': value = '\r'; break;
  550. case 't': value = '\t'; break;
  551. case '\'': value = '\''; break;
  552. case '\\': value = '\\'; break;
  553. default: error("Invalid escape sequence.");
  554. }
  555. }
  556. else if(isNewline(ch))
  557. error("Newline not alowed in character literal.");
  558. else
  559. value = ch;
  560. advanceChar(ErrorOnEOF.Yes); // Skip the character itself
  561.  
  562. if(ch == '\'')
  563. advanceChar(ErrorOnEOF.No); // Skip closing single-quote
  564. else
  565. error("Expected closing single-quote.");
  566.  
  567. mixin(accept!("Value", "value"));
  568. }
  569. /// Lex base64 binary literal
  570. private void lexBinary()
  571. {
  572. assert(ch == '[');
  573. advanceChar(ErrorOnEOF.Yes);
  574. void eatBase64Whitespace()
  575. {
  576. while(!isEOF && isWhite(ch))
  577. {
  578. if(isNewline(ch))
  579. advanceChar(ErrorOnEOF.Yes);
  580. if(!isEOF && isWhite(ch))
  581. eatWhite();
  582. }
  583. }
  584. eatBase64Whitespace();
  585.  
  586. // Iterates all valid base64 characters, ending at ']'.
  587. // Skips all whitespace. Throws on invalid chars.
  588. struct Base64InputRange
  589. {
  590. Lexer lexer;
  591. private bool isInited = false;
  592. private int numInputCharsMod4 = 0;
  593. @property bool empty()
  594. {
  595. if(lexer.ch == ']')
  596. {
  597. if(numInputCharsMod4 != 0)
  598. lexer.error("Length of Base64 encoding must be a multiple of 4. ("~to!string(numInputCharsMod4)~")");
  599. return true;
  600. }
  601. return false;
  602. }
  603.  
  604. @property dchar front()
  605. {
  606. return lexer.ch;
  607. }
  608. void popFront()
  609. {
  610. auto lex = lexer;
  611.  
  612. if(!isInited)
  613. {
  614. if(lexer.isBase64(lexer.ch))
  615. {
  616. numInputCharsMod4++;
  617. numInputCharsMod4 %= 4;
  618. }
  619. isInited = true;
  620. }
  621. lex.advanceChar(lex.ErrorOnEOF.Yes);
  622.  
  623. eatBase64Whitespace();
  624. if(lex.isEOF)
  625. lex.error("Unexpected end of file.");
  626.  
  627. if(lex.ch != ']')
  628. {
  629. if(!lex.isBase64(lex.ch))
  630. lex.error("Invalid character in base64 binary literal.");
  631. numInputCharsMod4++;
  632. numInputCharsMod4 %= 4;
  633. }
  634. }
  635. }
  636. // This is a slow ugly hack. It's necessary because Base64.decode
  637. // currently requires the source to have known length.
  638. //TODO: Remove this when DMD issue #9543 is fixed.
  639. dchar[] tmpBuf = array(Base64InputRange(this));
  640.  
  641. Appender!(ubyte[]) outputBuf;
  642. // Ugly workaround for DMD issue #9102
  643. //TODO: Remove this when DMD #9102 is fixed
  644. struct OutputBuf
  645. {
  646. void put(ubyte ch)
  647. {
  648. outputBuf.put(ch);
  649. }
  650. }
  651. try
  652. //Base64.decode(Base64InputRange(this), OutputBuf());
  653. Base64.decode(tmpBuf, OutputBuf());
  654.  
  655. //TODO: Starting with dmd 2.062, this should be a Base64Exception
  656. catch(Exception e)
  657. error("Invalid character in base64 binary literal.");
  658. advanceChar(ErrorOnEOF.No); // Skip ']'
  659. mixin(accept!("Value", "outputBuf.data"));
  660. }
  661. private BigInt toBigInt(bool isNegative, string absValue)
  662. {
  663. auto num = BigInt(absValue);
  664. assert(num >= 0);
  665.  
  666. if(isNegative)
  667. num = -num;
  668.  
  669. return num;
  670. }
  671.  
  672. /// Lex [0-9]+, but without emitting a token.
  673. /// This is used by the other numeric parsing functions.
  674. private string lexNumericFragment()
  675. {
  676. if(!isDigit(ch))
  677. error("Expected a digit 0-9.");
  678. auto spanStart = location.index;
  679. do
  680. {
  681. advanceChar(ErrorOnEOF.No);
  682. } while(!isEOF && isDigit(ch));
  683. return source[spanStart..location.index];
  684. }
  685.  
  686. /// Lex anything that starts with 0-9 or '-'. Ints, floats, dates, etc.
  687. private void lexNumeric(LookaheadTokenInfo laTokenInfo = LookaheadTokenInfo.init)
  688. {
  689. bool isNegative;
  690. string firstFragment;
  691. if(laTokenInfo.exists)
  692. {
  693. firstFragment = laTokenInfo.numericFragment;
  694. isNegative = laTokenInfo.isNegative;
  695. }
  696. else
  697. {
  698. assert(ch == '-' || ch == '.' || isDigit(ch));
  699.  
  700. // Check for negative
  701. isNegative = ch == '-';
  702. if(isNegative)
  703. advanceChar(ErrorOnEOF.Yes);
  704.  
  705. // Some floating point with omitted leading zero?
  706. if(ch == '.')
  707. {
  708. lexFloatingPoint("");
  709. return;
  710. }
  711.  
  712. firstFragment = lexNumericFragment();
  713. }
  714.  
  715. // Long integer (64-bit signed)?
  716. if(ch == 'L' || ch == 'l')
  717. {
  718. advanceChar(ErrorOnEOF.No);
  719.  
  720. // BigInt(long.min) is a workaround for DMD issue #9548
  721. auto num = toBigInt(isNegative, firstFragment);
  722. if(num < BigInt(long.min) || num > long.max)
  723. error(tokenStart, "Value doesn't fit in 64-bit signed long integer: "~to!string(num));
  724.  
  725. mixin(accept!("Value", "num.toLong()"));
  726. }
  727. // Float (32-bit signed)?
  728. else if(ch == 'F' || ch == 'f')
  729. {
  730. auto value = to!float(tokenData);
  731. advanceChar(ErrorOnEOF.No);
  732. mixin(accept!("Value", "value"));
  733. }
  734. // Double float (64-bit signed) with suffix?
  735. else if((ch == 'D' || ch == 'd') && !lookahead(':')
  736. )
  737. {
  738. auto value = to!double(tokenData);
  739. advanceChar(ErrorOnEOF.No);
  740. mixin(accept!("Value", "value"));
  741. }
  742. // Decimal (128+ bits signed)?
  743. else if(
  744. (ch == 'B' || ch == 'b') &&
  745. (lookahead('D') || lookahead('d'))
  746. )
  747. {
  748. auto value = to!real(tokenData);
  749. advanceChar(ErrorOnEOF.No);
  750. advanceChar(ErrorOnEOF.No);
  751. mixin(accept!("Value", "value"));
  752. }
  753. // Some floating point?
  754. else if(ch == '.')
  755. lexFloatingPoint(firstFragment);
  756. // Some date?
  757. else if(ch == '/' && hasNextCh && isDigit(nextCh))
  758. lexDate(isNegative, firstFragment);
  759. // Some time span?
  760. else if(ch == ':' || ch == 'd')
  761. lexTimeSpan(isNegative, firstFragment);
  762.  
  763. // Integer (32-bit signed)?
  764. else if(isEndOfNumber())
  765. {
  766. auto num = toBigInt(isNegative, firstFragment);
  767. if(num < int.min || num > int.max)
  768. error(tokenStart, "Value doesn't fit in 32-bit signed integer: "~to!string(num));
  769.  
  770. mixin(accept!("Value", "num.toInt()"));
  771. }
  772.  
  773. // Invalid suffix
  774. else
  775. error("Invalid integer suffix.");
  776. }
  777. /// Lex any floating-point literal (after the initial numeric fragment was lexed)
  778. private void lexFloatingPoint(string firstPart)
  779. {
  780. assert(ch == '.');
  781. advanceChar(ErrorOnEOF.No);
  782. auto secondPart = lexNumericFragment();
  783. try
  784. {
  785. // Double float (64-bit signed) with suffix?
  786. if(ch == 'D' || ch == 'd')
  787. {
  788. auto value = to!double(tokenData);
  789. advanceChar(ErrorOnEOF.No);
  790. mixin(accept!("Value", "value"));
  791. }
  792.  
  793. // Float (32-bit signed)?
  794. else if(ch == 'F' || ch == 'f')
  795. {
  796. auto value = to!float(tokenData);
  797. advanceChar(ErrorOnEOF.No);
  798. mixin(accept!("Value", "value"));
  799. }
  800.  
  801. // Decimal (128+ bits signed)?
  802. else if(ch == 'B' || ch == 'b')
  803. {
  804. auto value = to!real(tokenData);
  805. advanceChar(ErrorOnEOF.Yes);
  806.  
  807. if(!isEOF && (ch == 'D' || ch == 'd'))
  808. {
  809. advanceChar(ErrorOnEOF.No);
  810. if(isEndOfNumber())
  811. mixin(accept!("Value", "value"));
  812. }
  813.  
  814. error("Invalid floating point suffix.");
  815. }
  816.  
  817. // Double float (64-bit signed) without suffix?
  818. else if(isEOF || !isIdentChar(ch))
  819. {
  820. auto value = to!double(tokenData);
  821. mixin(accept!("Value", "value"));
  822. }
  823.  
  824. // Invalid suffix
  825. else
  826. error("Invalid floating point suffix.");
  827. }
  828. catch(ConvException e)
  829. error("Invalid floating point literal.");
  830. }
  831.  
  832. private Date makeDate(bool isNegative, string yearStr, string monthStr, string dayStr)
  833. {
  834. BigInt biTmp;
  835. biTmp = BigInt(yearStr);
  836. if(isNegative)
  837. biTmp = -biTmp;
  838. if(biTmp < int.min || biTmp > int.max)
  839. error(tokenStart, "Date's year is out of range. (Must fit within a 32-bit signed int.)");
  840. auto year = biTmp.toInt();
  841.  
  842. biTmp = BigInt(monthStr);
  843. if(biTmp < 1 || biTmp > 12)
  844. error(tokenStart, "Date's month is out of range.");
  845. auto month = biTmp.toInt();
  846. biTmp = BigInt(dayStr);
  847. if(biTmp < 1 || biTmp > 31)
  848. error(tokenStart, "Date's month is out of range.");
  849. auto day = biTmp.toInt();
  850. return Date(year, month, day);
  851. }
  852. private DateTimeFrac makeDateTimeFrac(
  853. bool isNegative, Date date, string hourStr, string minuteStr,
  854. string secondStr, string millisecondStr
  855. )
  856. {
  857. BigInt biTmp;
  858.  
  859. biTmp = BigInt(hourStr);
  860. if(biTmp < int.min || biTmp > int.max)
  861. error(tokenStart, "Datetime's hour is out of range.");
  862. auto numHours = biTmp.toInt();
  863. biTmp = BigInt(minuteStr);
  864. if(biTmp < 0 || biTmp > int.max)
  865. error(tokenStart, "Datetime's minute is out of range.");
  866. auto numMinutes = biTmp.toInt();
  867. int numSeconds = 0;
  868. if(secondStr != "")
  869. {
  870. biTmp = BigInt(secondStr);
  871. if(biTmp < 0 || biTmp > int.max)
  872. error(tokenStart, "Datetime's second is out of range.");
  873. numSeconds = biTmp.toInt();
  874. }
  875. int millisecond = 0;
  876. if(millisecondStr != "")
  877. {
  878. biTmp = BigInt(millisecondStr);
  879. if(biTmp < 0 || biTmp > int.max)
  880. error(tokenStart, "Datetime's millisecond is out of range.");
  881. millisecond = biTmp.toInt();
  882.  
  883. if(millisecondStr.length == 1)
  884. millisecond *= 100;
  885. else if(millisecondStr.length == 2)
  886. millisecond *= 10;
  887. }
  888.  
  889. FracSec fracSecs;
  890. fracSecs.msecs = millisecond;
  891. auto offset = hours(numHours) + minutes(numMinutes) + seconds(numSeconds);
  892.  
  893. if(isNegative)
  894. {
  895. offset = -offset;
  896. fracSecs = -fracSecs;
  897. }
  898. return DateTimeFrac(DateTime(date) + offset, fracSecs);
  899. }
  900.  
  901. private Duration makeDuration(
  902. bool isNegative, string dayStr,
  903. string hourStr, string minuteStr, string secondStr,
  904. string millisecondStr
  905. )
  906. {
  907. BigInt biTmp;
  908.  
  909. long day = 0;
  910. if(dayStr != "")
  911. {
  912. biTmp = BigInt(dayStr);
  913. if(biTmp < long.min || biTmp > long.max)
  914. error(tokenStart, "Time span's day is out of range.");
  915. day = biTmp.toLong();
  916. }
  917.  
  918. biTmp = BigInt(hourStr);
  919. if(biTmp < long.min || biTmp > long.max)
  920. error(tokenStart, "Time span's hour is out of range.");
  921. auto hour = biTmp.toLong();
  922.  
  923. biTmp = BigInt(minuteStr);
  924. if(biTmp < long.min || biTmp > long.max)
  925. error(tokenStart, "Time span's minute is out of range.");
  926. auto minute = biTmp.toLong();
  927.  
  928. biTmp = BigInt(secondStr);
  929. if(biTmp < long.min || biTmp > long.max)
  930. error(tokenStart, "Time span's second is out of range.");
  931. auto second = biTmp.toLong();
  932.  
  933. long millisecond = 0;
  934. if(millisecondStr != "")
  935. {
  936. biTmp = BigInt(millisecondStr);
  937. if(biTmp < long.min || biTmp > long.max)
  938. error(tokenStart, "Time span's millisecond is out of range.");
  939. millisecond = biTmp.toLong();
  940.  
  941. if(millisecondStr.length == 1)
  942. millisecond *= 100;
  943. else if(millisecondStr.length == 2)
  944. millisecond *= 10;
  945. }
  946. auto duration =
  947. dur!"days" (day) +
  948. dur!"hours" (hour) +
  949. dur!"minutes"(minute) +
  950. dur!"seconds"(second) +
  951. dur!"msecs" (millisecond);
  952.  
  953. if(isNegative)
  954. duration = -duration;
  955. return duration;
  956. }
  957.  
  958. // This has to reproduce some weird corner case behaviors from the
  959. // original Java version of SDL. So some of this may seem weird.
  960. private Nullable!Duration getTimeZoneOffset(string str)
  961. {
  962. if(str.length < 2)
  963. return Nullable!Duration(); // Unknown timezone
  964. if(str[0] != '+' && str[0] != '-')
  965. return Nullable!Duration(); // Unknown timezone
  966.  
  967. auto isNegative = str[0] == '-';
  968.  
  969. string numHoursStr;
  970. string numMinutesStr;
  971. if(str[1] == ':')
  972. {
  973. numMinutesStr = str[1..$];
  974. numHoursStr = "";
  975. }
  976. else
  977. {
  978. numMinutesStr = str.find(':');
  979. numHoursStr = str[1 .. $-numMinutesStr.length];
  980. }
  981. long numHours = 0;
  982. long numMinutes = 0;
  983. bool isUnknown = false;
  984. try
  985. {
  986. switch(numHoursStr.length)
  987. {
  988. case 0:
  989. if(numMinutesStr.length == 3)
  990. {
  991. numHours = 0;
  992. numMinutes = to!long(numMinutesStr[1..$]);
  993. }
  994. else
  995. isUnknown = true;
  996. break;
  997.  
  998. case 1:
  999. case 2:
  1000. if(numMinutesStr.length == 0)
  1001. {
  1002. numHours = to!long(numHoursStr);
  1003. numMinutes = 0;
  1004. }
  1005. else if(numMinutesStr.length == 3)
  1006. {
  1007. numHours = to!long(numHoursStr);
  1008. numMinutes = to!long(numMinutesStr[1..$]);
  1009. }
  1010. else
  1011. isUnknown = true;
  1012. break;
  1013.  
  1014. default:
  1015. if(numMinutesStr.length == 0)
  1016. {
  1017. // Yes, this is correct
  1018. numHours = 0;
  1019. numMinutes = to!long(numHoursStr[1..$]);
  1020. }
  1021. else
  1022. isUnknown = true;
  1023. break;
  1024. }
  1025. }
  1026. catch(ConvException e)
  1027. isUnknown = true;
  1028. if(isUnknown)
  1029. return Nullable!Duration(); // Unknown timezone
  1030.  
  1031. auto timeZoneOffset = hours(numHours) + minutes(numMinutes);
  1032. if(isNegative)
  1033. timeZoneOffset = -timeZoneOffset;
  1034.  
  1035. // Timezone valid
  1036. return Nullable!Duration(timeZoneOffset);
  1037. }
  1038. /// Lex date or datetime (after the initial numeric fragment was lexed)
  1039. private void lexDate(bool isDateNegative, string yearStr)
  1040. {
  1041. assert(ch == '/');
  1042. // Lex months
  1043. advanceChar(ErrorOnEOF.Yes); // Skip '/'
  1044. auto monthStr = lexNumericFragment();
  1045.  
  1046. // Lex days
  1047. if(ch != '/')
  1048. error("Invalid date format: Missing days.");
  1049. advanceChar(ErrorOnEOF.Yes); // Skip '/'
  1050. auto dayStr = lexNumericFragment();
  1051. auto date = makeDate(isDateNegative, yearStr, monthStr, dayStr);
  1052.  
  1053. if(!isEndOfNumber() && ch != '/')
  1054. error("Dates cannot have suffixes.");
  1055. // Date?
  1056. if(isEOF)
  1057. mixin(accept!("Value", "date"));
  1058. auto endOfDate = location;
  1059. while(
  1060. !isEOF &&
  1061. ( ch == '\\' || ch == '/' || (isWhite(ch) && !isNewline(ch)) )
  1062. )
  1063. {
  1064. if(ch == '\\' && hasNextCh && isNewline(nextCh))
  1065. {
  1066. advanceChar(ErrorOnEOF.Yes);
  1067. if(isAtNewline())
  1068. advanceChar(ErrorOnEOF.Yes);
  1069. advanceChar(ErrorOnEOF.No);
  1070. }
  1071.  
  1072. eatWhite();
  1073. }
  1074.  
  1075. // Date?
  1076. if(isEOF || (!isDigit(ch) && ch != '-'))
  1077. mixin(accept!("Value", "date", "", "endOfDate.index"));
  1078. auto startOfTime = location;
  1079.  
  1080. // Is time negative?
  1081. bool isTimeNegative = ch == '-';
  1082. if(isTimeNegative)
  1083. advanceChar(ErrorOnEOF.Yes);
  1084.  
  1085. // Lex hours
  1086. auto hourStr = ch == '.'? "" : lexNumericFragment();
  1087. // Lex minutes
  1088. if(ch != ':')
  1089. {
  1090. // No minutes found. Therefore we had a plain Date followed
  1091. // by a numeric literal, not a DateTime.
  1092. lookaheadTokenInfo.exists = true;
  1093. lookaheadTokenInfo.numericFragment = hourStr;
  1094. lookaheadTokenInfo.isNegative = isTimeNegative;
  1095. lookaheadTokenInfo.tokenStart = startOfTime;
  1096. mixin(accept!("Value", "date", "", "endOfDate.index"));
  1097. }
  1098. advanceChar(ErrorOnEOF.Yes); // Skip ':'
  1099. auto minuteStr = lexNumericFragment();
  1100. // Lex seconds, if exists
  1101. string secondStr;
  1102. if(ch == ':')
  1103. {
  1104. advanceChar(ErrorOnEOF.Yes); // Skip ':'
  1105. secondStr = lexNumericFragment();
  1106. }
  1107. // Lex milliseconds, if exists
  1108. string millisecondStr;
  1109. if(ch == '.')
  1110. {
  1111. advanceChar(ErrorOnEOF.Yes); // Skip '.'
  1112. millisecondStr = lexNumericFragment();
  1113. }
  1114.  
  1115. auto dateTimeFrac = makeDateTimeFrac(isTimeNegative, date, hourStr, minuteStr, secondStr, millisecondStr);
  1116. // Lex zone, if exists
  1117. if(ch == '-')
  1118. {
  1119. advanceChar(ErrorOnEOF.Yes); // Skip '-'
  1120. auto timezoneStart = location;
  1121. if(!isAlpha(ch))
  1122. error("Invalid timezone format.");
  1123. while(!isEOF && !isWhite(ch))
  1124. advanceChar(ErrorOnEOF.No);
  1125. auto timezoneStr = source[timezoneStart.index..location.index];
  1126. if(timezoneStr.startsWith("GMT"))
  1127. {
  1128. auto isoPart = timezoneStr["GMT".length..$];
  1129. auto offset = getTimeZoneOffset(isoPart);
  1130. if(offset.isNull())
  1131. {
  1132. // Unknown time zone
  1133. mixin(accept!("Value", "DateTimeFracUnknownZone(dateTimeFrac.dateTime, dateTimeFrac.fracSec, timezoneStr)"));
  1134. }
  1135. else
  1136. {
  1137. auto timezone = new immutable SimpleTimeZone(offset.get());
  1138. mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, dateTimeFrac.fracSec, timezone)"));
  1139. }
  1140. }
  1141. try
  1142. {
  1143. auto timezone = TimeZone.getTimeZone(timezoneStr);
  1144. if(timezone)
  1145. mixin(accept!("Value", "SysTime(dateTimeFrac.dateTime, dateTimeFrac.fracSec, timezone)"));
  1146. }
  1147. catch(TimeException e)
  1148. {
  1149. // Time zone not found. So just move along to "Unknown time zone" below.
  1150. }
  1151.  
  1152. // Unknown time zone
  1153. mixin(accept!("Value", "DateTimeFracUnknownZone(dateTimeFrac.dateTime, dateTimeFrac.fracSec, timezoneStr)"));
  1154. }
  1155.  
  1156. if(!isEndOfNumber())
  1157. error("Date-Times cannot have suffixes.");
  1158.  
  1159. mixin(accept!("Value", "dateTimeFrac"));
  1160. }
  1161.  
  1162. /// Lex time span (after the initial numeric fragment was lexed)
  1163. private void lexTimeSpan(bool isNegative, string firstPart)
  1164. {
  1165. assert(ch == ':' || ch == 'd');
  1166. string dayStr = "";
  1167. string hourStr;
  1168.  
  1169. // Lexed days?
  1170. bool hasDays = ch == 'd';
  1171. if(hasDays)
  1172. {
  1173. dayStr = firstPart;
  1174. advanceChar(ErrorOnEOF.Yes); // Skip 'd'
  1175.  
  1176. // Lex hours
  1177. if(ch != ':')
  1178. error("Invalid time span format: Missing hours.");
  1179. advanceChar(ErrorOnEOF.Yes); // Skip ':'
  1180. hourStr = lexNumericFragment();
  1181. }
  1182. else
  1183. hourStr = firstPart;
  1184.  
  1185. // Lex minutes
  1186. if(ch != ':')
  1187. error("Invalid time span format: Missing minutes.");
  1188. advanceChar(ErrorOnEOF.Yes); // Skip ':'
  1189. auto minuteStr = lexNumericFragment();
  1190.  
  1191. // Lex seconds
  1192. if(ch != ':')
  1193. error("Invalid time span format: Missing seconds.");
  1194. advanceChar(ErrorOnEOF.Yes); // Skip ':'
  1195. auto secondStr = lexNumericFragment();
  1196. // Lex milliseconds, if exists
  1197. string millisecondStr = "";
  1198. if(ch == '.')
  1199. {
  1200. advanceChar(ErrorOnEOF.Yes); // Skip '.'
  1201. millisecondStr = lexNumericFragment();
  1202. }
  1203.  
  1204. if(!isEndOfNumber())
  1205. error("Time spans cannot have suffixes.");
  1206. auto duration = makeDuration(isNegative, dayStr, hourStr, minuteStr, secondStr, millisecondStr);
  1207. mixin(accept!("Value", "duration"));
  1208. }
  1209.  
  1210. /// Advances past whitespace and comments
  1211. private void eatWhite(bool allowComments=true)
  1212. {
  1213. // -- Comment/Whitepace Lexer -------------
  1214.  
  1215. enum State
  1216. {
  1217. normal,
  1218. lineComment, // Got "#" or "//" or "--", Eating everything until newline
  1219. blockComment, // Got "/*", Eating everything until "*/"
  1220. }
  1221.  
  1222. if(isEOF)
  1223. return;
  1224. Location commentStart;
  1225. State state = State.normal;
  1226. bool consumeNewlines = false;
  1227. bool hasConsumedNewline = false;
  1228. while(true)
  1229. {
  1230. final switch(state)
  1231. {
  1232. case State.normal:
  1233.  
  1234. if(ch == '\\')
  1235. {
  1236. commentStart = location;
  1237. consumeNewlines = true;
  1238. hasConsumedNewline = false;
  1239. }
  1240.  
  1241. else if(ch == '#')
  1242. {
  1243. if(!allowComments)
  1244. return;
  1245.  
  1246. commentStart = location;
  1247. state = State.lineComment;
  1248. }
  1249.  
  1250. else if(ch == '/' || ch == '-')
  1251. {
  1252. commentStart = location;
  1253. if(lookahead(ch))
  1254. {
  1255. if(!allowComments)
  1256. return;
  1257.  
  1258. advanceChar(ErrorOnEOF.No);
  1259. state = State.lineComment;
  1260. }
  1261. else if(ch == '/' && lookahead('*'))
  1262. {
  1263. if(!allowComments)
  1264. return;
  1265.  
  1266. advanceChar(ErrorOnEOF.No);
  1267. state = State.blockComment;
  1268. }
  1269. else
  1270. return; // Done
  1271. }
  1272. else if(isAtNewline())
  1273. {
  1274. if(consumeNewlines)
  1275. hasConsumedNewline = true;
  1276. else
  1277. return; // Done
  1278. }
  1279. else if(!isWhite(ch))
  1280. {
  1281. if(consumeNewlines)
  1282. {
  1283. if(hasConsumedNewline)
  1284. return; // Done
  1285. else
  1286. error("Only whitespace can come between a line-continuation backslash and the following newline.");
  1287. }
  1288. else
  1289. return; // Done
  1290. }
  1291.  
  1292. break;
  1293. case State.lineComment:
  1294. if(isNewline(ch))
  1295. state = State.normal;
  1296. break;
  1297. case State.blockComment:
  1298. if(ch == '*' && lookahead('/'))
  1299. {
  1300. advanceChar(ErrorOnEOF.No);
  1301. state = State.normal;
  1302. }
  1303. break;
  1304. }
  1305. advanceChar(ErrorOnEOF.No);
  1306. if(isEOF)
  1307. {
  1308. // Reached EOF
  1309.  
  1310. if(consumeNewlines && !hasConsumedNewline)
  1311. error("Missing newline after line-continuation backslash.");
  1312.  
  1313. else if(state == State.blockComment)
  1314. error(commentStart, "Unterminated block comment.");
  1315.  
  1316. else
  1317. return; // Done, reached EOF
  1318. }
  1319. }
  1320. }
  1321. }
  1322.  
  1323. version(sdlangUnittest)
  1324. {
  1325. import std.stdio;
  1326.  
  1327. private auto loc = Location("filename", 0, 0, 0);
  1328. private auto loc2 = Location("a", 1, 1, 1);
  1329.  
  1330. unittest
  1331. {
  1332. assert([Token(symbol!"EOL",loc) ] == [Token(symbol!"EOL",loc) ] );
  1333. assert([Token(symbol!"EOL",loc,Value(7),"A")] == [Token(symbol!"EOL",loc2,Value(7),"B")] );
  1334. }
  1335.  
  1336. private int numErrors = 0;
  1337. private void testLex(string file=__FILE__, size_t line=__LINE__)(string source, Token[] expected)
  1338. {
  1339. Token[] actual;
  1340. try
  1341. actual = lexSource(source, "filename");
  1342. catch(SDLangParseException e)
  1343. {
  1344. numErrors++;
  1345. stderr.writeln(file, "(", line, "): testLex failed on: ", source);
  1346. stderr.writeln(" Expected:");
  1347. stderr.writeln(" ", expected);
  1348. stderr.writeln(" Actual: SDLangParseException thrown:");
  1349. stderr.writeln(" ", e.msg);
  1350. return;
  1351. }
  1352. if(actual != expected)
  1353. {
  1354. numErrors++;
  1355. stderr.writeln(file, "(", line, "): testLex failed on: ", source);
  1356. stderr.writeln(" Expected:");
  1357. stderr.writeln(" ", expected);
  1358. stderr.writeln(" Actual:");
  1359. stderr.writeln(" ", actual);
  1360.  
  1361. if(expected.length > 1 || actual.length > 1)
  1362. {
  1363. stderr.writeln(" expected.length: ", expected.length);
  1364. stderr.writeln(" actual.length: ", actual.length);
  1365.  
  1366. if(actual.length == expected.length)
  1367. foreach(i; 0..actual.length)
  1368. if(actual[i] != expected[i])
  1369. {
  1370. stderr.writeln(" Unequal at index #", i, ":");
  1371. stderr.writeln(" Expected:");
  1372. stderr.writeln(" ", expected[i]);
  1373. stderr.writeln(" Actual:");
  1374. stderr.writeln(" ", actual[i]);
  1375. }
  1376. }
  1377. }
  1378. }
  1379.  
  1380. private void testLexThrows(string file=__FILE__, size_t line=__LINE__)(string source)
  1381. {
  1382. bool hadException = false;
  1383. Token[] actual;
  1384. try
  1385. actual = lexSource(source, "filename");
  1386. catch(SDLangParseException e)
  1387. hadException = true;
  1388.  
  1389. if(!hadException)
  1390. {
  1391. numErrors++;
  1392. stderr.writeln(file, "(", line, "): testLex failed on: ", source);
  1393. stderr.writeln(" Expected SDLangParseException");
  1394. stderr.writeln(" Actual:");
  1395. stderr.writeln(" ", actual);
  1396. }
  1397. }
  1398. }
  1399.  
  1400. version(sdlangUnittest)
  1401. unittest
  1402. {
  1403. writeln("Unittesting sdlang lexer...");
  1404. stdout.flush();
  1405. testLex("", []);
  1406. testLex(" ", []);
  1407. testLex("\\\n", []);
  1408. testLex("/*foo*/", []);
  1409. testLex("/* multiline \n comment */", []);
  1410. testLex("/* * */", []);
  1411. testLexThrows("/* ");
  1412.  
  1413. testLex(":", [ Token(symbol!":", loc) ]);
  1414. testLex("=", [ Token(symbol!"=", loc) ]);
  1415. testLex("{", [ Token(symbol!"{", loc) ]);
  1416. testLex("}", [ Token(symbol!"}", loc) ]);
  1417. testLex(";", [ Token(symbol!"EOL",loc) ]);
  1418. testLex("\n", [ Token(symbol!"EOL",loc) ]);
  1419.  
  1420. testLex("foo", [ Token(symbol!"Ident",loc,Value(null),"foo") ]);
  1421. testLex("_foo", [ Token(symbol!"Ident",loc,Value(null),"_foo") ]);
  1422. testLex("foo.bar", [ Token(symbol!"Ident",loc,Value(null),"foo.bar") ]);
  1423. testLex("foo-bar", [ Token(symbol!"Ident",loc,Value(null),"foo-bar") ]);
  1424. testLex("foo.", [ Token(symbol!"Ident",loc,Value(null),"foo.") ]);
  1425. testLex("foo-", [ Token(symbol!"Ident",loc,Value(null),"foo-") ]);
  1426. testLexThrows(".foo");
  1427.  
  1428. testLex("foo bar", [
  1429. Token(symbol!"Ident",loc,Value(null),"foo"),
  1430. Token(symbol!"Ident",loc,Value(null),"bar"),
  1431. ]);
  1432. testLex("foo \\ \n \n bar", [
  1433. Token(symbol!"Ident",loc,Value(null),"foo"),
  1434. Token(symbol!"Ident",loc,Value(null),"bar"),
  1435. ]);
  1436. testLex("foo \\ \n \\ \n bar", [
  1437. Token(symbol!"Ident",loc,Value(null),"foo"),
  1438. Token(symbol!"Ident",loc,Value(null),"bar"),
  1439. ]);
  1440. testLexThrows("foo \\ ");
  1441. testLexThrows("foo \\ bar");
  1442. testLexThrows("foo \\ \n \\ ");
  1443. testLexThrows("foo \\ \n \\ bar");
  1444.  
  1445. testLex("foo : = { } ; \n bar \n", [
  1446. Token(symbol!"Ident",loc,Value(null),"foo"),
  1447. Token(symbol!":",loc),
  1448. Token(symbol!"=",loc),
  1449. Token(symbol!"{",loc),
  1450. Token(symbol!"}",loc),
  1451. Token(symbol!"EOL",loc),
  1452. Token(symbol!"EOL",loc),
  1453. Token(symbol!"Ident",loc,Value(null),"bar"),
  1454. Token(symbol!"EOL",loc),
  1455. ]);
  1456.  
  1457. testLexThrows("<");
  1458. testLexThrows("*");
  1459. testLexThrows(`\`);
  1460. // Integers
  1461. testLex( "7", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]);
  1462. testLex( "-7", [ Token(symbol!"Value",loc,Value(cast( int)-7)) ]);
  1463. testLex( "7L", [ Token(symbol!"Value",loc,Value(cast(long) 7)) ]);
  1464. testLex( "7l", [ Token(symbol!"Value",loc,Value(cast(long) 7)) ]);
  1465. testLex("-7L", [ Token(symbol!"Value",loc,Value(cast(long)-7)) ]);
  1466. testLex( "0", [ Token(symbol!"Value",loc,Value(cast( int) 0)) ]);
  1467. testLex( "-0", [ Token(symbol!"Value",loc,Value(cast( int) 0)) ]);
  1468.  
  1469. testLex("7/**/", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]);
  1470. testLex("7#", [ Token(symbol!"Value",loc,Value(cast( int) 7)) ]);
  1471.  
  1472. testLex("7 A", [
  1473. Token(symbol!"Value",loc,Value(cast(int)7)),
  1474. Token(symbol!"Ident",loc,Value( null),"A"),
  1475. ]);
  1476. testLexThrows("7A");
  1477. testLexThrows("-A");
  1478. testLexThrows(`-""`);
  1479. testLex("7;", [
  1480. Token(symbol!"Value",loc,Value(cast(int)7)),
  1481. Token(symbol!"EOL",loc),
  1482. ]);
  1483. // Floats
  1484. testLex("1.2F" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]);
  1485. testLex("1.2f" , [ Token(symbol!"Value",loc,Value(cast( float)1.2)) ]);
  1486. testLex("1.2" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]);
  1487. testLex("1.2D" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]);
  1488. testLex("1.2d" , [ Token(symbol!"Value",loc,Value(cast(double)1.2)) ]);
  1489. testLex("1.2BD", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]);
  1490. testLex("1.2bd", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]);
  1491. testLex("1.2Bd", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]);
  1492. testLex("1.2bD", [ Token(symbol!"Value",loc,Value(cast( real)1.2)) ]);
  1493.  
  1494. testLex(".2F" , [ Token(symbol!"Value",loc,Value(cast( float)0.2)) ]);
  1495. testLex(".2" , [ Token(symbol!"Value",loc,Value(cast(double)0.2)) ]);
  1496. testLex(".2D" , [ Token(symbol!"Value",loc,Value(cast(double)0.2)) ]);
  1497. testLex(".2BD", [ Token(symbol!"Value",loc,Value(cast( real)0.2)) ]);
  1498.  
  1499. testLex("-1.2F" , [ Token(symbol!"Value",loc,Value(cast( float)-1.2)) ]);
  1500. testLex("-1.2" , [ Token(symbol!"Value",loc,Value(cast(double)-1.2)) ]);
  1501. testLex("-1.2D" , [ Token(symbol!"Value",loc,Value(cast(double)-1.2)) ]);
  1502. testLex("-1.2BD", [ Token(symbol!"Value",loc,Value(cast( real)-1.2)) ]);
  1503.  
  1504. testLex("-.2F" , [ Token(symbol!"Value",loc,Value(cast( float)-0.2)) ]);
  1505. testLex("-.2" , [ Token(symbol!"Value",loc,Value(cast(double)-0.2)) ]);
  1506. testLex("-.2D" , [ Token(symbol!"Value",loc,Value(cast(double)-0.2)) ]);
  1507. testLex("-.2BD", [ Token(symbol!"Value",loc,Value(cast( real)-0.2)) ]);
  1508.  
  1509. testLex( "0.0" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]);
  1510. testLex( "0.0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]);
  1511. testLex( "0.0BD", [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]);
  1512. testLex("-0.0" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]);
  1513. testLex("-0.0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]);
  1514. testLex("-0.0BD", [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]);
  1515. testLex( "7F" , [ Token(symbol!"Value",loc,Value(cast( float)7.0)) ]);
  1516. testLex( "7D" , [ Token(symbol!"Value",loc,Value(cast(double)7.0)) ]);
  1517. testLex( "7BD" , [ Token(symbol!"Value",loc,Value(cast( real)7.0)) ]);
  1518. testLex( "0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]);
  1519. testLex( "0D" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]);
  1520. testLex( "0BD" , [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]);
  1521. testLex("-0F" , [ Token(symbol!"Value",loc,Value(cast( float)0.0)) ]);
  1522. testLex("-0D" , [ Token(symbol!"Value",loc,Value(cast(double)0.0)) ]);
  1523. testLex("-0BD" , [ Token(symbol!"Value",loc,Value(cast( real)0.0)) ]);
  1524.  
  1525. testLex("1.2 F", [
  1526. Token(symbol!"Value",loc,Value(cast(double)1.2)),
  1527. Token(symbol!"Ident",loc,Value( null),"F"),
  1528. ]);
  1529. testLexThrows("1.2A");
  1530. testLexThrows("1.2B");
  1531. testLexThrows("1.2BDF");
  1532.  
  1533. testLex("1.2;", [
  1534. Token(symbol!"Value",loc,Value(cast(double)1.2)),
  1535. Token(symbol!"EOL",loc),
  1536. ]);
  1537.  
  1538. testLex("1.2F;", [
  1539. Token(symbol!"Value",loc,Value(cast(float)1.2)),
  1540. Token(symbol!"EOL",loc),
  1541. ]);
  1542.  
  1543. testLex("1.2BD;", [
  1544. Token(symbol!"Value",loc,Value(cast(real)1.2)),
  1545. Token(symbol!"EOL",loc),
  1546. ]);
  1547.  
  1548. // Booleans and null
  1549. testLex("true", [ Token(symbol!"Value",loc,Value( true)) ]);
  1550. testLex("false", [ Token(symbol!"Value",loc,Value(false)) ]);
  1551. testLex("on", [ Token(symbol!"Value",loc,Value( true)) ]);
  1552. testLex("off", [ Token(symbol!"Value",loc,Value(false)) ]);
  1553. testLex("null", [ Token(symbol!"Value",loc,Value( null)) ]);
  1554.  
  1555. testLex("TRUE", [ Token(symbol!"Ident",loc,Value(null),"TRUE") ]);
  1556. testLex("true ", [ Token(symbol!"Value",loc,Value(true)) ]);
  1557. testLex("true ", [ Token(symbol!"Value",loc,Value(true)) ]);
  1558. testLex("tru", [ Token(symbol!"Ident",loc,Value(null),"tru") ]);
  1559. testLex("truX", [ Token(symbol!"Ident",loc,Value(null),"truX") ]);
  1560. testLex("trueX", [ Token(symbol!"Ident",loc,Value(null),"trueX") ]);
  1561.  
  1562. // Raw Backtick Strings
  1563. testLex("`hello world`", [ Token(symbol!"Value",loc,Value(`hello world` )) ]);
  1564. testLex("` hello world `", [ Token(symbol!"Value",loc,Value(` hello world ` )) ]);
  1565. testLex("`hello \\t world`", [ Token(symbol!"Value",loc,Value(`hello \t world`)) ]);
  1566. testLex("`hello \\n world`", [ Token(symbol!"Value",loc,Value(`hello \n world`)) ]);
  1567. testLex("`hello \n world`", [ Token(symbol!"Value",loc,Value("hello \n world")) ]);
  1568. testLex("`hello \r\n world`", [ Token(symbol!"Value",loc,Value("hello \r\n world")) ]);
  1569. testLex("`hello \"world\"`", [ Token(symbol!"Value",loc,Value(`hello "world"` )) ]);
  1570.  
  1571. testLexThrows("`foo");
  1572. testLexThrows("`");
  1573.  
  1574. // Double-Quote Strings
  1575. testLex(`"hello world"`, [ Token(symbol!"Value",loc,Value("hello world" )) ]);
  1576. testLex(`" hello world "`, [ Token(symbol!"Value",loc,Value(" hello world " )) ]);
  1577. testLex(`"hello \t world"`, [ Token(symbol!"Value",loc,Value("hello \t world")) ]);
  1578. testLex(`"hello \n world"`, [ Token(symbol!"Value",loc,Value("hello \n world")) ]);
  1579. testLex("\"hello \\\n world\"", [ Token(symbol!"Value",loc,Value("hello world" )) ]);
  1580. testLex("\"hello \\ \n world\"", [ Token(symbol!"Value",loc,Value("hello world" )) ]);
  1581. testLex("\"hello \\ \n\n world\"", [ Token(symbol!"Value",loc,Value("hello world" )) ]);
  1582.  
  1583. testLexThrows("\"hello \n world\"");
  1584. testLexThrows(`"foo`);
  1585. testLexThrows(`"`);
  1586.  
  1587. // Characters
  1588. testLex("'a'", [ Token(symbol!"Value",loc,Value(cast(dchar) 'a')) ]);
  1589. testLex("'\\n'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\n')) ]);
  1590. testLex("'\\t'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\t')) ]);
  1591. testLex("'\t'", [ Token(symbol!"Value",loc,Value(cast(dchar)'\t')) ]);
  1592. testLex("'\\''", [ Token(symbol!"Value",loc,Value(cast(dchar)'\'')) ]);
  1593. testLex(`'\\'`, [ Token(symbol!"Value",loc,Value(cast(dchar)'\\')) ]);
  1594.  
  1595. testLexThrows("'a");
  1596. testLexThrows("'aa'");
  1597. testLexThrows("''");
  1598. testLexThrows("'\\\n'");
  1599. testLexThrows("'\n'");
  1600. testLexThrows(`'\`);
  1601. testLexThrows(`'\'`);
  1602. testLexThrows("'");
  1603. // Unicode
  1604. testLex("日本語", [ Token(symbol!"Ident",loc,Value(null), "日本語") ]);
  1605. testLex("`おはよう、日本。`", [ Token(symbol!"Value",loc,Value(`おはよう、日本。`)) ]);
  1606. testLex(`"おはよう、日本。"`, [ Token(symbol!"Value",loc,Value(`おはよう、日本。`)) ]);
  1607. testLex("'月'", [ Token(symbol!"Value",loc,Value("月"d.dup[0])) ]);
  1608.  
  1609. // Base64 Binary
  1610. testLex("[aGVsbG8gd29ybGQ=]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]);
  1611. testLex("[ aGVsbG8gd29ybGQ= ]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]);
  1612. testLex("[\n aGVsbG8g \n \n d29ybGQ= \n]", [ Token(symbol!"Value",loc,Value(cast(ubyte[])"hello world".dup))]);
  1613.  
  1614. testLexThrows("[aGVsbG8gd29ybGQ]"); // Ie: Not multiple of 4
  1615. testLexThrows("[ aGVsbG8gd29ybGQ ]");
  1616.  
  1617. // Date
  1618. testLex( "1999/12/5", [ Token(symbol!"Value",loc,Value(Date( 1999, 12, 5))) ]);
  1619. testLex( "2013/2/22", [ Token(symbol!"Value",loc,Value(Date( 2013, 2, 22))) ]);
  1620. testLex("-2013/2/22", [ Token(symbol!"Value",loc,Value(Date(-2013, 2, 22))) ]);
  1621.  
  1622. testLexThrows("7/");
  1623. testLexThrows("2013/2/22a");
  1624. testLexThrows("2013/2/22f");
  1625.  
  1626. testLex("1999/12/5\n", [
  1627. Token(symbol!"Value",loc,Value(Date(1999, 12, 5))),
  1628. Token(symbol!"EOL",loc),
  1629. ]);
  1630.  
  1631. // DateTime, no timezone
  1632. testLex( "2013/2/22 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]);
  1633. testLex( "2013/2/22 \t 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]);
  1634. testLex( "2013/2/22/*foo*/07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]);
  1635. testLex( "2013/2/22 /*foo*/ \\\n /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]);
  1636. testLex( "2013/2/22 /*foo*/ \\\n\n \n /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]);
  1637. testLex( "2013/2/22 /*foo*/ \\\n\\\n \\\n /*bar*/ 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]);
  1638. testLex( "2013/2/22/*foo*/\\\n/*bar*/07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0)))) ]);
  1639. testLex("-2013/2/22 07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime(-2013, 2, 22, 7, 53, 0)))) ]);
  1640. testLex( "2013/2/22 -07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53)))) ]);
  1641. testLex("-2013/2/22 -07:53", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53)))) ]);
  1642. testLex( "2013/2/22 07:53:34", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34)))) ]);
  1643. testLex( "2013/2/22 07:53:34.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123)))) ]);
  1644. testLex( "2013/2/22 07:53:34.12", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(120)))) ]);
  1645. testLex( "2013/2/22 07:53:34.1", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(100)))) ]);
  1646. testLex( "2013/2/22 07:53.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123)))) ]);
  1647.  
  1648. testLex( "2013/2/22 34:65", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0)))) ]);
  1649. testLex( "2013/2/22 34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds(77), FracSec.from!"msecs"(123)))) ]);
  1650. testLex( "2013/2/22 34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) + hours(34) + minutes(65) + seconds( 0), FracSec.from!"msecs"(123)))) ]);
  1651.  
  1652. testLex( "2013/2/22 -34:65", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0)))) ]);
  1653. testLex( "2013/2/22 -34:65:77.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds(77), FracSec.from!"msecs"(-123)))) ]);
  1654. testLex( "2013/2/22 -34:65.123", [ Token(symbol!"Value",loc,Value(DateTimeFrac(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), FracSec.from!"msecs"(-123)))) ]);
  1655.  
  1656. testLexThrows("2013/2/22 07:53a");
  1657. testLexThrows("2013/2/22 07:53f");
  1658. testLexThrows("2013/2/22 07:53:34.123a");
  1659. testLexThrows("2013/2/22 07:53:34.123f");
  1660. testLexThrows("2013/2/22a 07:53");
  1661.  
  1662. testLex(`2013/2/22 "foo"`, [
  1663. Token(symbol!"Value",loc,Value(Date(2013, 2, 22))),
  1664. Token(symbol!"Value",loc,Value("foo")),
  1665. ]);
  1666.  
  1667. testLex("2013/2/22 07", [
  1668. Token(symbol!"Value",loc,Value(Date(2013, 2, 22))),
  1669. Token(symbol!"Value",loc,Value(cast(int)7)),
  1670. ]);
  1671.  
  1672. testLex("2013/2/22 1.2F", [
  1673. Token(symbol!"Value",loc,Value(Date(2013, 2, 22))),
  1674. Token(symbol!"Value",loc,Value(cast(float)1.2)),
  1675. ]);
  1676.  
  1677. testLex("2013/2/22 .2F", [
  1678. Token(symbol!"Value",loc,Value(Date(2013, 2, 22))),
  1679. Token(symbol!"Value",loc,Value(cast(float)0.2)),
  1680. ]);
  1681.  
  1682. testLex("2013/2/22 -1.2F", [
  1683. Token(symbol!"Value",loc,Value(Date(2013, 2, 22))),
  1684. Token(symbol!"Value",loc,Value(cast(float)-1.2)),
  1685. ]);
  1686.  
  1687. testLex("2013/2/22 -.2F", [
  1688. Token(symbol!"Value",loc,Value(Date(2013, 2, 22))),
  1689. Token(symbol!"Value",loc,Value(cast(float)-0.2)),
  1690. ]);
  1691.  
  1692. // DateTime, with known timezone
  1693. testLex( "2013/2/22 07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone( hours(0) )))) ]);
  1694. testLex("-2013/2/22 07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime(-2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone( hours(0) )))) ]);
  1695. testLex( "2013/2/22 -07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), new immutable SimpleTimeZone( hours(0) )))) ]);
  1696. testLex("-2013/2/22 -07:53-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), new immutable SimpleTimeZone( hours(0) )))) ]);
  1697. testLex( "2013/2/22 07:53-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]);
  1698. testLex( "2013/2/22 07:53-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]);
  1699. testLex( "2013/2/22 07:53:34-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(0) )))) ]);
  1700. testLex( "2013/2/22 07:53:34-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]);
  1701. testLex( "2013/2/22 07:53:34-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]);
  1702. testLex( "2013/2/22 07:53:34.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123), new immutable SimpleTimeZone( hours(0) )))) ]);
  1703. testLex( "2013/2/22 07:53:34.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]);
  1704. testLex( "2013/2/22 07:53:34.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]);
  1705. testLex( "2013/2/22 07:53.123-GMT+00:00", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123), new immutable SimpleTimeZone( hours(0) )))) ]);
  1706. testLex( "2013/2/22 07:53.123-GMT+02:10", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123), new immutable SimpleTimeZone( hours(2)+minutes(10))))) ]);
  1707. testLex( "2013/2/22 07:53.123-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]);
  1708.  
  1709. testLex( "2013/2/22 -34:65-GMT-05:30", [ Token(symbol!"Value",loc,Value(SysTime(DateTime( 2013, 2, 22, 0, 0, 0) - hours(34) - minutes(65) - seconds( 0), new immutable SimpleTimeZone(-hours(5)-minutes(30))))) ]);
  1710.  
  1711. // DateTime, with Java SDL's occasionally weird interpretation of some
  1712. // "not quite ISO" variations of the "GMT with offset" timezone strings.
  1713. Token testTokenSimpleTimeZone(Duration d)
  1714. {
  1715. auto dateTime = DateTime(2013, 2, 22, 7, 53, 0);
  1716. auto tz = new immutable SimpleTimeZone(d);
  1717. return Token( symbol!"Value", loc, Value(SysTime(dateTime,tz)) );
  1718. }
  1719. Token testTokenUnknownTimeZone(string tzName)
  1720. {
  1721. auto dateTime = DateTime(2013, 2, 22, 7, 53, 0);
  1722. auto frac = FracSec.from!"msecs"(0);
  1723. return Token( symbol!"Value", loc, Value(DateTimeFracUnknownZone(dateTime,frac,tzName)) );
  1724. }
  1725. testLex("2013/2/22 07:53-GMT+", [ testTokenUnknownTimeZone("GMT+") ]);
  1726. testLex("2013/2/22 07:53-GMT+:", [ testTokenUnknownTimeZone("GMT+:") ]);
  1727. testLex("2013/2/22 07:53-GMT+:3", [ testTokenUnknownTimeZone("GMT+:3") ]);
  1728. testLex("2013/2/22 07:53-GMT+:03", [ testTokenSimpleTimeZone(minutes(3)) ]);
  1729. testLex("2013/2/22 07:53-GMT+:003", [ testTokenUnknownTimeZone("GMT+:003") ]);
  1730.  
  1731. testLex("2013/2/22 07:53-GMT+4", [ testTokenSimpleTimeZone(hours(4)) ]);
  1732. testLex("2013/2/22 07:53-GMT+4:", [ testTokenUnknownTimeZone("GMT+4:") ]);
  1733. testLex("2013/2/22 07:53-GMT+4:3", [ testTokenUnknownTimeZone("GMT+4:3") ]);
  1734. testLex("2013/2/22 07:53-GMT+4:03", [ testTokenSimpleTimeZone(hours(4)+minutes(3)) ]);
  1735. testLex("2013/2/22 07:53-GMT+4:003", [ testTokenUnknownTimeZone("GMT+4:003") ]);
  1736.  
  1737. testLex("2013/2/22 07:53-GMT+04", [ testTokenSimpleTimeZone(hours(4)) ]);
  1738. testLex("2013/2/22 07:53-GMT+04:", [ testTokenUnknownTimeZone("GMT+04:") ]);
  1739. testLex("2013/2/22 07:53-GMT+04:3", [ testTokenUnknownTimeZone("GMT+04:3") ]);
  1740. testLex("2013/2/22 07:53-GMT+04:03", [ testTokenSimpleTimeZone(hours(4)+minutes(3)) ]);
  1741. testLex("2013/2/22 07:53-GMT+04:03abc", [ testTokenUnknownTimeZone("GMT+04:03abc") ]);
  1742. testLex("2013/2/22 07:53-GMT+04:003", [ testTokenUnknownTimeZone("GMT+04:003") ]);
  1743.  
  1744. testLex("2013/2/22 07:53-GMT+004", [ testTokenSimpleTimeZone(minutes(4)) ]);
  1745. testLex("2013/2/22 07:53-GMT+004:", [ testTokenUnknownTimeZone("GMT+004:") ]);
  1746. testLex("2013/2/22 07:53-GMT+004:3", [ testTokenUnknownTimeZone("GMT+004:3") ]);
  1747. testLex("2013/2/22 07:53-GMT+004:03", [ testTokenUnknownTimeZone("GMT+004:03") ]);
  1748. testLex("2013/2/22 07:53-GMT+004:003", [ testTokenUnknownTimeZone("GMT+004:003") ]);
  1749.  
  1750. testLex("2013/2/22 07:53-GMT+0004", [ testTokenSimpleTimeZone(minutes(4)) ]);
  1751. testLex("2013/2/22 07:53-GMT+0004:", [ testTokenUnknownTimeZone("GMT+0004:") ]);
  1752. testLex("2013/2/22 07:53-GMT+0004:3", [ testTokenUnknownTimeZone("GMT+0004:3") ]);
  1753. testLex("2013/2/22 07:53-GMT+0004:03", [ testTokenUnknownTimeZone("GMT+0004:03") ]);
  1754. testLex("2013/2/22 07:53-GMT+0004:003", [ testTokenUnknownTimeZone("GMT+0004:003") ]);
  1755.  
  1756. testLex("2013/2/22 07:53-GMT+00004", [ testTokenSimpleTimeZone(minutes(4)) ]);
  1757. testLex("2013/2/22 07:53-GMT+00004:", [ testTokenUnknownTimeZone("GMT+00004:") ]);
  1758. testLex("2013/2/22 07:53-GMT+00004:3", [ testTokenUnknownTimeZone("GMT+00004:3") ]);
  1759. testLex("2013/2/22 07:53-GMT+00004:03", [ testTokenUnknownTimeZone("GMT+00004:03") ]);
  1760. testLex("2013/2/22 07:53-GMT+00004:003", [ testTokenUnknownTimeZone("GMT+00004:003") ]);
  1761.  
  1762. // DateTime, with unknown timezone
  1763. testLex( "2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"( 0), "Bogus/Foo")), "2013/2/22 07:53-Bogus/Foo") ]);
  1764. testLex("-2013/2/22 07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"( 0), "Bogus/Foo"))) ]);
  1765. testLex( "2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), FracSec.from!"msecs"( 0), "Bogus/Foo"))) ]);
  1766. testLex("-2013/2/22 -07:53-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime(-2013, 2, 22, 0, 0, 0) - hours(7) - minutes(53), FracSec.from!"msecs"( 0), "Bogus/Foo"))) ]);
  1767. testLex( "2013/2/22 07:53:34-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"( 0), "Bogus/Foo"))) ]);
  1768. testLex( "2013/2/22 07:53:34.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 34), FracSec.from!"msecs"(123), "Bogus/Foo"))) ]);
  1769. testLex( "2013/2/22 07:53.123-Bogus/Foo", [ Token(symbol!"Value",loc,Value(DateTimeFracUnknownZone(DateTime( 2013, 2, 22, 7, 53, 0), FracSec.from!"msecs"(123), "Bogus/Foo"))) ]);
  1770.  
  1771. // Time Span
  1772. testLex( "12:14:42", [ Token(symbol!"Value",loc,Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs( 0))) ]);
  1773. testLex("-12:14:42", [ Token(symbol!"Value",loc,Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs( 0))) ]);
  1774. testLex( "00:09:12", [ Token(symbol!"Value",loc,Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs( 0))) ]);
  1775. testLex( "00:00:01.023", [ Token(symbol!"Value",loc,Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23))) ]);
  1776. testLex( "23d:05:21:23.532", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532))) ]);
  1777. testLex( "23d:05:21:23.53", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530))) ]);
  1778. testLex( "23d:05:21:23.5", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500))) ]);
  1779. testLex("-23d:05:21:23.532", [ Token(symbol!"Value",loc,Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532))) ]);
  1780. testLex("-23d:05:21:23.5", [ Token(symbol!"Value",loc,Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500))) ]);
  1781. testLex( "23d:05:21:23", [ Token(symbol!"Value",loc,Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs( 0))) ]);
  1782.  
  1783. testLexThrows("12:14:42a");
  1784. testLexThrows("23d:05:21:23.532a");
  1785. testLexThrows("23d:05:21:23.532f");
  1786.  
  1787. // Combination
  1788. testLex("foo. 7", [
  1789. Token(symbol!"Ident",loc,Value( null),"foo."),
  1790. Token(symbol!"Value",loc,Value(cast(int)7))
  1791. ]);
  1792. testLex(`
  1793. namespace:person "foo" "bar" 1 23L name.first="ひとみ" name.last="Smith" {
  1794. namespace:age 37; namespace:favorite_color "blue" // comment
  1795. somedate 2013/2/22 07:53 -- comment
  1796. inventory /* comment */ {
  1797. socks
  1798. }
  1799. }
  1800. `,
  1801. [
  1802. Token(symbol!"EOL",loc,Value(null),"\n"),
  1803.  
  1804. Token(symbol!"Ident", loc, Value( null ), "namespace"),
  1805. Token(symbol!":", loc, Value( null ), ":"),
  1806. Token(symbol!"Ident", loc, Value( null ), "person"),
  1807. Token(symbol!"Value", loc, Value( "foo" ), `"foo"`),
  1808. Token(symbol!"Value", loc, Value( "bar" ), `"bar"`),
  1809. Token(symbol!"Value", loc, Value( cast( int) 1 ), "1"),
  1810. Token(symbol!"Value", loc, Value( cast(long)23 ), "23L"),
  1811. Token(symbol!"Ident", loc, Value( null ), "name.first"),
  1812. Token(symbol!"=", loc, Value( null ), "="),
  1813. Token(symbol!"Value", loc, Value( "ひとみ" ), `"ひとみ"`),
  1814. Token(symbol!"Ident", loc, Value( null ), "name.last"),
  1815. Token(symbol!"=", loc, Value( null ), "="),
  1816. Token(symbol!"Value", loc, Value( "Smith" ), `"Smith"`),
  1817. Token(symbol!"{", loc, Value( null ), "{"),
  1818. Token(symbol!"EOL", loc, Value( null ), "\n"),
  1819.  
  1820. Token(symbol!"Ident", loc, Value( null ), "namespace"),
  1821. Token(symbol!":", loc, Value( null ), ":"),
  1822. Token(symbol!"Ident", loc, Value( null ), "age"),
  1823. Token(symbol!"Value", loc, Value( cast(int)37 ), "37"),
  1824. Token(symbol!"EOL", loc, Value( null ), ";"),
  1825. Token(symbol!"Ident", loc, Value( null ), "namespace"),
  1826. Token(symbol!":", loc, Value( null ), ":"),
  1827. Token(symbol!"Ident", loc, Value( null ), "favorite_color"),
  1828. Token(symbol!"Value", loc, Value( "blue" ), `"blue"`),
  1829.  
  1830. Token(symbol!"Ident", loc, Value( null ), "somedate"),
  1831. Token(symbol!"Value", loc, Value( DateTimeFrac(DateTime(2013, 2, 22, 7, 53, 0)) ), "2013/2/22 07:53"),
  1832. Token(symbol!"EOL", loc, Value( null ), "\n"),
  1833.  
  1834. Token(symbol!"Ident", loc, Value(null), "inventory"),
  1835. Token(symbol!"{", loc, Value(null), "{"),
  1836. Token(symbol!"EOL", loc, Value(null), "\n"),
  1837.  
  1838. Token(symbol!"Ident", loc, Value(null), "socks"),
  1839. Token(symbol!"EOL", loc, Value(null), "\n"),
  1840.  
  1841. Token(symbol!"}", loc, Value(null), "}"),
  1842. Token(symbol!"EOL", loc, Value(null), "\n"),
  1843.  
  1844. Token(symbol!"}", loc, Value(null), "}"),
  1845. Token(symbol!"EOL", loc, Value(null), "\n"),
  1846. ]);
  1847. if(numErrors > 0)
  1848. stderr.writeln(numErrors, " failed test(s)");
  1849. }
  1850.  
  1851. version(sdlangUnittest)
  1852. unittest
  1853. {
  1854. writeln("lexer: Regression test issue #8...");
  1855. stdout.flush();
  1856.  
  1857. testLex(`"\n \n"`, [ Token(symbol!"Value",loc,Value("\n \n"),`"\n \n"`) ]);
  1858. testLex(`"\t\t"`, [ Token(symbol!"Value",loc,Value("\t\t"),`"\t\t"`) ]);
  1859. testLex(`"\n\n"`, [ Token(symbol!"Value",loc,Value("\n\n"),`"\n\n"`) ]);
  1860. }
  1861.  
  1862. version(sdlangUnittest)
  1863. unittest
  1864. {
  1865. writeln("lexer: Regression test issue #11...");
  1866. stdout.flush();
  1867. testLex("//X\na", [ Token(symbol!"Ident",loc,Value(null),"a") ]);
  1868. testLex("//\na", [ Token(symbol!"Ident",loc,Value(null),"a") ]);
  1869. testLex("--\na", [ Token(symbol!"Ident",loc,Value(null),"a") ]);
  1870. testLex("#\na", [ Token(symbol!"Ident",loc,Value(null),"a") ]);
  1871. }