1 /*
2  *  OpenSCAD (www.openscad.org)
3  *  Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
4  *                          Marius Kintel <marius@kintel.net>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  As a special exception, you have permission to link this program
12  *  with the CGAL library and distribute executables, as long as you
13  *  follow the requirements of the GNU GPL in regard to all of the
14  *  software in the executable aside from CGAL.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  *
25  */
26 
27 #include "compiler_specific.h"
28 #include "context.h"
29 #include "evalcontext.h"
30 #include "expression.h"
31 #include "function.h"
32 #include "UserModule.h"
33 #include "ModuleInstantiation.h"
34 #include "builtin.h"
35 #include "printutils.h"
36 #include <boost/filesystem.hpp>
37 #include "boost-utils.h"
38 namespace fs = boost::filesystem;
39 
40 // $children is not a config_variable. config_variables have dynamic scope,
41 // meaning they are passed down the call chain implicitly.
42 // $children is simply misnamed and shouldn't have included the '$'.
is_config_variable(const std::string & name)43 static bool is_config_variable(const std::string &name)
44 {
45 	return name[0] == '$' && name != "$children";
46 }
47 
48 /*!
49 	Initializes this context. Optionally initializes a context for an
50 	external library. Note that if parent is null, a new stack will be
51 	created, and all children will share the root parent's stack.
52 */
Context(const std::shared_ptr<Context> parent)53 Context::Context(const std::shared_ptr<Context> parent) : parent(parent)
54 {
55 	if (parent) {
56 		assert(parent->ctx_stack && "Parent context stack was null!");
57 		this->ctx_stack = parent->ctx_stack;
58 		this->document_path = parent->document_path;
59 	}
60 	else {
61 		this->ctx_stack = new Stack;
62 		this->document_path = std::make_shared<std::string>();
63 	}
64 }
65 
~Context()66 Context::~Context()
67 {
68 	if (!parent) delete this->ctx_stack;
69 }
70 
push(std::shared_ptr<Context> ctx)71 void Context::push(std::shared_ptr<Context> ctx)
72 {
73 	this->ctx_stack->push_back(ctx);
74 }
75 
pop()76 void Context::pop()
77 {
78 	assert(this->ctx_stack && "Context stack was null at destruction!");
79 	this->ctx_stack->pop_back();
80 }
81 
82 /*!
83 	Initialize context from a module argument list and a evaluation context
84 	which may pass variables which will be preferred over default values.
85 */
setVariables(const std::shared_ptr<EvalContext> & evalctx,const AssignmentList & args,const AssignmentList & optargs,bool usermodule)86 void Context::setVariables(const std::shared_ptr<EvalContext> &evalctx, const AssignmentList &args, const AssignmentList &optargs, bool usermodule)
87 {
88 	// Set any default values
89 	for (const auto &arg : args) {
90 		// FIXME should we just not set value if arg.expr is false?
91 		set_variable(arg->getName(), arg->getExpr() ? arg->getExpr()->evaluate(this->parent) : Value::undefined.clone());
92 	}
93 
94 	if (evalctx) {
95 		auto assignments = evalctx->resolveArguments(args, optargs, usermodule && !OpenSCAD::parameterCheck);
96 		for (const auto &ass : assignments) {
97 			this->set_variable(ass.first, ass.second->evaluate(evalctx));
98 		}
99 	}
100 }
101 
102 // sink for value takes &&
set_variable(const std::string & name,Value && value)103 void Context::set_variable(const std::string &name, Value&& value)
104 {
105 	if (is_config_variable(name)) {
106 		this->config_variables.insert_or_assign(name, std::move(value));
107 	} else {
108 		this->variables.insert_or_assign(name, std::move(value));
109 	}
110 }
111 
112 // sink for value takes &&
set_constant(const std::string & name,Value && value)113 void Context::set_constant(const std::string &name, Value&& value)
114 {
115 	if (this->constants.contains(name)) {
116 		LOG(message_group::Warning,Location::NONE,"","Attempt to modify constant '%1$s'.",name);
117 	}
118 	else {
119 		this->constants.emplace(name, std::move(value));
120 	}
121 }
122 
apply_variables(const std::shared_ptr<Context> & other)123 void Context::apply_variables(const std::shared_ptr<Context> &other)
124 {
125 	this->variables.applyFrom(other->variables);
126 }
127 
128 /*!
129   Apply config variables of 'other' to this context, from the full context stack of 'other', bottom-up.
130 */
apply_config_variables(const std::shared_ptr<Context> & other)131 void Context::apply_config_variables(const std::shared_ptr<Context> &other)
132 {
133 	if (other.get() == this) {
134 		// Anything in 'other' and its ancestors is already part of this context, no need to descend any further.
135 		return;
136 	}
137 	if (other->parent) {
138 		// Assign parent's variables first, since they might be overridden by a child
139 		apply_config_variables(other->parent);
140 	}
141 	this->config_variables.applyFrom(other->config_variables);
142 }
143 
lookup_variable(const std::string & name,bool silent,const Location & loc) const144 const Value& Context::lookup_variable(const std::string &name, bool silent, const Location &loc) const
145 {
146 	assert(this->ctx_stack && "Context had null stack in lookup_variable()!!");
147 	ValueMap::const_iterator result;
148 	if (is_config_variable(name)) {
149 		for (int i = this->ctx_stack->size()-1; i >= 0; i--) {
150 			const auto &confvars = ctx_stack->at(i)->config_variables;
151 			if ((result = confvars.find(name)) != confvars.end()) {
152 				return result->second;
153 			}
154 		}
155 		if (!silent) {
156 			LOG(message_group::Warning,loc,this->documentPath(),"Ignoring unknown variable '%1$s'",name);
157 		}
158 		return Value::undefined;
159 	}
160 	if (!this->parent) {
161 			if ((result = this->constants.find(name)) != this->constants.end()) {
162 				return result->second;
163 			}
164 	}
165 	if ((result = this->variables.find(name)) != this->variables.end()) {
166 		return result->second;
167 	}
168 	if (this->parent) {
169 		return this->parent->lookup_variable(name, silent, loc);
170 	}
171 	if (!silent) {
172 		LOG(message_group::Warning,loc,this->documentPath(),"Ignoring unknown variable '%1$s'",name);
173 	}
174 	return Value::undefined;
175 }
176 
177 
lookup_variable_with_default(const std::string & variable,const double & def,const Location & loc) const178 double Context::lookup_variable_with_default(const std::string &variable, const double &def, const Location &loc) const
179 {
180 	const Value& v = this->lookup_variable(variable, true, loc);
181 	return (v.type() == Value::Type::NUMBER) ? v.toDouble() : def;
182 }
183 
lookup_variable_with_default(const std::string & variable,const std::string & def,const Location & loc) const184 const std::string& Context::lookup_variable_with_default(const std::string &variable, const std::string &def, const Location &loc) const
185 {
186 	const Value& v = this->lookup_variable(variable, true, loc);
187 	return (v.type() == Value::Type::STRING) ? v.toStrUtf8Wrapper().toString() : def;
188 }
189 
lookup_local_config_variable(const std::string & name) const190 Value Context::lookup_local_config_variable(const std::string &name) const
191 {
192 	if (is_config_variable(name)) {
193   	ValueMap::const_iterator result;
194 		if ((result = config_variables.find(name)) != config_variables.end()) {
195 			return result->second.clone();
196 		}
197 	}
198 	return Value::undefined.clone();
199 }
200 
has_local_variable(const std::string & name) const201 bool Context::has_local_variable(const std::string &name) const
202 {
203 	if (is_config_variable(name)) {
204 		return config_variables.find(name) != config_variables.end();
205 	}
206 	if (!parent && constants.find(name) != constants.end()) {
207 		return true;
208 	}
209 	return variables.find(name) != variables.end();
210 }
211 
212 /**
213  * This is separated because PRINTB uses quite a lot of stack space
214  * and the methods using it evaluate_function() and instantiate_module()
215  * are called often when recursive functions or modules are evaluated.
216  * noinline prevents compiler optimization, as we here specifically
217  * optimize for stack usage during normal operating, not runtime during
218  * error handling.
219  *
220  * @param what what is ignored
221  * @param name name of the ignored object
222  * @param loc location of the function/module call
223  * @param docPath document path of the root file, used to calculate the relative path
224  */
print_ignore_warning(const char * what,const char * name,const Location & loc,const char * docPath)225 static void NOINLINE print_ignore_warning(const char *what, const char *name, const Location &loc, const char *docPath){
226 	LOG(message_group::Warning,loc,docPath,"Ignoring unknown %1$s '%2$s'",what,name);
227 }
228 
evaluate_function(const std::string & name,const std::shared_ptr<EvalContext> & evalctx) const229 Value Context::evaluate_function(const std::string &name, const std::shared_ptr<EvalContext>& evalctx) const
230 {
231 	if (this->parent) return this->parent->evaluate_function(name, evalctx);
232 	print_ignore_warning("function", name.c_str(),evalctx->loc,this->documentPath().c_str());
233 	return Value::undefined.clone();
234 }
235 
instantiate_module(const ModuleInstantiation & inst,const std::shared_ptr<EvalContext> & evalctx) const236 AbstractNode *Context::instantiate_module(const ModuleInstantiation &inst, const std::shared_ptr<EvalContext>& evalctx) const
237 {
238 	if (this->parent) return this->parent->instantiate_module(inst, evalctx);
239 	print_ignore_warning("module", inst.name().c_str(),evalctx->loc,this->documentPath().c_str());
240 	return nullptr;
241 }
242 
243 /*!
244 	Returns the absolute path to the given filename, unless it's empty.
245  */
getAbsolutePath(const std::string & filename) const246 std::string Context::getAbsolutePath(const std::string &filename) const
247 {
248 	if (!filename.empty() && !fs::path(filename).is_absolute()) {
249 		return fs::absolute(fs::path(*this->document_path) / filename).string();
250 	}
251 	else {
252 		return filename;
253 	}
254 }
255 
256 #ifdef DEBUG
dump(const AbstractModule * mod,const ModuleInstantiation * inst)257 std::string Context::dump(const AbstractModule *mod, const ModuleInstantiation *inst)
258 {
259 	std::ostringstream s;
260 	if (inst) {
261 		s << boost::format("ModuleContext %p (%p) for %s inst (%p)\n") % this % this->parent % inst->name() % inst;
262 	}
263 	else {
264 		s << boost::format("Context: %p (%p)\n") % this % this->parent;
265 	}
266 	s << boost::format("  document path: %s\n") % *this->document_path;
267 	if (mod) {
268 		const UserModule *m = dynamic_cast<const UserModule*>(mod);
269 		if (m) {
270 			s << "  module args:";
271 			for(const auto &arg : m->definition_arguments) {
272 				s << boost::format("    %s = %s\n") % arg->getName() % variables.get(arg->getName());
273 			}
274 		}
275 	}
276 	s << "  vars:\n";
277 	for(const auto &v : constants) {
278 		s << boost::format("    %s = %s\n") % v.first % v.second.toEchoString();
279 	}
280 	for(const auto &v : variables) {
281 		s << boost::format("    %s = %s\n") % v.first % v.second.toEchoString();
282 	}
283 	for(const auto &v : config_variables) {
284 		s << boost::format("    %s = %s\n") % v.first % v.second.toEchoString();
285 	}
286 	return s.str();
287 }
288 #endif
289 
290