Last active
August 13, 2021 09:21
-
-
Save iemelyanov/428fda12271af077c2c467ed001ce658 to your computer and use it in GitHub Desktop.
Impl simple calc for play with C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
======= | |
Grammar | |
======= | |
Syntax | |
------ | |
expression → literal | |
| unary | |
| binary | |
| grouping ; | |
literal → NUMBER ; | |
grouping → "(" expression ")" ; | |
unary → ( "-" ) expression ; | |
binary → expression operator expression ; | |
operator → "+" | "-" | "*" | "/" ; | |
Precedence | |
---------- | |
expr → term | |
term → factor ( ( "-" | "+" ) factor )* | |
factor → unary ( ( "/" | "*" ) unary )* | |
unary → ( "-" ) unary | primary | |
primary → NUMBER | "(" expr ")" | |
*/ | |
#include <cstdint> | |
#include <cstring> | |
#include <iostream> | |
#include <memory> | |
#include <string> | |
#include <vector> | |
namespace token { | |
enum class Kind { | |
LParen, | |
RParen, | |
Add, | |
Sub, | |
Mul, | |
Div, | |
Num, | |
Eof, | |
}; | |
struct Token { | |
Kind kind; | |
double value; | |
}; | |
} // namespace token | |
namespace scanner { | |
using namespace token; | |
class Scanner { | |
const std::string &source; | |
uint32_t start = 0; | |
uint32_t current = 0; | |
std::vector<Token> tokens; | |
public: | |
static std::vector<Token> scanTokens(const std::string &source) { | |
Scanner scanner(source); | |
return scanner.scanTokens(); | |
} | |
private: | |
Scanner(const std::string &source) : source{source} {} | |
bool isAtEnd() const { | |
return current >= source.size(); | |
} | |
char advance() { | |
char c = source[current]; | |
current++; | |
return c; | |
} | |
char peek() const { | |
return isAtEnd() ? '\0' : source[current]; | |
} | |
char peekNext() const { | |
if (current + 1 >= source.size()) | |
return '\0'; | |
return source[current + 1]; | |
} | |
std::vector<Token> scanTokens() { | |
while (!isAtEnd()) { | |
start = current; | |
char c = advance(); | |
switch (c) { | |
case '-': | |
tokens.push_back(Token{.kind = Kind::Sub}); | |
break; | |
case '+': | |
tokens.push_back(Token{.kind = Kind::Add}); | |
break; | |
case '/': | |
tokens.push_back(Token{.kind = Kind::Div}); | |
break; | |
case '*': | |
tokens.push_back(Token{.kind = Kind::Mul}); | |
break; | |
case '(': | |
tokens.push_back(Token{.kind = Kind::LParen}); | |
break; | |
case ')': | |
tokens.push_back(Token{.kind = Kind::RParen}); | |
break; | |
case ' ': | |
continue; | |
case '\n': | |
break; | |
default: | |
if (isdigit(c)) { | |
while (isdigit(peek())) | |
advance(); | |
if (peek() == '.' && isdigit(peekNext())) { | |
advance(); | |
while (isdigit(peek())) | |
advance(); | |
} | |
tokens.push_back(Token{ | |
.kind = Kind::Num, | |
.value = atof(source.data() + start), | |
}); | |
} else { | |
std::cerr << "Unexpected character\n"; | |
exit(1); | |
} | |
} | |
} | |
tokens.push_back(Token{.kind = token::Kind::Eof}); | |
return std::move(tokens); | |
} | |
}; | |
} // namespace scanner | |
namespace ast { | |
class Expr { | |
public: | |
virtual ~Expr() {} | |
virtual std::string toString() const = 0; | |
virtual double eval() const = 0; | |
}; | |
class Literal final : public Expr { | |
double value; | |
public: | |
Literal(double value) : value{value} {} | |
~Literal() {} | |
std::string toString() const { | |
return std::to_string(value); | |
} | |
double eval() const { | |
return value; | |
} | |
}; | |
class Binary final : public Expr { | |
const token::Token &op; | |
const Expr &lhs; | |
const Expr &rhs; | |
public: | |
Binary(const token::Token &op, const Expr &lhs, const Expr &rhs) | |
: op{op}, lhs{lhs}, rhs{rhs} {} | |
~Binary() {} | |
std::string toString() const { | |
std::string opStr; | |
switch (op.kind) { | |
case token::Kind::Add: | |
opStr = "+"; | |
break; | |
case token::Kind::Sub: | |
opStr = "-"; | |
break; | |
case token::Kind::Mul: | |
opStr = "*"; | |
break; | |
case token::Kind::Div: | |
opStr = "/"; | |
break; | |
default: | |
break; | |
} | |
return "(" + opStr + " " + lhs.toString() + " " + rhs.toString() + ")"; | |
} | |
double eval() const { | |
switch (op.kind) { | |
case token::Kind::Add: | |
return lhs.eval() + rhs.eval(); | |
case token::Kind::Sub: | |
return lhs.eval() - rhs.eval(); | |
case token::Kind::Mul: | |
return lhs.eval() * rhs.eval(); | |
case token::Kind::Div: | |
return lhs.eval() / rhs.eval(); | |
default: | |
break; | |
} | |
return 0; | |
} | |
}; | |
class Unary final : public Expr { | |
const token::Token &op; | |
const Expr &rhs; | |
public: | |
Unary(const token::Token &op, const Expr &rhs) : op{op}, rhs{rhs} {} | |
~Unary() {} | |
std::string toString() const { | |
if (op.kind == token::Kind::Sub) | |
return "( - " + rhs.toString() + ")"; | |
return rhs.toString(); | |
} | |
double eval() const { | |
if (op.kind == token::Kind::Sub) | |
return -rhs.eval(); | |
return rhs.eval(); | |
} | |
}; | |
class Group final : public Expr { | |
const Expr &expr; | |
public: | |
Group(const Expr &expr) : expr{expr} {} | |
~Group() {} | |
std::string toString() const { | |
return "(" + expr.toString() + ")"; | |
} | |
double eval() const { | |
return expr.eval(); | |
} | |
}; | |
} // namespace ast | |
namespace parser { | |
class Result { | |
std::vector<std::unique_ptr<ast::Expr>> exprs; | |
public: | |
static Result ok(std::vector<std::unique_ptr<ast::Expr>> exprs) { | |
return Result(std::move(exprs)); | |
} | |
static Result empty() { | |
return Result(); | |
} | |
bool isEmpty() const { | |
return exprs.size() == 0; | |
} | |
ast::Expr *expr() { | |
if (exprs.size() == 0) | |
return nullptr; | |
return exprs[exprs.size() - 1].get(); | |
} | |
private: | |
Result() : exprs() {} | |
Result(std::vector<std::unique_ptr<ast::Expr>> &&exprs) : exprs{std::move(exprs)} {} | |
}; | |
class Parser { | |
std::vector<std::unique_ptr<ast::Expr>> exprs; | |
const std::vector<token::Token> &tokens; | |
unsigned int current; | |
public: | |
static Result parse(const std::vector<token::Token> &tokens) { | |
if (tokens.size() == 0) | |
return Result::empty(); | |
Parser parser(tokens); | |
return Result::ok(parser.parse()); | |
} | |
private: | |
Parser(const std::vector<token::Token> &tokens) : tokens{tokens}, current{0} {} | |
std::vector<std::unique_ptr<ast::Expr>> parse() { | |
expression(); | |
return std::move(exprs); | |
} | |
const token::Token &peek() const { | |
return tokens[current]; | |
} | |
const token::Token &previous() const { | |
return tokens[current - 1]; | |
} | |
bool isAtEnd() const { | |
return peek().kind == token::Kind::Eof; | |
} | |
const token::Token &advance() { | |
if (!isAtEnd()) | |
current++; | |
return previous(); | |
} | |
bool check(token::Kind kind) const { | |
if (isAtEnd()) | |
return false; | |
return peek().kind == kind; | |
} | |
bool match(const std::vector<token::Kind> &tokenKinds) { | |
for (auto kind : tokenKinds) { | |
if (check(kind)) { | |
advance(); | |
return true; | |
} | |
} | |
return false; | |
} | |
const token::Token &consume(token::Kind kind) { | |
if (check(kind)) | |
return advance(); | |
std::cerr << "Error parse <consume>\n"; | |
exit(1); | |
} | |
ast::Expr *expression() { | |
return term(); | |
} | |
ast::Expr *term() { | |
auto *expr = factor(); | |
while (match({token::Kind::Sub, token::Kind::Add})) { | |
auto &op = previous(); | |
auto *right = factor(); | |
exprs.push_back(std::make_unique<ast::Binary>(op, *expr, *right)); | |
expr = exprs[exprs.size() - 1].get(); | |
} | |
return expr; | |
} | |
ast::Expr *factor() { | |
auto *expr = unary(); | |
while (match({token::Kind::Div, token::Kind::Mul})) { | |
auto &op = previous(); | |
auto *right = unary(); | |
exprs.push_back(std::make_unique<ast::Binary>(op, *expr, *right)); | |
expr = exprs[exprs.size() - 1].get(); | |
} | |
return expr; | |
} | |
ast::Expr *unary() { | |
if (match({token::Kind::Sub})) { | |
auto &op = previous(); | |
auto *right = unary(); | |
exprs.push_back(std::make_unique<ast::Unary>(op, *right)); | |
return exprs[exprs.size() - 1].get(); | |
} | |
return primary(); | |
} | |
ast::Expr *primary() { | |
if (match({token::Kind::Num})) { | |
exprs.push_back(std::make_unique<ast::Literal>(previous().value)); | |
return exprs[exprs.size() - 1].get(); | |
} | |
if (match({token::Kind::LParen})) { | |
auto *expr = expression(); | |
consume(token::Kind::RParen); | |
exprs.push_back(std::make_unique<ast::Group>(*expr)); | |
return exprs[exprs.size() - 1].get(); | |
} | |
std::cerr << "Err pasrse <primary>\n"; | |
exit(1); | |
} | |
}; | |
} // namespace parser | |
int main(int argc, char **argv) { | |
auto tokens = scanner::Scanner::scanTokens("-4 / (2 + (5 + 1)) * -(-(-123)) / (123 + 111)"); | |
auto result = parser::Parser::parse(tokens); | |
if (!result.isEmpty()) { | |
auto *expr = result.expr(); | |
std::cout << expr->toString() << '\n'; | |
std::cout << expr->eval() << '\n'; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment