| // 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() { |
| } |
| |
| Evaluable::~Evaluable() { |
| } |
| |
| string Evaluable::Eval(Evaluator* ev) const { |
| string s; |
| Eval(ev, &s); |
| return s; |
| } |
| |
| Value::Value() { |
| } |
| |
| Value::~Value() { |
| } |
| |
| string Value::DebugString() const { |
| if (static_cast<const Value*>(this)) { |
| return NoLineBreak(DebugString_()); |
| } |
| return "(null)"; |
| } |
| |
| class Literal : public Value { |
| public: |
| explicit Literal(StringPiece s) |
| : s_(s) { |
| } |
| |
| StringPiece val() const { return s_; } |
| |
| virtual void Eval(Evaluator*, string* s) const override { |
| s->append(s_.begin(), s_.end()); |
| } |
| |
| virtual bool IsLiteral() const override { return true; } |
| virtual StringPiece GetLiteralValueUnsafe() const override { return s_; } |
| |
| virtual string DebugString_() const override { |
| return s_.as_string(); |
| } |
| |
| private: |
| StringPiece s_; |
| }; |
| |
| class Expr : public Value { |
| public: |
| Expr() { |
| } |
| |
| virtual ~Expr() { |
| for (Value* v : vals_) { |
| delete v; |
| } |
| } |
| |
| // Takes the ownership of |v|. |
| void AddValue(Value* v) { |
| vals_.push_back(v); |
| } |
| |
| virtual void Eval(Evaluator* ev, string* s) const override { |
| for (Value* v : vals_) { |
| v->Eval(ev, s); |
| } |
| } |
| |
| virtual string DebugString_() const override { |
| string r; |
| for (Value* v : vals_) { |
| if (r.empty()) { |
| r += "Expr("; |
| } else { |
| r += ", "; |
| } |
| r += v->DebugString(); |
| } |
| if (!r.empty()) |
| r += ")"; |
| return r; |
| } |
| |
| virtual Value* Compact() override { |
| if (vals_.size() != 1) { |
| return this; |
| } |
| Value* r = vals_[0]; |
| vals_.clear(); |
| delete this; |
| return r; |
| } |
| |
| private: |
| vector<Value*> vals_; |
| }; |
| |
| class SymRef : public Value { |
| public: |
| explicit SymRef(Symbol n) |
| : name_(n) { |
| } |
| virtual ~SymRef() { |
| } |
| |
| virtual void Eval(Evaluator* ev, string* s) const override { |
| Var* v = ev->LookupVar(name_); |
| v->Eval(ev, s); |
| } |
| |
| virtual string DebugString_() const override { |
| return StringPrintf("SymRef(%s)", name_.c_str()); |
| } |
| |
| private: |
| Symbol name_; |
| }; |
| |
| class VarRef : public Value { |
| public: |
| explicit VarRef(Value* n) |
| : name_(n) { |
| } |
| virtual ~VarRef() { |
| delete name_; |
| } |
| |
| virtual void Eval(Evaluator* ev, string* s) const override { |
| ev->IncrementEvalDepth(); |
| const string&& name = name_->Eval(ev); |
| ev->DecrementEvalDepth(); |
| Var* v = ev->LookupVar(Intern(name)); |
| v->Eval(ev, s); |
| } |
| |
| virtual string DebugString_() const override { |
| return StringPrintf("VarRef(%s)", name_->DebugString().c_str()); |
| } |
| |
| private: |
| Value* name_; |
| }; |
| |
| class VarSubst : public Value { |
| public: |
| explicit VarSubst(Value* n, Value* p, Value* s) |
| : name_(n), pat_(p), subst_(s) { |
| } |
| virtual ~VarSubst() { |
| delete name_; |
| delete pat_; |
| delete subst_; |
| } |
| |
| virtual void Eval(Evaluator* ev, string* s) const override { |
| ev->IncrementEvalDepth(); |
| const string&& name = name_->Eval(ev); |
| Var* v = ev->LookupVar(Intern(name)); |
| const string&& pat_str = pat_->Eval(ev); |
| const string&& subst = subst_->Eval(ev); |
| ev->DecrementEvalDepth(); |
| const string&& value = v->Eval(ev); |
| WordWriter ww(s); |
| Pattern pat(pat_str); |
| for (StringPiece tok : WordScanner(value)) { |
| ww.MaybeAddWhitespace(); |
| pat.AppendSubstRef(tok, subst, s); |
| } |
| } |
| |
| virtual string DebugString_() const override { |
| return StringPrintf("VarSubst(%s:%s=%s)", |
| name_->DebugString().c_str(), |
| pat_->DebugString().c_str(), |
| subst_->DebugString().c_str()); |
| } |
| |
| private: |
| Value* name_; |
| Value* pat_; |
| Value* subst_; |
| }; |
| |
| class Func : public Value { |
| public: |
| explicit Func(FuncInfo* fi) |
| : fi_(fi) { |
| } |
| |
| ~Func() { |
| for (Value* a : args_) |
| delete a; |
| } |
| |
| virtual void Eval(Evaluator* ev, string* s) const override { |
| LOG("Invoke func %s(%s)", name(), JoinValues(args_, ",").c_str()); |
| ev->IncrementEvalDepth(); |
| fi_->func(args_, ev, s); |
| ev->DecrementEvalDepth(); |
| } |
| |
| virtual 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: |
| FuncInfo* fi_; |
| vector<Value*> args_; |
| }; |
| |
| static char CloseParen(char c) { |
| switch (c) { |
| case '(': |
| return ')'; |
| case '{': |
| return '}'; |
| } |
| return 0; |
| } |
| |
| static size_t SkipSpaces(StringPiece 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 = s.get(i + 1); |
| if (n != '\r' && n != '\n') |
| return i; |
| } |
| } |
| return s.size(); |
| } |
| |
| bool ShouldHandleComments(ParseExprOpt opt) { |
| return opt != ParseExprOpt::DEFINE && opt != ParseExprOpt::COMMAND; |
| } |
| |
| void ParseFunc(const Loc& loc, |
| Func* f, StringPiece s, size_t i, char* terms, |
| size_t* index_out) { |
| terms[1] = ','; |
| terms[2] = '\0'; |
| i += SkipSpaces(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 = s.get(i+1); |
| if (c == '\r' || c == '\n') |
| 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("%s:%d: *** unterminated call to function '%s': " |
| "missing '%c'.", |
| LOCF(loc), 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("%s:%d: *** insufficient number of arguments (%d) to function `%s'.", |
| LOCF(loc), nargs - 1, f->name()); |
| } |
| |
| *index_out = i; |
| return; |
| } |
| |
| Value* ParseDollar(const Loc& loc, StringPiece s, size_t* index_out) { |
| CHECK(s.size() >= 2); |
| CHECK(s[0] == '$'); |
| CHECK(s[1] != '$'); |
| |
| char cp = CloseParen(s[1]); |
| if (cp == 0) { |
| *index_out = 2; |
| return new SymRef(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 != string::npos) { |
| KATI_WARN("%s:%d: *warning*: variable lookup with '%c': %.*s", |
| loc, sym.str()[found], SPF(s)); |
| } |
| } |
| Value* r = new SymRef(sym); |
| delete lit; |
| return r; |
| } |
| return new VarRef(vname); |
| } |
| |
| if (s[i] == ' ' || s[i] == '\\') { |
| // ${func ...} |
| if (vname->IsLiteral()) { |
| Literal* lit = static_cast<Literal*>(vname); |
| if (FuncInfo* fi = GetFuncInfo(lit->val())) { |
| delete lit; |
| Func* func = new Func(fi); |
| ParseFunc(loc, func, s, i+1, terms, index_out); |
| return func; |
| } else { |
| KATI_WARN("%s:%d: *warning*: unknown make function '%.*s': %.*s", |
| loc, 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) { |
| Expr* v = new Expr; |
| v->AddValue(vname); |
| v->AddValue(new Literal(":")); |
| v->AddValue(pat); |
| *index_out = i + 1; |
| return new VarRef(v); |
| } |
| |
| 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(vname->Compact(), pat, subst); |
| } |
| |
| // GNU make accepts expressions like $((). See unmatched_paren*.mk |
| // for detail. |
| size_t found = s.find(cp); |
| if (found != string::npos) { |
| KATI_WARN("%s:%d: *warning*: unmatched parentheses: %.*s", |
| loc, SPF(s)); |
| *index_out = s.size(); |
| return new SymRef(Intern(s.substr(2, found-2))); |
| } |
| ERROR("%s:%d: *** unterminated variable reference.", LOCF(loc)); |
| } |
| } |
| |
| Value* ParseExprImpl(const Loc& loc, |
| StringPiece s, const char* terms, ParseExprOpt opt, |
| size_t* index_out, bool trim_right_space) { |
| if (s.get(s.size() - 1) == '\r') |
| s.remove_suffix(1); |
| |
| Expr* r = new Expr; |
| size_t b = 0; |
| char save_paren = 0; |
| int paren_depth = 0; |
| size_t i; |
| for (i = 0; i < s.size(); i++) { |
| char c = s[i]; |
| if (terms && strchr(terms, c) && !save_paren) { |
| break; |
| } |
| |
| // Handle a comment. |
| if (!terms && c == '#' && ShouldHandleComments(opt)) { |
| if (i > b) |
| r->AddValue(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 r->Compact(); |
| } |
| |
| if (c == '$') { |
| if (i + 1 >= s.size()) { |
| break; |
| } |
| |
| if (i > b) |
| r->AddValue(new Literal(s.substr(b, i-b))); |
| |
| if (s[i+1] == '$') { |
| r->AddValue(new Literal(StringPiece("$"))); |
| i += 1; |
| b = i + 1; |
| continue; |
| } |
| |
| if (terms && strchr(terms, s[i+1])) { |
| *index_out = i + 1; |
| return r->Compact(); |
| } |
| |
| size_t n; |
| Value* v = ParseDollar(loc, s.substr(i), &n); |
| i += n; |
| b = i; |
| i--; |
| r->AddValue(v); |
| 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)) { |
| r->AddValue(new Literal(s.substr(b, i-b))); |
| i++; |
| b = i; |
| continue; |
| } |
| if (n == '\r' || n == '\n') { |
| if (terms && strchr(terms, ' ')) { |
| break; |
| } |
| if (i > b) { |
| r->AddValue(new Literal(TrimRightSpace(s.substr(b, i-b)))); |
| } |
| r->AddValue(new Literal(StringPiece(" "))); |
| // Skip the current escaped newline |
| i += 2; |
| // Then continue skipping escaped newlines, spaces, and tabs |
| for (; i < s.size(); i++) { |
| if (s[i] == '\\' && (s.get(i+1) == '\r' || s.get(i+1) == '\n')) { |
| i++; |
| continue; |
| } |
| if (s[i] != ' ' && s[i] != '\t') { |
| break; |
| } |
| } |
| b = i; |
| i--; |
| } |
| } |
| } |
| |
| if (i > b) { |
| StringPiece rest = s.substr(b, i-b); |
| if (trim_right_space) |
| rest = TrimRightSpace(rest); |
| if (!rest.empty()) |
| r->AddValue(new Literal(rest)); |
| } |
| *index_out = i; |
| return r->Compact(); |
| } |
| |
| Value* ParseExpr(const Loc& loc, StringPiece s, ParseExprOpt opt) { |
| size_t n; |
| return ParseExprImpl(loc, s, NULL, opt, &n); |
| } |
| |
| string JoinValues(const vector<Value*>& vals, const char* sep) { |
| vector<string> val_strs; |
| for (Value* v : vals) { |
| val_strs.push_back(v->DebugString()); |
| } |
| return JoinStrings(val_strs, sep); |
| } |
| |
| Value* NewExpr2(Value* v1, Value* v2) { |
| Expr* e = new Expr(); |
| e->AddValue(v1); |
| e->AddValue(v2); |
| return e; |
| } |
| |
| Value* NewExpr3(Value* v1, Value* v2, Value* v3) { |
| Expr* e = new Expr(); |
| e->AddValue(v1); |
| e->AddValue(v2); |
| e->AddValue(v3); |
| return e; |
| } |
| |
| Value* NewLiteral(StringPiece s) { |
| return new Literal(s); |
| } |