24constexpr Fn
kFns[] = {
31constexpr float kPi = 3.14159265358979323846f;
33enum class Tok { NUM, NAME, OP, LPAREN, RPAREN, COMMA, END };
44 Parser(
const std::string&
text,
size_t n_inputs) :
s_(
text),
n_(n_inputs) {
lex(); }
47 if (
toks_.front().tok == Tok::END) fail(
"empty expression", 1);
55 throw std::invalid_argument(
"expr: " +
what +
" at column " + std::to_string(
column) +
" in \"" + s_ +
"\"");
60 while (
i <
s_.size()) {
61 const char ch =
s_[
i];
62 const size_t col =
i + 1;
63 if (
ch ==
' ' ||
ch ==
'\t') {
65 }
else if (std::isdigit(
static_cast<unsigned char>(
ch)) ||
ch ==
'.') {
68 while (
j <
s_.size() && std::isdigit(
static_cast<unsigned char>(s_[
j]))) ++
j, ++
mant;
69 if (
j <
s_.size() && s_[
j] ==
'.') {
71 while (
j <
s_.size() && std::isdigit(
static_cast<unsigned char>(s_[
j]))) ++
j, ++
mant;
73 if (
mant == 0) fail(
"malformed number",
col);
74 if (
j <
s_.size() && (s_[
j] ==
'e' || s_[
j] ==
'E')) {
76 if (
k <
s_.size() && (s_[
k] ==
'+' || s_[
k] ==
'-')) ++
k;
78 while (
k <
s_.size() && std::isdigit(
static_cast<unsigned char>(s_[
k]))) ++
k;
79 if (
k ==
d0) fail(
"malformed number (exponent without digits)",
col);
82 const std::string
lit =
s_.substr(
i,
j -
i);
83 const float v = std::strtof(
lit.c_str(),
nullptr);
84 if (!std::isfinite(
v)) fail(
"number " +
lit +
" is out of float range",
col);
87 }
else if (std::isalpha(
static_cast<unsigned char>(
ch)) ||
ch ==
'_') {
89 while (
j <
s_.size() && (std::isalnum(
static_cast<unsigned char>(s_[
j])) || s_[
j] ==
'_')) ++
j;
92 }
else if (
ch ==
'+' ||
ch ==
'-' ||
ch ==
'*' ||
ch ==
'/' ||
ch ==
'^') {
93 toks_.push_back({Tok::OP, std::string(1,
ch), 0.0f,
col});
95 }
else if (
ch ==
'(') {
96 toks_.push_back({Tok::LPAREN,
"(", 0.0f,
col});
98 }
else if (
ch ==
')') {
99 toks_.push_back({Tok::RPAREN,
")", 0.0f,
col});
101 }
else if (
ch ==
',') {
102 toks_.push_back({Tok::COMMA,
",", 0.0f,
col});
105 fail(std::string(
"unexpected character '") +
ch +
"'",
col);
108 toks_.push_back({Tok::END,
"end", 0.0f,
s_.size() + 1});
112 const Token& next() {
return toks_[
pos_++]; }
113 bool at_op(
char op)
const {
return peek().tok == Tok::OP &&
peek().text[0] == op; }
123 n.args.push_back(std::move(
a));
124 n.args.push_back(std::move(
b));
133 const Token op = next();
145 const Token op = next();
156 const Token op = next();
159 n.column = op.column;
171 const Token op = next();
180 const Token
t = next();
183 if (
t.tok == Tok::NUM) {
188 if (
t.tok == Tok::LPAREN) {
194 if (
t.tok != Tok::NAME) {
195 fail(
t.tok == Tok::END ?
"expected an operand, got the end" :
"expected an operand, got '" +
t.
text +
"'",
198 for (
const Fn&
f :
kFns) {
199 if (
t.text !=
f.name)
continue;
200 if (
peek().
tok != Tok::LPAREN) fail(
"'" +
t.text +
"' is a function: expected '('",
peek().
column);
205 if (
peek().
tok == Tok::COMMA) {
213 if (
n.args.size() !=
f.arity)
214 fail(
"'" +
t.text +
"' takes " + std::to_string(
f.arity) +
" argument" + (
f.arity == 1 ?
"" :
"s") +
219 if (
peek().
tok == Tok::LPAREN) fail(
"unknown function '" +
t.text +
"'",
t.column);
220 if (
t.text ==
"pi") {
229 }
else if (
t.text.size() == 1 &&
t.text[0] >=
'a' &&
t.text[0] <
static_cast<char>(
'a' + n_ - 1)) {
230 idx =
static_cast<size_t>(
t.text[0] -
'a') + 1;
233 std::string
names =
"x";
234 for (
size_t k = 1;
k <
n_; ++
k)
names += std::string(
", ") +
static_cast<char>(
'a' +
k - 1);
235 fail(
"unknown name '" +
t.text +
"' (inputs: " +
names +
"; constant: pi)",
t.column);
242 const std::string&
s_;
255 enum Shape { SCALAR = 0, COL = 1, FULL = 2 }
shape = SCALAR;
257 std::vector<float>
v;
273 default:
throw std::logic_error(
"expr: unary");
286 default:
throw std::logic_error(
"expr: binary");
292 Evaluator(
const std::vector<const float*>&
ins,
int T,
int w) :
ins_(
ins),
T_(
T),
w_(
w) {}
294 void result(
const Node& root,
float*
out) {
295 Value
r =
to(
eval(root), Value::FULL);
296 std::copy(
r.v.begin(),
r.v.end(),
out);
300 size_t size(Value::Shape
s)
const {
301 return s == Value::SCALAR ? 1 :
static_cast<size_t>(
T_) * (
s == Value::COL ? 1 :
w_);
305 Value
to(Value
r, Value::Shape
shape)
const {
306 if (
r.shape ==
shape)
return r;
310 if (
r.shape == Value::SCALAR) {
311 std::fill(
out.v.begin(),
out.v.end(),
r.c);
313 for (
int t = 0;
t <
T_; ++
t) std::fill_n(
out.v.begin() +
static_cast<size_t>(
t) * w_, w_,
r.v[
t]);
324 Value
eval(
const Node&
n) {
329 out.shape = Value::FULL;
330 out.v.assign(ins_[
n.input], ins_[
n.input] + size(Value::FULL));
335 Value
a =
eval(
n.args[0]);
345 if (
c.shape == Value::SCALAR &&
a.shape == Value::SCALAR &&
b.shape == Value::SCALAR)
347 const auto s = std::max({
c.shape,
a.shape,
b.shape});
348 c =
to(std::move(
c),
s),
a =
to(std::move(
a),
s),
b =
to(std::move(
b),
s);
349 for (
size_t i = 0;
i <
a.v.size(); ++
i)
a.v[
i] =
c.v[
i] > 0.0f ?
a.v[
i] :
b.
v[
i];
353 const Value
full =
to(
eval(
n.args[0]), Value::FULL);
355 col.shape = Value::COL;
356 col.v.resize(
static_cast<size_t>(T_));
357 for (
int t = 0;
t <
T_; ++
t) {
358 const float*
row =
full.v.data() +
static_cast<size_t>(
t) * w_;
373 throw std::logic_error(
"expr: unhandled node");
378 const auto s = std::max(
a.shape,
b.shape);
384 if (
a.shape == Value::SCALAR ||
b.shape == Value::SCALAR) {
385 const bool left =
a.shape == Value::SCALAR;
386 Value&
t = left ?
b :
a;
387 const float c = left ?
a.c :
b.c;
391 a =
to(std::move(
a),
s),
b =
to(std::move(
b),
s);
396 const std::vector<const float*>&
ins_;
421 default:
throw std::logic_error(
"expr: no instruction");
427 explicit Lowering(
const Expr&
e) :
e_(
e) {}
429 std::vector<Stage>
run() {
433 return std::move(stages_);
439 static bool constant(
const Node&
n,
float&
v) {
446 v =
c > 0.0f ?
a :
b;
453 if (
n.args.size() == 1) {
466 throw std::invalid_argument(
"expr: \"" +
e_.text() +
"\" needs " + std::to_string(
s.code.size()) +
467 " instructions and a stack of " + std::to_string(
peak) +
" in one stage (the CUDA form "
468 "takes " + std::to_string(
kMaxCode) +
" and " + std::to_string(
kMaxStack) +
")");
472 auto push = [&](
Op op,
int idx = 0,
float c = 0.0f) {
473 code.push_back({op, idx,
c});
486 throw std::invalid_argument(
"expr: \"" +
e_.text() +
"\" has more than " + std::to_string(
kMaxColumns) +
487 " row reductions (the CUDA form's limit)");
500 depth -=
n.args.size() - 1;
509std::vector<Stage>
lower(
const Expr&
e) {
return Lowering(
e).run(); }
513 throw std::invalid_argument(
"expr: " + std::to_string(
n_inputs) +
" inputs (1 to " + std::to_string(
kMaxInputs) +
523 if (
ins.size() !=
e.n_inputs())
524 throw std::invalid_argument(
"expr: " + std::to_string(
ins.size()) +
" inputs given, \"" +
e.text() +
525 "\" was parsed for " + std::to_string(
e.n_inputs()));
526 if (
T <= 0 ||
w <= 0)
throw std::invalid_argument(
"expr: inputs must be [T, w] with T, w > 0");
527 Evaluator(
ins,
T,
w).result(
e.root(),
out);
531 static std::mutex
mu;
532 static std::map<std::pair<std::string, size_t>,
Expr>
cache;
533 std::lock_guard<std::mutex>
lock(
mu);
534 auto key = std::make_pair(
text, n_inputs);