1 module calcool.parser; 2 3 import std.stdio; 4 import std.array : front, popFront; 5 6 import calcool.lexer; 7 import calcool.token; 8 import calcool.expression; 9 import calcool.parselets; 10 import calcool.exceptions; 11 12 public class Parser { 13 14 private: 15 real[string] variables; 16 Lexer lexer; 17 Token[] input; 18 static const syntaxError = new ParseException("Syntax error"); 19 20 static immutable { 21 PrefixParselet[TokenType] prefixParselets; 22 InfixParselet[TokenType] infixParselets; 23 } 24 25 shared static this() { 26 prefixParselets[TokenType.NUMBER] = new NumberParselet(); 27 prefixParselets[TokenType.IDENTIFIER] = new IdentifierParselet(); 28 prefixParselets[TokenType.OP_MINUS] = new NegateParselet(); 29 prefixParselets[TokenType.FUNC] = new FuncParselet(); 30 prefixParselets[TokenType.PR_OPEN] = new GroupParselet(); 31 prefixParselets[TokenType.EOL] = new EolParselet(); 32 33 infixParselets[TokenType.OP_ADD] = infixParselets[TokenType.OP_MINUS] = new AddMinusParselet(); 34 infixParselets[TokenType.OP_MULT] = infixParselets[TokenType.OP_DIV] = new MultDivParselet(); 35 infixParselets[TokenType.OP_POW] = new PowerParselet(); 36 } 37 38 public: 39 this() { 40 lexer = new Lexer(); 41 } 42 43 this(File f) { 44 lexer = new Lexer(f); 45 } 46 47 void setInput(File f) { 48 lexer = new Lexer(f); 49 input.length = 0; 50 } 51 52 const auto ref getVariables() { 53 return variables; 54 } 55 56 void setVariable(string name, real value) { 57 import std.algorithm : canFind; 58 59 if (FuncExpression.funcNames().canFind(name)) { 60 error("Cannot use reserved function name '%s' as a variable", name); 61 } 62 variables[name] = value; 63 } 64 65 private Token consume() { 66 if (input.length == 0) { 67 input = lexer.nextLine(); 68 if (input.length == 0) { 69 throw new EofException(); 70 } 71 } 72 const f = input.front(); 73 input.popFront(); 74 return f; 75 } 76 77 Expression parseGroupExpression() { 78 auto inside = parseExpression(Precedence.START); 79 expect(TokenType.PR_CLOSE); 80 return inside; 81 } 82 83 Expression parseExpression() { 84 return parseExpression(Precedence.START, true); 85 } 86 87 Expression parseExpression(const Precedence precedence = Precedence.START, bool start = false) { 88 auto token = consume(); 89 while (start && token.type == TokenType.EOL) { 90 token = consume(); 91 } 92 93 if (start && token.type == TokenType.SET_VAR) { 94 const variable = expect(TokenType.IDENTIFIER); 95 expect(TokenType.EQUALS); 96 if (input.front().type == TokenType.EOL) { 97 error(); 98 } 99 auto expr = parseExpression(Precedence.START, true); 100 setVariable(variable.value, expr.evaluate()); 101 return expr; 102 } 103 104 if (auto parselet = token.type in prefixParselets) { 105 auto left = parselet.parse(this, token); 106 107 while (precedence < getPrecedence()) { 108 token = consume(); 109 immutable infix = infixParselets[token.type]; 110 left = infix.parse(this, left, token); 111 } 112 113 if (start && input.length > 0 && consume().type != TokenType.EOL) { 114 error(); 115 } 116 return left; 117 118 } else { 119 error(); 120 } 121 assert(false); 122 } 123 124 private Precedence getPrecedence() { 125 if (input.length > 0) { 126 const t = input.front().type; 127 if (t == TokenType.PR_CLOSE || t == TokenType.EOL) { 128 return Precedence.START; 129 } 130 131 if (auto parselet = t in infixParselets) { 132 return parselet.getPrecedence(); 133 } else { 134 error(); 135 } 136 } 137 138 return Precedence.START; 139 } 140 141 private noreturn error(const ParseException exception = syntaxError) { 142 input.length = 0; 143 throw exception; 144 } 145 146 private noreturn error(Args...)(const string msg, Args args) { 147 import std.format : format; 148 149 return error(new ParseException(format(msg, args))); 150 } 151 152 auto expect(const TokenType t) { 153 if ((input.length > 0 && input.front().type != t) || input.length == 0) { 154 error("Expected token of type %s", t); 155 } 156 const res = input.front(); 157 input.popFront(); 158 return res; 159 } 160 161 string evaluateFromString(in string exp) { 162 import std.conv : to; 163 164 input = lexer.nextLine(exp); 165 return parseExpression(Precedence.START, true).evaluate().to!string; 166 } 167 168 }