389 lines
10 KiB
C++
389 lines
10 KiB
C++
// 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 "eval.h"
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include "expr.h"
|
|
#include "file.h"
|
|
#include "file_cache.h"
|
|
#include "fileutil.h"
|
|
#include "parser.h"
|
|
#include "rule.h"
|
|
#include "stmt.h"
|
|
#include "strutil.h"
|
|
#include "symtab.h"
|
|
#include "var.h"
|
|
|
|
Evaluator::Evaluator()
|
|
: last_rule_(NULL),
|
|
current_scope_(NULL),
|
|
avoid_io_(false),
|
|
eval_depth_(0),
|
|
posix_sym_(Intern(".POSIX")),
|
|
is_posix_(false),
|
|
kati_readonly_(Intern(".KATI_READONLY")) {
|
|
}
|
|
|
|
Evaluator::~Evaluator() {
|
|
// delete vars_;
|
|
// for (auto p : rule_vars) {
|
|
// delete p.second;
|
|
// }
|
|
}
|
|
|
|
Var* Evaluator::EvalRHS(Symbol lhs, Value* rhs_v, StringPiece orig_rhs,
|
|
AssignOp op, bool is_override) {
|
|
VarOrigin origin = (
|
|
(is_bootstrap_ ? VarOrigin::DEFAULT :
|
|
is_commandline_ ? VarOrigin::COMMAND_LINE :
|
|
is_override ? VarOrigin::OVERRIDE : VarOrigin::FILE));
|
|
|
|
Var* rhs = NULL;
|
|
bool needs_assign = true;
|
|
switch (op) {
|
|
case AssignOp::COLON_EQ: {
|
|
SimpleVar* sv = new SimpleVar(origin);
|
|
rhs_v->Eval(this, sv->mutable_value());
|
|
rhs = sv;
|
|
break;
|
|
}
|
|
case AssignOp::EQ:
|
|
rhs = new RecursiveVar(rhs_v, origin, orig_rhs);
|
|
break;
|
|
case AssignOp::PLUS_EQ: {
|
|
Var* prev = LookupVarInCurrentScope(lhs);
|
|
if (!prev->IsDefined()) {
|
|
rhs = new RecursiveVar(rhs_v, origin, orig_rhs);
|
|
} else if (prev->ReadOnly()) {
|
|
Error(StringPrintf("*** cannot assign to readonly variable: %s", lhs.c_str()));
|
|
} else {
|
|
prev->AppendVar(this, rhs_v);
|
|
rhs = prev;
|
|
needs_assign = false;
|
|
}
|
|
break;
|
|
}
|
|
case AssignOp::QUESTION_EQ: {
|
|
Var* prev = LookupVarInCurrentScope(lhs);
|
|
if (!prev->IsDefined()) {
|
|
rhs = new RecursiveVar(rhs_v, origin, orig_rhs);
|
|
} else {
|
|
rhs = prev;
|
|
needs_assign = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
LOG("Assign: %s=%s", lhs.c_str(), rhs->DebugString().c_str());
|
|
if (needs_assign) {
|
|
return rhs;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void Evaluator::EvalAssign(const AssignStmt* stmt) {
|
|
loc_ = stmt->loc();
|
|
last_rule_ = NULL;
|
|
Symbol lhs = stmt->GetLhsSymbol(this);
|
|
if (lhs.empty())
|
|
Error("*** empty variable name.");
|
|
|
|
if (lhs == kati_readonly_) {
|
|
string rhs;
|
|
stmt->rhs->Eval(this, &rhs);
|
|
for (auto const& name : WordScanner(rhs)) {
|
|
Var* var = Intern(name).GetGlobalVar();
|
|
if (!var->IsDefined()) {
|
|
Error(StringPrintf("*** unknown variable: %s", name.as_string().c_str()));
|
|
}
|
|
var->SetReadOnly();
|
|
}
|
|
return;
|
|
}
|
|
|
|
Var* rhs = EvalRHS(lhs, stmt->rhs, stmt->orig_rhs, stmt->op,
|
|
stmt->directive == AssignDirective::OVERRIDE);
|
|
if (rhs) {
|
|
bool readonly;
|
|
lhs.SetGlobalVar(rhs,
|
|
stmt->directive == AssignDirective::OVERRIDE,
|
|
&readonly);
|
|
if (readonly) {
|
|
Error(StringPrintf("*** cannot assign to readonly variable: %s", lhs.c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Evaluator::EvalRule(const RuleStmt* stmt) {
|
|
loc_ = stmt->loc();
|
|
last_rule_ = NULL;
|
|
|
|
const string&& expr = stmt->expr->Eval(this);
|
|
// See semicolon.mk.
|
|
if (expr.find_first_not_of(" \t;") == string::npos) {
|
|
if (stmt->term == ';')
|
|
Error("*** missing rule before commands.");
|
|
return;
|
|
}
|
|
|
|
Rule* rule;
|
|
RuleVarAssignment rule_var;
|
|
function<string()> after_term_fn = [this, stmt](){
|
|
return stmt->after_term ? stmt->after_term->Eval(this) : "";
|
|
};
|
|
ParseRule(loc_, expr, stmt->term, after_term_fn, &rule, &rule_var);
|
|
|
|
if (rule) {
|
|
if (stmt->term == ';') {
|
|
rule->cmds.push_back(stmt->after_term);
|
|
}
|
|
|
|
for (Symbol o : rule->outputs) {
|
|
if (o == posix_sym_)
|
|
is_posix_ = true;
|
|
}
|
|
|
|
LOG("Rule: %s", rule->DebugString().c_str());
|
|
rules_.push_back(rule);
|
|
last_rule_ = rule;
|
|
return;
|
|
}
|
|
|
|
Symbol lhs = Intern(rule_var.lhs);
|
|
for (Symbol output : rule_var.outputs) {
|
|
auto p = rule_vars_.emplace(output, nullptr);
|
|
if (p.second) {
|
|
p.first->second = new Vars;
|
|
}
|
|
|
|
Value* rhs = stmt->after_term;
|
|
if (!rule_var.rhs.empty()) {
|
|
Value* lit = NewLiteral(rule_var.rhs);
|
|
if (rhs) {
|
|
// TODO: We always insert two whitespaces around the
|
|
// terminator. Preserve whitespaces properly.
|
|
if (stmt->term == ';') {
|
|
rhs = NewExpr3(lit, NewLiteral(StringPiece(" ; ")), rhs);
|
|
} else {
|
|
rhs = NewExpr3(lit, NewLiteral(StringPiece(" = ")), rhs);
|
|
}
|
|
} else {
|
|
rhs = lit;
|
|
}
|
|
}
|
|
|
|
current_scope_ = p.first->second;
|
|
|
|
if (lhs == kati_readonly_) {
|
|
string rhs_value;
|
|
rhs->Eval(this, &rhs_value);
|
|
for (auto const& name : WordScanner(rhs_value)) {
|
|
Var* var = current_scope_->Lookup(Intern(name));
|
|
if (!var->IsDefined()) {
|
|
Error(StringPrintf("*** unknown variable: %s", name.as_string().c_str()));
|
|
}
|
|
var->SetReadOnly();
|
|
}
|
|
current_scope_ = NULL;
|
|
continue;
|
|
}
|
|
|
|
Var* rhs_var = EvalRHS(lhs, rhs, StringPiece("*TODO*"), rule_var.op);
|
|
if (rhs_var) {
|
|
bool readonly;
|
|
current_scope_->Assign(lhs, new RuleVar(rhs_var, rule_var.op), &readonly);
|
|
if (readonly) {
|
|
Error(StringPrintf("*** cannot assign to readonly variable: %s", lhs.c_str()));
|
|
}
|
|
}
|
|
current_scope_ = NULL;
|
|
}
|
|
}
|
|
|
|
void Evaluator::EvalCommand(const CommandStmt* stmt) {
|
|
loc_ = stmt->loc();
|
|
|
|
if (!last_rule_) {
|
|
vector<Stmt*> stmts;
|
|
ParseNotAfterRule(stmt->orig, stmt->loc(), &stmts);
|
|
for (Stmt* a : stmts)
|
|
a->Eval(this);
|
|
return;
|
|
}
|
|
|
|
last_rule_->cmds.push_back(stmt->expr);
|
|
if (last_rule_->cmd_lineno == 0)
|
|
last_rule_->cmd_lineno = stmt->loc().lineno;
|
|
LOG("Command: %s", stmt->expr->DebugString().c_str());
|
|
}
|
|
|
|
void Evaluator::EvalIf(const IfStmt* stmt) {
|
|
loc_ = stmt->loc();
|
|
|
|
bool is_true;
|
|
switch (stmt->op) {
|
|
case CondOp::IFDEF:
|
|
case CondOp::IFNDEF: {
|
|
string var_name;
|
|
stmt->lhs->Eval(this, &var_name);
|
|
Symbol lhs = Intern(TrimRightSpace(var_name));
|
|
if (lhs.str().find_first_of(" \t") != string::npos)
|
|
Error("*** invalid syntax in conditional.");
|
|
Var* v = LookupVarInCurrentScope(lhs);
|
|
is_true = (v->String().empty() == (stmt->op == CondOp::IFNDEF));
|
|
break;
|
|
}
|
|
case CondOp::IFEQ:
|
|
case CondOp::IFNEQ: {
|
|
const string&& lhs = stmt->lhs->Eval(this);
|
|
const string&& rhs = stmt->rhs->Eval(this);
|
|
is_true = ((lhs == rhs) == (stmt->op == CondOp::IFEQ));
|
|
break;
|
|
}
|
|
default:
|
|
CHECK(false);
|
|
abort();
|
|
}
|
|
|
|
const vector<Stmt*>* stmts;
|
|
if (is_true) {
|
|
stmts = &stmt->true_stmts;
|
|
} else {
|
|
stmts = &stmt->false_stmts;
|
|
}
|
|
for (Stmt* a : *stmts) {
|
|
LOG("%s", a->DebugString().c_str());
|
|
a->Eval(this);
|
|
}
|
|
}
|
|
|
|
void Evaluator::DoInclude(const string& fname) {
|
|
Makefile* mk = MakefileCacheManager::Get()->ReadMakefile(fname);
|
|
if (!mk->Exists()) {
|
|
Error(StringPrintf("%s does not exist", fname.c_str()));
|
|
}
|
|
|
|
Var* var_list = LookupVar(Intern("MAKEFILE_LIST"));
|
|
var_list->AppendVar(this, NewLiteral(Intern(TrimLeadingCurdir(fname)).str()));
|
|
for (Stmt* stmt : mk->stmts()) {
|
|
LOG("%s", stmt->DebugString().c_str());
|
|
stmt->Eval(this);
|
|
}
|
|
}
|
|
|
|
void Evaluator::EvalInclude(const IncludeStmt* stmt) {
|
|
loc_ = stmt->loc();
|
|
last_rule_ = NULL;
|
|
|
|
const string&& pats = stmt->expr->Eval(this);
|
|
for (StringPiece pat : WordScanner(pats)) {
|
|
ScopedTerminator st(pat);
|
|
vector<string>* files;
|
|
Glob(pat.data(), &files);
|
|
|
|
if (stmt->should_exist) {
|
|
if (files->empty()) {
|
|
// TODO: Kati does not support building a missing include file.
|
|
Error(StringPrintf("%s: %s", pat.data(), strerror(errno)));
|
|
}
|
|
}
|
|
|
|
for (const string& fname : *files) {
|
|
if (!stmt->should_exist && g_flags.ignore_optional_include_pattern &&
|
|
Pattern(g_flags.ignore_optional_include_pattern).Match(fname)) {
|
|
continue;
|
|
}
|
|
DoInclude(fname);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Evaluator::EvalExport(const ExportStmt* stmt) {
|
|
loc_ = stmt->loc();
|
|
last_rule_ = NULL;
|
|
|
|
const string&& exports = stmt->expr->Eval(this);
|
|
for (StringPiece tok : WordScanner(exports)) {
|
|
size_t equal_index = tok.find('=');
|
|
if (equal_index == string::npos) {
|
|
exports_[Intern(tok)] = stmt->is_export;
|
|
} else if (equal_index == 0 ||
|
|
(equal_index == 1 &&
|
|
(tok[0] == ':' || tok[0] == '?' || tok[0] == '+'))) {
|
|
// Do not export tokens after an assignment.
|
|
break;
|
|
} else {
|
|
StringPiece lhs, rhs;
|
|
AssignOp op;
|
|
ParseAssignStatement(tok, equal_index, &lhs, &rhs, &op);
|
|
exports_[Intern(lhs)] = stmt->is_export;
|
|
}
|
|
}
|
|
}
|
|
|
|
Var* Evaluator::LookupVarGlobal(Symbol name) {
|
|
Var* v = name.GetGlobalVar();
|
|
if (v->IsDefined())
|
|
return v;
|
|
used_undefined_vars_.insert(name);
|
|
return v;
|
|
}
|
|
|
|
Var* Evaluator::LookupVar(Symbol name) {
|
|
if (current_scope_) {
|
|
Var* v = current_scope_->Lookup(name);
|
|
if (v->IsDefined())
|
|
return v;
|
|
}
|
|
return LookupVarGlobal(name);
|
|
}
|
|
|
|
Var* Evaluator::LookupVarInCurrentScope(Symbol name) {
|
|
if (current_scope_) {
|
|
return current_scope_->Lookup(name);
|
|
}
|
|
return LookupVarGlobal(name);
|
|
}
|
|
|
|
string Evaluator::EvalVar(Symbol name) {
|
|
return LookupVar(name)->Eval(this);
|
|
}
|
|
|
|
string Evaluator::GetShell() {
|
|
return EvalVar(kShellSym);
|
|
}
|
|
|
|
string Evaluator::GetShellFlag() {
|
|
// TODO: Handle $(.SHELLFLAGS)
|
|
return is_posix_ ? "-ec" : "-c";
|
|
}
|
|
|
|
string Evaluator::GetShellAndFlag() {
|
|
string shell = GetShell();
|
|
shell += ' ';
|
|
shell += GetShellFlag();
|
|
return shell;
|
|
}
|
|
|
|
void Evaluator::Error(const string& msg) {
|
|
ERROR_LOC(loc_, "%s", msg.c_str());
|
|
}
|
|
|
|
unordered_set<Symbol> Evaluator::used_undefined_vars_;
|