first commit

This commit is contained in:
2024-04-07 17:55:16 +02:00
commit a15ca3add7
12 changed files with 905 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
main
main.exe
+4
View File
@@ -0,0 +1,4 @@
This repository implements a simple arithmetic expression parser and evaluator using the visitor pattern.
The main inspiration was this article:
https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions/
+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
+129
View File
@@ -0,0 +1,129 @@
#include "expr.hpp"
struct EvalVisitor : Visitor {
static const uint32_t max_depth = 32;
bool fail;
uint32_t depth;
double stack[max_depth];
EvalVisitor()
{
depth = 0;
fail = false;
}
void visit_int(IntValue *node)
{
if (fail) return;
if (depth == max_depth) {
fail = true;
return;
}
stack[depth++] = node->val;
}
void visit_float(FloatValue *node)
{
if (fail) return;
if (depth == max_depth) {
fail = true;
return;
}
stack[depth++] = node->val;
}
void visit_neg(NegOperation *node)
{
node->op->visit(*this);
if (fail) return;
if (depth < 1) {
fail = true;
return;
}
stack[depth-1] = -stack[depth-1];
}
void visit_add(AddOperation *node)
{
node->lhs->visit(*this);
node->rhs->visit(*this);
if (fail) return;
if (depth < 2) {
fail = true;
return;
}
double rhs = stack[--depth];
double lhs = stack[--depth];
stack[depth++] = lhs + rhs;
}
void visit_sub(SubOperation *node)
{
node->lhs->visit(*this);
node->rhs->visit(*this);
if (fail) return;
if (depth < 2) {
fail = true;
return;
}
double rhs = stack[--depth];
double lhs = stack[--depth];
stack[depth++] = lhs - rhs;
}
void visit_mul(MulOperation *node)
{
node->lhs->visit(*this);
node->rhs->visit(*this);
if (fail) return;
if (depth < 2) {
fail = true;
return;
}
double rhs = stack[--depth];
double lhs = stack[--depth];
stack[depth++] = lhs * rhs;
}
void visit_div(DivOperation *node)
{
node->lhs->visit(*this);
node->rhs->visit(*this);
if (fail) return;
if (depth < 2) {
fail = true;
return;
}
double rhs = stack[--depth];
double lhs = stack[--depth];
stack[depth++] = lhs / rhs;
}
};
double eval(Expr *e)
{
EvalVisitor visitor;
e->visit(visitor);
return visitor.stack[visitor.depth-1];
}
+3
View File
@@ -0,0 +1,3 @@
#include "expr.hpp"
double eval(Expr *e);
+138
View File
@@ -0,0 +1,138 @@
#pragma once
#include <cstdint>
struct IntValue;
struct FloatValue;
struct NegOperation;
struct AddOperation;
struct SubOperation;
struct MulOperation;
struct DivOperation;
struct Visitor {
virtual void visit_int(IntValue *node) = 0;
virtual void visit_float(FloatValue *node) = 0;
virtual void visit_neg(NegOperation *node) = 0;
virtual void visit_add(AddOperation *node) = 0;
virtual void visit_sub(SubOperation *node) = 0;
virtual void visit_mul(MulOperation *node) = 0;
virtual void visit_div(DivOperation *node) = 0;
};
struct Expr {
virtual void visit(Visitor &visitor) = 0;
};
struct IntValue : Expr {
int64_t val;
IntValue(int64_t val_)
{
val = val_;
}
virtual void visit(Visitor &visitor)
{
visitor.visit_int(this);
}
};
struct FloatValue : Expr {
double val;
FloatValue(double val_)
{
val = val_;
}
virtual void visit(Visitor &visitor)
{
visitor.visit_float(this);
}
};
struct NegOperation : Expr {
Expr *op;
NegOperation(Expr *op_)
{
op = op_;
}
virtual void visit(Visitor &visitor)
{
visitor.visit_neg(this);
}
};
struct AddOperation : Expr {
Expr *lhs;
Expr *rhs;
AddOperation(Expr *lhs_, Expr *rhs_)
{
lhs = lhs_;
rhs = rhs_;
}
virtual void visit(Visitor &visitor)
{
visitor.visit_add(this);
}
};
struct SubOperation : Expr {
Expr *lhs;
Expr *rhs;
SubOperation(Expr *lhs_, Expr *rhs_)
{
lhs = lhs_;
rhs = rhs_;
}
virtual void visit(Visitor &visitor)
{
visitor.visit_sub(this);
}
};
struct MulOperation : Expr {
Expr *lhs;
Expr *rhs;
MulOperation(Expr *lhs_, Expr *rhs_)
{
lhs = lhs_;
rhs = rhs_;
}
virtual void visit(Visitor &visitor)
{
visitor.visit_mul(this);
}
};
struct DivOperation : Expr {
Expr *lhs;
Expr *rhs;
DivOperation(Expr *lhs_, Expr *rhs_)
{
lhs = lhs_;
rhs = rhs_;
}
virtual void visit(Visitor &visitor)
{
visitor.visit_div(this);
}
};
+52
View File
@@ -0,0 +1,52 @@
#include <stdio.h>
#include <stdint.h>
#include "expr.hpp"
#include "eval.hpp"
#include "parse.hpp"
#include "stringify.hpp"
uint32_t read_line(FILE *stream, char *dst, uint32_t max)
{
uint32_t num = 0;
for (char c; (c = getc(stream)) != '\n'; )
if (num < max) dst[num++] = c;
if (max > 0) {
if (num == max)
num--;
dst[num] = '\0';
}
return num;
}
int main()
{
for (;;) {
uint32_t len;
char text[1<<9];
char pool[1<<12];
/*
* Read an expression from stdin
*/
fprintf(stdout, "> ");
len = read_line(stdin, text, sizeof(text));
/*
* Parse it
*/
Expr *e = parse(text, len, pool, sizeof(pool));
if (e == nullptr) {
fprintf(stderr, "Error\n");
continue;
}
/*
* Print the expression and its result
*/
stringify(e, text, sizeof(text));
fprintf(stdout, "%s = %g\n", text, eval(e));
}
return 0;
}
+3
View File
@@ -0,0 +1,3 @@
all:
g++ main.cpp parse.cpp eval.cpp stringify.cpp -o main -Wall -Wextra -ggdb
+442
View File
@@ -0,0 +1,442 @@
#include <new>
#include <cassert>
#include "parse.hpp"
struct Arena {
char *base;
uint32_t size;
uint32_t used;
Arena()
{
base = nullptr;
size = 0;
used = 0;
}
void init(void *base_, uint32_t size_)
{
base = (char*) base_;
size = size_;
used = 0;
}
static bool is_pow2(uint32_t n)
{
return (n & (n-1)) == 0;
}
void *alloc(uint32_t num, uint32_t align)
{
assert(is_pow2(align));
uint32_t pad = -(uintptr_t) (base + num) & (align-1);
if (used + pad + num > size)
return nullptr;
void *ptr = base + used + pad;
used += pad + num;
return ptr;
}
};
struct Scanner {
const char *src;
uint32_t cur;
uint32_t len;
};
struct Token {
enum Type {
ADD,
SUB,
MUL,
DIV,
INT,
FLOAT,
OTHER,
END,
};
Type type;
union {
double _float;
int64_t _int;
} data;
Token()
{
type = OTHER;
}
Token(Type type_)
{
type = type_;
if (type == INT)
data._int = 0;
else
data._float = 0;
}
Token(int64_t val)
{
type = INT;
data._int = val;
}
Token(double val)
{
type = FLOAT;
data._float = val;
}
};
static bool is_space(char c)
{
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
static bool is_digit(char c)
{
return c >= '0' && c <= '9';
}
static Token tokenize(Scanner &s)
{
while (s.cur < s.len && is_space(s.src[s.cur]))
s.cur++;
if (s.cur == s.len)
return Token::END;
if (is_digit(s.src[s.cur])) {
/*
* May be either an integer or a float. Peek after the first
* digit sequence to see if there's a dot
*/
uint32_t peek = s.cur;
while (peek < s.len && is_digit(s.src[peek]))
peek++;
bool no_dot = (peek == s.len || s.src[peek] != '.');
if (no_dot) {
/*
* Parse integer token
*/
int64_t buf = 0;
do {
int n = s.src[s.cur] - '0';
if (buf > (INT64_MAX - n) / 10) {
/*
* Overflow. Consume all remaining digits and return
* the maximum representable value.
*/
while (s.cur < s.len && is_digit(s.src[s.cur]))
s.cur++;
return INT64_MAX;
}
buf = buf * 10 + n;
s.cur++;
} while (s.cur < s.len && is_digit(s.src[s.cur]));
return buf;
} else {
/*
* Parse float token
*/
double buf = 0;
do {
int n = s.src[s.cur] - '0';
buf = buf * 10 + n;
s.cur++;
} while (s.src[s.cur] != '.');
s.cur++; // Consume the dot
/*
* Consume the decimal part, if present
*/
if (s.cur < s.len && is_digit(s.src[s.cur])) {
double q = 1;
do {
q /= 10;
int n = s.src[s.cur] - '0';
buf += q * n;
s.cur++;
} while (s.cur < s.len && is_digit(s.src[s.cur]));
}
return buf;
}
}
if (s.src[s.cur] == '+') {
s.cur++;
return Token::ADD;
}
if (s.src[s.cur] == '-') {
s.cur++;
return Token::SUB;
}
if (s.src[s.cur] == '*') {
s.cur++;
return Token::MUL;
}
if (s.src[s.cur] == '/') {
s.cur++;
return Token::DIV;
}
/*
* Unexpected character
*/
return Token::OTHER;
}
static int precedence_of(Token token)
{
switch (token.type) {
case Token::ADD:
case Token::SUB:
return 0;
case Token::MUL:
case Token::DIV:
return 1;
default:break;
}
return -1;
}
static bool is_right_associative(Token op)
{
(void) op;
return false;
}
/*
* Returns true if the next token is an operator
* with a greater or equal precedence than the one
* specified. The following operator is stored in
* "op", but is only consimed when true is returned.
*/
static bool
follows_oper_of_higher_precedence(Scanner &s, Token &op, int min)
{
uint32_t save = s.cur;
op = tokenize(s);
if (precedence_of(op) >= min)
return true;
s.cur = save;
return false;
}
static bool should_associate_right(Scanner &s, Token op, Token &peek)
{
uint32_t save = s.cur;
peek = tokenize(s);
s.cur = save;
int p1 = precedence_of(op);
int p2 = precedence_of(peek);
if (p1 < p2)
return true;
if (p1 == p2 && is_right_associative(peek))
return true;
return false;
}
static Expr *make_int(Arena &a, int64_t raw)
{
void *p = a.alloc(sizeof(IntValue), alignof(IntValue));
if (p == nullptr)
return nullptr;
return new (p) IntValue(raw);
}
static Expr *make_float(Arena &a, double raw)
{
FloatValue *n;
n = (FloatValue*) a.alloc(sizeof(FloatValue), alignof(FloatValue));
if (n == nullptr)
return nullptr;
new (n) FloatValue(raw);
return n;
}
static Expr *make_neg(Arena &a, Expr *operand)
{
NegOperation *n;
n = (NegOperation*) a.alloc(sizeof(NegOperation), alignof(NegOperation));
if (n == nullptr)
return nullptr;
new (n) NegOperation(operand);
return n;
}
static Expr *parse_primary(Scanner &s, Arena &a)
{
Token t;
/*
* Handle preceding unary operators
*/
bool neg = false;
for (;;) {
t = tokenize(s);
if (t.type == Token::SUB)
neg = !neg;
else {
if (t.type != Token::ADD)
break;
}
}
Expr *e;
switch (t.type) {
case Token::INT : e = make_int (a, t.data._int); break;
case Token::FLOAT : e = make_float(a, t.data._float); break;
default:
case Token::END:
case Token::OTHER:
return nullptr;
}
if (e == nullptr)
return nullptr;
if (neg)
return make_neg(a, e); // May return NULL
else
return e;
}
static Expr *make_add(Arena &a, Expr *lhs, Expr *rhs)
{
AddOperation *n;
n = (AddOperation*) a.alloc(sizeof(AddOperation), alignof(AddOperation));
if (n == nullptr)
return nullptr;
new (n) AddOperation(lhs, rhs);
return n;
}
static Expr *make_sub(Arena &a, Expr *lhs, Expr *rhs)
{
SubOperation *n;
n = (SubOperation*) a.alloc(sizeof(SubOperation), alignof(SubOperation));
if (n == nullptr)
return nullptr;
new (n) SubOperation(lhs, rhs);
return n;
}
static Expr *make_mul(Arena &a, Expr *lhs, Expr *rhs)
{
MulOperation *n;
n = (MulOperation*) a.alloc(sizeof(MulOperation), alignof(MulOperation));
if (n == nullptr)
return nullptr;
new (n) MulOperation(lhs, rhs);
return n;
}
static Expr *make_div(Arena &a, Expr *lhs, Expr *rhs)
{
DivOperation *n;
n = (DivOperation*) a.alloc(sizeof(DivOperation), alignof(DivOperation));
if (n == nullptr)
return nullptr;
new (n) DivOperation(lhs, rhs);
return n;
}
/*
* Got this algorithm from:
* https://en.wikipedia.org/wiki/Operator-precedence_parser
*/
static Expr *parse_expression_2(Scanner &s, Arena &a,
Expr *lhs, int min_precedence)
{
for (Token op; follows_oper_of_higher_precedence(s, op, min_precedence); ) {
Expr *rhs = parse_primary(s, a);
if (rhs == nullptr)
return nullptr;
for (Token peek; should_associate_right(s, op, peek); ) {
int p1 = precedence_of(op);
int p2 = precedence_of(peek);
rhs = parse_expression_2(s, a, rhs, p1 + (p2 > p1));
if (rhs == nullptr)
return nullptr;
}
switch (op.type) {
case Token::ADD: lhs = make_add(a, lhs, rhs); break;
case Token::SUB: lhs = make_sub(a, lhs, rhs); break;
case Token::MUL: lhs = make_mul(a, lhs, rhs); break;
case Token::DIV: lhs = make_div(a, lhs, rhs); break;
default:break;
}
if (lhs == nullptr)
return nullptr;
}
return lhs;
}
Expr *parse(const char *src, uint32_t len,
void *mem, uint32_t memlen)
{
Arena a;
a.init(mem, memlen);
Scanner s;
s.src = src;
s.len = len;
s.cur = 0;
Expr *e;
e = parse_primary(s, a);
if (e == nullptr)
return nullptr;
return parse_expression_2(s, a, e, 0);
}
+4
View File
@@ -0,0 +1,4 @@
#include "expr.hpp"
Expr *parse(const char *src, uint32_t len,
void *mem, uint32_t memlen);
+101
View File
@@ -0,0 +1,101 @@
#include <string>
#include <sstream>
#include <cstring>
#include <algorithm>
#include "expr.hpp"
struct StringifyVisitor : Visitor {
char *dst;
uint32_t num;
uint32_t max;
void append(const char *str, uint32_t len)
{
if (num < max) {
uint32_t cpy = std::min(len, max - num);
memcpy(dst + num, str, cpy);
}
num += len;
}
StringifyVisitor(char *dst_, uint32_t max_)
{
dst = dst_;
max = max_;
num = 0;
}
void visit_int(IntValue *node)
{
std::ostringstream sstr;
sstr << node->val;
std::string str = sstr.str();
append(str.c_str(), str.size());
}
void visit_float(FloatValue *node)
{
std::ostringstream sstr;
sstr << node->val;
std::string str = sstr.str();
append(str.c_str(), str.size());
}
void visit_neg(NegOperation *node)
{
append("-", 1);
node->op->visit(*this);
}
void visit_add(AddOperation *node)
{
append("(", 1);
node->lhs->visit(*this);
append(" + ", 3);
node->rhs->visit(*this);
append(")", 1);
}
void visit_sub(SubOperation *node)
{
append("(", 1);
node->lhs->visit(*this);
append(" - ", 3);
node->rhs->visit(*this);
append(")", 1);
}
void visit_mul(MulOperation *node)
{
append("(", 1);
node->lhs->visit(*this);
append(" * ", 3);
node->rhs->visit(*this);
append(")", 1);
}
void visit_div(DivOperation *node)
{
append("(", 1);
node->lhs->visit(*this);
append(" / ", 3);
node->rhs->visit(*this);
append(")", 1);
}
};
uint32_t stringify(Expr *e, char *dst, uint32_t max)
{
StringifyVisitor visitor(dst, max);
e->visit(visitor);
uint32_t num = visitor.num;
if (max > 0) {
if (num == max)
num--;
dst[num] = '\0';
}
return num;
}
+3
View File
@@ -0,0 +1,3 @@
#include "expr.hpp"
uint32_t stringify(Expr *e, char *dst, uint32_t max);