1 module calcool.lexer; 2 import std.stdio; 3 import std.ascii; 4 import calcool.token; 5 import calcool.exceptions; 6 import std.conv : to; 7 8 public class Lexer { 9 private: 10 File input; 11 12 uint pos = 0; 13 string line; 14 public: 15 this(File f) { 16 input = f; 17 } 18 19 this() { 20 this(stdin); 21 } 22 23 Token[] nextLine(in string stringInput = null) { 24 if (stringInput is null) { 25 if (input is stdin) { 26 version (Posix) { 27 import core.sys.posix.unistd : isatty; 28 29 if (isatty(stdin.fileno)) 30 write(">> "); 31 } else { 32 write(">> "); 33 } 34 } 35 36 line = input.readln(); 37 if (line is null) { 38 return []; 39 } 40 } else { 41 line = stringInput; // ~ '\n'; 42 } 43 pos = 0; 44 Token[] list; 45 for (auto t = next(); t.type != TokenType.EOL; t = next()) { 46 list ~= t; 47 } 48 list ~= Token(TokenType.EOL); 49 return list; 50 } 51 52 Token next() { 53 skipWhiteSpace(); 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 const identifier = name(); 62 if (identifier == "set") { 63 return Token(TokenType.SET_VAR, identifier); 64 } 65 skipWhiteSpace(); 66 if (!eol() && peek() == '(') { 67 return Token(TokenType.FUNC, identifier); 68 } 69 return Token(TokenType.IDENTIFIER, identifier); 70 } 71 pos++; 72 73 const value = ch.to!string; 74 switch (ch) { 75 case '(': 76 return Token(TokenType.PR_OPEN, value); 77 case ')': 78 return Token(TokenType.PR_CLOSE, value); 79 case '+': 80 return Token(TokenType.OP_ADD, value); 81 case '-': 82 return Token(TokenType.OP_MINUS, value); 83 case '*': 84 return Token(TokenType.OP_MULT, value); 85 case '/': 86 return Token(TokenType.OP_DIV, value); 87 case '^': 88 return Token(TokenType.OP_POW, value); 89 case '=': 90 return Token(TokenType.EQUALS, value); 91 default: 92 throw new UnsupportedTokenException(ch); 93 } 94 } 95 96 private: 97 pragma(inline, true) { 98 auto eol() pure nothrow const { 99 return line.length == 0 || pos >= line.length; 100 } 101 102 auto skipWhiteSpace() { 103 while (!eol() && isWhite(peek())) 104 pos++; 105 } 106 107 auto ref peek() { 108 return line[pos]; 109 } 110 } 111 112 auto name() { 113 const start = pos; 114 while (!eol() && (isAlpha(peek()) || isDigit(peek()))) 115 pos++; 116 return line[start .. pos]; 117 } 118 119 string number() { 120 const start = pos; 121 bool isFloat = false; 122 bool shouldContinue = true; 123 124 while (shouldContinue) { 125 while (!eol() && isDigit(peek())) { 126 pos++; 127 } 128 if (!eol() && peek() == '.') { 129 if (!isFloat) { 130 isFloat = true; 131 pos++; 132 } else { 133 throw new LexerException("Invalid number"); 134 } 135 } else { 136 shouldContinue = false; 137 } 138 } 139 140 if (!eol() && peek() == 'e') { 141 pos++; 142 if (!eol() && (peek() == '+' || peek() == '-')) { 143 pos++; 144 } 145 if (!eol() && !isDigit(peek())) { 146 throw new LexerException("Invalid number"); 147 } 148 while (!eol() && isDigit(peek())) { 149 pos++; 150 } 151 } 152 153 if (isFloat && (pos - start) == 1) 154 throw new LexerException("Point is not a number"); 155 return line[start .. pos]; 156 } 157 }