Skip to content

Instantly share code, notes, and snippets.

@iemelyanov
Last active August 13, 2021 09:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iemelyanov/428fda12271af077c2c467ed001ce658 to your computer and use it in GitHub Desktop.
Save iemelyanov/428fda12271af077c2c467ed001ce658 to your computer and use it in GitHub Desktop.
Impl simple calc for play with C++
/*
=======
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