aboutsummaryrefslogtreecommitdiffstats
path: root/src/expr.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/expr.cc')
-rw-r--r--src/expr.cc591
1 files changed, 591 insertions, 0 deletions
diff --git a/src/expr.cc b/src/expr.cc
new file mode 100644
index 0000000..e0f5be1
--- /dev/null
+++ b/src/expr.cc
@@ -0,0 +1,591 @@
+// 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 Value* v) {
+ return v ? NoLineBreak(v->DebugString_()) : "(null)";
+}
+
+class Literal : public Value {
+ public:
+ explicit Literal(StringPiece s) : s_(s) {}
+
+ StringPiece val() const { return s_; }
+
+ virtual void Eval(Evaluator* ev, string* s) const override {
+ ev->CheckStack();
+ 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 ValueList : public Value {
+ public:
+ ValueList() {}
+
+ ValueList(Value* v1, Value* v2, Value* v3) : ValueList() {
+ vals_.reserve(3);
+ vals_.push_back(v1);
+ vals_.push_back(v2);
+ vals_.push_back(v3);
+ }
+
+ ValueList(Value* v1, Value* v2) : ValueList() {
+ vals_.reserve(2);
+ vals_.push_back(v1);
+ vals_.push_back(v2);
+ }
+
+ ValueList(vector<Value*>* values) : ValueList() {
+ values->shrink_to_fit();
+ values->swap(vals_);
+ }
+
+ virtual ~ValueList() {
+ for (Value* v : vals_) {
+ delete v;
+ }
+ }
+
+ virtual void Eval(Evaluator* ev, string* s) const override {
+ ev->CheckStack();
+ for (Value* v : vals_) {
+ v->Eval(ev, s);
+ }
+ }
+
+ virtual string DebugString_() const override {
+ string r;
+ for (Value* v : vals_) {
+ if (r.empty()) {
+ r += "ValueList(";
+ } else {
+ r += ", ";
+ }
+ r += DebugString(v);
+ }
+ if (!r.empty())
+ r += ")";
+ 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 {
+ ev->CheckStack();
+ Var* v = ev->LookupVar(name_);
+ v->Used(ev, 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->CheckStack();
+ ev->IncrementEvalDepth();
+ const string&& name = name_->Eval(ev);
+ ev->DecrementEvalDepth();
+ Symbol sym = Intern(name);
+ Var* v = ev->LookupVar(sym);
+ v->Used(ev, sym);
+ v->Eval(ev, s);
+ }
+
+ virtual string DebugString_() const override {
+ return StringPrintf("VarRef(%s)", Value::DebugString(name_).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->CheckStack();
+ ev->IncrementEvalDepth();
+ const string&& name = name_->Eval(ev);
+ Symbol sym = Intern(name);
+ Var* v = ev->LookupVar(sym);
+ const string&& pat_str = pat_->Eval(ev);
+ const string&& subst = subst_->Eval(ev);
+ ev->DecrementEvalDepth();
+ v->Used(ev, sym);
+ 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)", 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:
+ explicit Func(FuncInfo* fi) : fi_(fi) {}
+
+ ~Func() {
+ for (Value* a : args_)
+ delete a;
+ }
+
+ virtual void Eval(Evaluator* ev, string* s) const override {
+ ev->CheckStack();
+ 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();
+}
+
+Value* Value::NewExpr(Value* v1, Value* v2) {
+ return new ValueList(v1, v2);
+}
+
+Value* Value::NewExpr(Value* v1, Value* v2, Value* v3) {
+ return new ValueList(v1, v2, v3);
+}
+
+Value* Value::NewExpr(vector<Value*>* values) {
+ if (values->size() == 1) {
+ Value* v = (*values)[0];
+ values->clear();
+ return v;
+ }
+ return new ValueList(values);
+}
+
+Value* Value::NewLiteral(StringPiece s) {
+ return new Literal(s);
+}
+
+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_LOC(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(loc,
+ "*** insufficient number of arguments (%d) to function `%s'.",
+ 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_LOC(loc, "*warning*: variable lookup with '%c': %.*s",
+ 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_LOC(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(Value::NewExpr(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(vname, 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_LOC(loc, "*warning*: unmatched parentheses: %.*s", SPF(s));
+ *index_out = s.size();
+ return new SymRef(Intern(s.substr(2, found - 2)));
+ }
+ ERROR_LOC(loc, "*** unterminated variable reference.");
+ }
+}
+
+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);
+
+ size_t b = 0;
+ char save_paren = 0;
+ int paren_depth = 0;
+ size_t i;
+ vector<Value*> list;
+ 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)
+ 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(&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(StringPiece("$")));
+ i += 1;
+ b = i + 1;
+ continue;
+ }
+
+ if (terms && strchr(terms, s[i + 1])) {
+ *index_out = i + 1;
+ return Value::NewExpr(&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') {
+ if (terms && strchr(terms, ' ')) {
+ break;
+ }
+ if (i > b) {
+ list.push_back(new Literal(TrimRightSpace(s.substr(b, i - b))));
+ }
+ list.push_back(new Literal(StringPiece(" ")));
+ // Skip the current escaped newline
+ i += 2;
+ if (n == '\r' && s.get(i) == '\n')
+ i++;
+ // 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())
+ list.push_back(new Literal(rest));
+ }
+ *index_out = i;
+ return Value::NewExpr(&list);
+}
+
+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(Value::DebugString(v));
+ }
+ return JoinStrings(val_strs, sep);
+}