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