1 module calcool.lexer; 2 import std.stdio; 3 import std.ascii; 4 import calcool.token; 5 import calcool.exceptions; 6 7 public class Lexer { 8 private: 9 File input; 10 11 uint pos = 0; 12 string line; 13 public: 14 this(File f) { 15 input = f; 16 } 17 18 this() { 19 this(stdin); 20 } 21 22 Token[] nextLine(in string stringInput = null) { 23 if (stringInput is null) { 24 if (input is stdin) { 25 version (Posix) { 26 import core.sys.posix.unistd : isatty; 27 28 if (isatty(stdin.fileno)) 29 write(">> "); 30 } else { 31 write(">> "); 32 } 33 } 34 35 line = input.readln(); 36 if (line is null) { 37 return []; 38 } 39 } else { 40 line = stringInput; // ~ '\n'; 41 } 42 pos = 0; 43 Token[] list; 44 for (auto t = next(); t.type != TokenType.EOL; t = next()) { 45 list ~= t; 46 } 47 list ~= Token(TokenType.EOL); 48 return list; 49 } 50 51 Token next() { 52 while (!eol() && isWhite(peek())) 53 pos++; 54 if (eol()) { 55 return Token(TokenType.EOL); 56 } 57 const ch = peek(); 58 if (isDigit(ch) || ch == '.') { 59 return Token(TokenType.NUMBER, number()); 60 } else if (isAlpha(ch)) 61 return Token(TokenType.FUNC, name()); 62 pos++; 63 switch (ch) { 64 import std.conv : to; 65 66 case '(': 67 return Token(TokenType.PR_OPEN, ch.to!string); 68 case ')': 69 return Token(TokenType.PR_CLOSE, ch.to!string); 70 case '+': 71 return Token(TokenType.OP_ADD, ch.to!string); 72 case '-': 73 return Token(TokenType.OP_MINUS, ch.to!string); 74 case '*': 75 return Token(TokenType.OP_MULT, ch.to!string); 76 case '/': 77 return Token(TokenType.OP_DIV, ch.to!string); 78 case '^': 79 return Token(TokenType.OP_POW, ch.to!string); 80 default: 81 throw new UnsupportedTokenException(ch); 82 } 83 } 84 85 private: 86 pragma(inline, true) { 87 auto eol() pure nothrow const { 88 return line.length == 0 || pos >= line.length; 89 } 90 91 auto ref peek() { 92 return line[pos]; 93 } 94 } 95 96 auto name() { 97 const start = pos; 98 while (!eol() && (isAlpha(peek()) || isDigit(peek()))) 99 pos++; 100 return line[start .. pos]; 101 } 102 103 string number() { 104 const start = pos; 105 bool isFloat = false; 106 read_digits: 107 while (!eol() && isDigit(peek())) { 108 pos++; 109 } 110 if (!eol() && peek() == '.') { 111 if (!isFloat) { 112 isFloat = true; 113 pos++; 114 goto read_digits; 115 } else { 116 throw new LexerException("Unknown number passed"); 117 } 118 } 119 120 if (isFloat && (pos - start) == 1) 121 throw new LexerException("Point is not a number"); 122 return line[start .. pos]; 123 } 124 }