blob: 102bfdf75ffe9aab2012b1cfe63d3a5e4ee5e305 [file] [log] [blame]
// Copyright 2015 Google Inc. All rights reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build ignore
#include "expr.h"
#include <vector>
#include "eval.h"
#include "func.h"
#include "log.h"
#include "stringprintf.h"
#include "strutil.h"
#include "var.h"
Evaluable::Evaluable(const Loc& loc) : loc_(loc) {}
Evaluable::~Evaluable() {}
std::string Evaluable::Eval(Evaluator* ev) const {
std::string s;
Eval(ev, &s);
return s;
}
Value::Value(const Loc& loc) : Evaluable(loc) {}
Value::~Value() {}
std::string Value::DebugString(const Value* v) {
return v ? NoLineBreak(v->DebugString_()) : "(null)";
}
class Literal : public Value {
public:
explicit Literal(std::string_view s) : Value(Loc()), s_(s) {}
std::string_view val() const { return s_; }
virtual bool IsFunc(Evaluator*) const override { return false; }
virtual void Eval(Evaluator* ev, std::string* s) const override {
ev->CheckStack();
s->append(s_.begin(), s_.end());
}
virtual bool IsLiteral() const override { return true; }
virtual std::string_view GetLiteralValueUnsafe() const override { return s_; }
virtual std::string DebugString_() const override { return std::string(s_); }
private:
std::string_view s_;
};
class ValueList : public Value {
public:
ValueList(const Loc& loc) : Value(loc) {}
ValueList(const Loc& loc, Value* v1, Value* v2, Value* v3) : ValueList(loc) {
vals_.reserve(3);
vals_.push_back(v1);
vals_.push_back(v2);
vals_.push_back(v3);
}
ValueList(const Loc& loc, Value* v1, Value* v2) : ValueList(loc) {
vals_.reserve(2);
vals_.push_back(v1);
vals_.push_back(v2);
}
ValueList(const Loc& loc, std::vector<Value*>* values) : ValueList(loc) {
values->shrink_to_fit();
values->swap(vals_);
}
virtual ~ValueList() {
for (Value* v : vals_) {
delete v;
}
}
virtual bool IsFunc(Evaluator* ev) const override {
for (Value* v : vals_) {
if (v->IsFunc(ev)) {
return true;
}
}
return false;
}
virtual void Eval(Evaluator* ev, std::string* s) const override {
ev->CheckStack();
for (Value* v : vals_) {
v->Eval(ev, s);
}
}
virtual std::string DebugString_() const override {
std::string r;
for (Value* v : vals_) {
if (r.empty()) {
r += "ValueList(";
} else {
r += ", ";
}
r += DebugString(v);
}
if (!r.empty())
r += ")";
return r;
}
private:
std::vector<Value*> vals_;
};
class SymRef : public Value {
public:
explicit SymRef(const Loc& loc, Symbol n) : Value(loc), name_(n) {}
virtual ~SymRef() {}
virtual bool IsFunc(Evaluator*) const override {
// This is a heuristic, where say that if a variable has positional
// parameters, we think it is likely to be a function. Callers can use
// .KATI_SYMBOLS to extract variables and their values, without evaluating
// macros that are likely to have side effects.
return IsInteger(name_.str());
}
virtual void Eval(Evaluator* ev, std::string* s) const override {
ev->CheckStack();
Var* v = ev->LookupVarForEval(name_);
v->Used(ev, name_);
v->Eval(ev, s);
v->CheckCurrentReferencingFile(ev->loc(), name_.c_str());
ev->VarEvalComplete(name_);
}
virtual std::string DebugString_() const override {
return StringPrintf("SymRef(%s)", name_.c_str());
}
private:
Symbol name_;
};
class VarRef : public Value {
public:
explicit VarRef(const Loc& loc, Value* n) : Value(loc), name_(n) {}
virtual ~VarRef() { delete name_; }
virtual bool IsFunc(Evaluator*) const override {
// This is the unhandled edge case as described in expr.h.
return true;
}
virtual void Eval(Evaluator* ev, std::string* s) const override {
ev->CheckStack();
ev->IncrementEvalDepth();
const std::string&& name = name_->Eval(ev);
ev->DecrementEvalDepth();
Symbol sym = Intern(name);
Var* v = ev->LookupVarForEval(sym);
v->Used(ev, sym);
v->Eval(ev, s);
v->CheckCurrentReferencingFile(ev->loc(), name.c_str());
ev->VarEvalComplete(sym);
}
virtual std::string DebugString_() const override {
return StringPrintf("VarRef(%s)", Value::DebugString(name_).c_str());
}
private:
Value* name_;
};
class VarSubst : public Value {
public:
VarSubst(const Loc& loc, Value* n, Value* p, Value* s)
: Value(loc), name_(n), pat_(p), subst_(s) {}
virtual ~VarSubst() {
delete name_;
delete pat_;
delete subst_;
}
virtual bool IsFunc(Evaluator* ev) const override {
return name_->IsFunc(ev) || pat_->IsFunc(ev) || subst_->IsFunc(ev);
}
virtual void Eval(Evaluator* ev, std::string* s) const override {
ev->CheckStack();
ev->IncrementEvalDepth();
const std::string&& name = name_->Eval(ev);
Symbol sym = Intern(name);
Var* v = ev->LookupVar(sym);
const std::string&& pat_str = pat_->Eval(ev);
const std::string&& subst = subst_->Eval(ev);
ev->DecrementEvalDepth();
v->Used(ev, sym);
const std::string&& value = v->Eval(ev);
WordWriter ww(s);
Pattern pat(pat_str);
for (std::string_view tok : WordScanner(value)) {
ww.MaybeAddSeparator();
pat.AppendSubstRef(tok, subst, s);
}
}
virtual std::string DebugString_() const override {
return StringPrintf("VarSubst(%s:%s=%s)", Value::DebugString(name_).c_str(),
Value::DebugString(pat_).c_str(),
Value::DebugString(subst_).c_str());
}
private:
Value* name_;
Value* pat_;
Value* subst_;
};
class Func : public Value {
public:
Func(const Loc& loc, const FuncInfo* fi) : Value(loc), fi_(fi) {}
~Func() {
for (Value* a : args_)
delete a;
}
virtual bool IsFunc(Evaluator*) const override { return true; }
virtual void Eval(Evaluator* ev, std::string* s) const override {
ScopedFrame frame(ev->Enter(FrameType::FUNCALL, fi_->name, Location()));
ev->CheckStack();
LOG("Invoke func %s(%s)", name(), JoinValues(args_, ",").c_str());
ev->IncrementEvalDepth();
fi_->func(args_, ev, s);
ev->DecrementEvalDepth();
}
virtual std::string DebugString_() const override {
return StringPrintf("Func(%s %s)", fi_->name,
JoinValues(args_, ",").c_str());
}
void AddArg(Value* v) { args_.push_back(v); }
const char* name() const { return fi_->name; }
int arity() const { return fi_->arity; }
int min_arity() const { return fi_->min_arity; }
bool trim_space() const { return fi_->trim_space; }
bool trim_right_space_1st() const { return fi_->trim_right_space_1st; }
private:
const FuncInfo* fi_;
std::vector<Value*> args_;
};
static char CloseParen(char c) {
switch (c) {
case '(':
return ')';
case '{':
return '}';
}
return 0;
}
static size_t SkipSpaces(Loc* loc, std::string_view s, const char* terms) {
for (size_t i = 0; i < s.size(); i++) {
char c = s[i];
if (strchr(terms, c)) {
return i;
}
if (!isspace(c)) {
if (c != '\\') {
return i;
}
char n = i + 1 < s.size() ? s[i + 1] : 0;
if (n != '\r' && n != '\n') {
return i;
}
loc->lineno++; // This is a backspace continuation
}
}
return s.size();
}
Value* Value::NewExpr(const Loc& loc, Value* v1, Value* v2) {
return new ValueList(loc, v1, v2);
}
Value* Value::NewExpr(const Loc& loc, Value* v1, Value* v2, Value* v3) {
return new ValueList(loc, v1, v2, v3);
}
Value* Value::NewExpr(const Loc& loc, std::vector<Value*>* values) {
if (values->size() == 1) {
Value* v = (*values)[0];
values->clear();
return v;
}
return new ValueList(loc, values);
}
Value* Value::NewLiteral(std::string_view s) {
return new Literal(s);
}
bool ShouldHandleComments(ParseExprOpt opt) {
return opt != ParseExprOpt::DEFINE && opt != ParseExprOpt::COMMAND;
}
void ParseFunc(Loc* loc,
Func* f,
std::string_view s,
size_t i,
char* terms,
size_t* index_out) {
Loc start_loc = *loc;
terms[1] = ',';
terms[2] = '\0';
i += SkipSpaces(loc, s.substr(i), terms);
if (i == s.size()) {
*index_out = i;
return;
}
int nargs = 1;
while (true) {
if (f->arity() && nargs >= f->arity()) {
terms[1] = '\0'; // Drop ','.
}
if (f->trim_space()) {
for (; i < s.size(); i++) {
if (isspace(s[i])) {
continue;
}
if (s[i] == '\\') {
char c = i + 1 < s.size() ? s[i + 1] : 0;
if (c == '\r' || c == '\n') {
loc->lineno++;
continue;
}
}
break;
}
}
const bool trim_right_space =
(f->trim_space() || (nargs == 1 && f->trim_right_space_1st()));
size_t n;
Value* v = ParseExprImpl(loc, s.substr(i), terms, ParseExprOpt::FUNC, &n,
trim_right_space);
// TODO: concatLine???
f->AddArg(v);
i += n;
if (i == s.size()) {
ERROR_LOC(start_loc,
"*** unterminated call to function '%s': "
"missing '%c'.",
f->name(), terms[0]);
}
nargs++;
if (s[i] == terms[0]) {
i++;
break;
}
i++; // Should be ','.
if (i == s.size())
break;
}
if (nargs <= f->min_arity()) {
ERROR_LOC(start_loc,
"*** insufficient number of arguments (%d) to function `%s'.",
nargs - 1, f->name());
}
*index_out = i;
return;
}
Value* ParseDollar(Loc* loc, std::string_view s, size_t* index_out) {
CHECK(s.size() >= 2);
CHECK(s[0] == '$');
CHECK(s[1] != '$');
Loc start_loc = *loc;
char cp = CloseParen(s[1]);
if (cp == 0) {
*index_out = 2;
return new SymRef(start_loc, Intern(s.substr(1, 1)));
}
char terms[] = {cp, ':', ' ', 0};
for (size_t i = 2;;) {
size_t n;
Value* vname =
ParseExprImpl(loc, s.substr(i), terms, ParseExprOpt::NORMAL, &n);
i += n;
if (s[i] == cp) {
*index_out = i + 1;
if (vname->IsLiteral()) {
Literal* lit = static_cast<Literal*>(vname);
Symbol sym = Intern(lit->val());
if (g_flags.enable_kati_warnings) {
size_t found = sym.str().find_first_of(" ({");
if (found != std::string::npos) {
KATI_WARN_LOC(start_loc,
"*warning*: variable lookup with '%c': %.*s",
sym.str()[found], SPF(s));
}
}
Value* r = new SymRef(start_loc, sym);
delete lit;
return r;
}
return new VarRef(start_loc, vname);
}
if (s[i] == ' ' || s[i] == '\\') {
// ${func ...}
if (vname->IsLiteral()) {
Literal* lit = static_cast<Literal*>(vname);
if (const FuncInfo* fi = GetFuncInfo(lit->val())) {
delete lit;
Func* func = new Func(start_loc, fi);
ParseFunc(loc, func, s, i + 1, terms, index_out);
return func;
} else {
KATI_WARN_LOC(start_loc,
"*warning*: unknown make function '%.*s': %.*s",
SPF(lit->val()), SPF(s));
}
}
// Not a function. Drop ' ' from |terms| and parse it
// again. This is inefficient, but this code path should be
// rarely used.
delete vname;
terms[2] = 0;
i = 2;
continue;
}
if (s[i] == ':') {
terms[2] = '\0';
terms[1] = '=';
size_t n;
Value* pat =
ParseExprImpl(loc, s.substr(i + 1), terms, ParseExprOpt::NORMAL, &n);
i += 1 + n;
if (s[i] == cp) {
*index_out = i + 1;
return new VarRef(
start_loc, Value::NewExpr(start_loc, vname, new Literal(":"), pat));
}
terms[1] = '\0';
Value* subst =
ParseExprImpl(loc, s.substr(i + 1), terms, ParseExprOpt::NORMAL, &n);
i += 1 + n;
*index_out = i + 1;
return new VarSubst(start_loc, vname, pat, subst);
}
// GNU make accepts expressions like $((). See unmatched_paren*.mk
// for detail.
size_t found = s.find(cp);
if (found != std::string::npos) {
KATI_WARN_LOC(start_loc, "*warning*: unmatched parentheses: %.*s",
SPF(s));
*index_out = s.size();
return new SymRef(start_loc, Intern(s.substr(2, found - 2)));
}
ERROR_LOC(start_loc, "*** unterminated variable reference.");
}
}
Value* ParseExprImpl(Loc* loc,
std::string_view s,
const char* terms,
ParseExprOpt opt,
size_t* index_out,
bool trim_right_space) {
Loc list_loc = *loc;
if (!s.empty() && s.back() == '\r')
s.remove_suffix(1);
size_t b = 0;
char save_paren = 0;
int paren_depth = 0;
size_t i;
std::vector<Value*> list;
for (i = 0; i < s.size(); i++) {
Loc item_loc = *loc;
char c = s[i];
if (terms && strchr(terms, c) && !save_paren) {
break;
}
// Handle a comment.
if (!terms && c == '#' && ShouldHandleComments(opt)) {
if (i > b)
list.push_back(new Literal(s.substr(b, i - b)));
bool was_backslash = false;
for (; i < s.size() && !(s[i] == '\n' && !was_backslash); i++) {
was_backslash = !was_backslash && s[i] == '\\';
}
*index_out = i;
return Value::NewExpr(item_loc, &list);
}
if (c == '$') {
if (i + 1 >= s.size()) {
break;
}
if (i > b)
list.push_back(new Literal(s.substr(b, i - b)));
if (s[i + 1] == '$') {
list.push_back(new Literal(std::string_view("$")));
i += 1;
b = i + 1;
continue;
}
if (terms && strchr(terms, s[i + 1])) {
list.push_back(new Literal(std::string_view("$")));
*index_out = i + 1;
return Value::NewExpr(item_loc, &list);
}
size_t n;
list.push_back(ParseDollar(loc, s.substr(i), &n));
i += n;
b = i;
i--;
continue;
}
if ((c == '(' || c == '{') && opt == ParseExprOpt::FUNC) {
char cp = CloseParen(c);
if (terms && terms[0] == cp) {
paren_depth++;
save_paren = cp;
terms++;
} else if (cp == save_paren) {
paren_depth++;
}
continue;
}
if (c == save_paren) {
paren_depth--;
if (paren_depth == 0) {
terms--;
save_paren = 0;
}
}
if (c == '\\' && i + 1 < s.size() && opt != ParseExprOpt::COMMAND) {
char n = s[i + 1];
if (n == '\\') {
i++;
continue;
}
if (n == '#' && ShouldHandleComments(opt)) {
list.push_back(new Literal(s.substr(b, i - b)));
i++;
b = i;
continue;
}
if (n == '\r' || n == '\n') {
loc->lineno++;
if (terms && strchr(terms, ' ')) {
break;
}
if (i > b) {
list.push_back(new Literal(TrimRightSpace(s.substr(b, i - b))));
}
list.push_back(new Literal(std::string_view(" ")));
// Skip the current escaped newline
i += 2;
if (n == '\r' && i < s.size() && s[i] == '\n') {
i++;
}
// Then continue skipping escaped newlines, spaces, and tabs
for (; i < s.size(); i++) {
if (s[i] == '\\' && i + 1 < s.size() &&
(s[i + 1] == '\r' || s[i + 1] == '\n')) {
loc->lineno++;
i++;
continue;
}
if (s[i] != ' ' && s[i] != '\t') {
break;
}
}
b = i;
i--;
}
}
}
if (i > b) {
std::string_view rest = s.substr(b, i - b);
if (trim_right_space)
rest = TrimRightSpace(rest);
if (!rest.empty())
list.push_back(new Literal(rest));
}
*index_out = i;
return Value::NewExpr(list_loc, &list);
}
Value* ParseExpr(Loc* loc, std::string_view s, ParseExprOpt opt) {
size_t n;
return ParseExprImpl(loc, s, NULL, opt, &n);
}
std::string JoinValues(const std::vector<Value*>& vals, const char* sep) {
std::vector<std::string> val_strs;
val_strs.reserve(vals.size());
for (Value* v : vals) {
val_strs.push_back(Value::DebugString(v));
}
return JoinStrings(val_strs, sep);
}