diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/MainWindow.h | 11 | ||||
-rw-r--r-- | src/ModuleCache.cc | 105 | ||||
-rw-r--r-- | src/ModuleCache.h | 23 | ||||
-rw-r--r-- | src/context.cc | 1 | ||||
-rw-r--r-- | src/lexer.l | 20 | ||||
-rw-r--r-- | src/mainwin.cc | 305 | ||||
-rw-r--r-- | src/module.cc | 33 | ||||
-rw-r--r-- | src/module.h | 13 | ||||
-rw-r--r-- | src/openscad.cc | 34 | ||||
-rw-r--r-- | src/openscad.h | 2 | ||||
-rw-r--r-- | src/parser.y | 121 |
11 files changed, 393 insertions, 275 deletions
diff --git a/src/MainWindow.h b/src/MainWindow.h index 226689a..ed12f34 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -27,10 +27,10 @@ public: double tval, fps, fsteps; QTimer *autoReloadTimer; - QString autoReloadInfo; + std::string autoReloadId; Context root_ctx; - AbstractModule *root_module; // Result of parsing + Module *root_module; // Result of parsing ModuleInstantiation root_inst; // Top level instance AbstractNode *absolute_root_node; // Result of tree evaluation AbstractNode *root_node; // Root if the root modifier (!) is used @@ -73,9 +73,12 @@ private slots: private: void openFile(const QString &filename); - void load(); + void refreshDocument(); AbstractNode *find_root_tag(AbstractNode *n); - void compile(bool procevents); + void updateTemporalVariables(); + bool fileChangedOnDisk(); + bool compileTopLevelDocument(bool reload); + bool compile(bool reload, bool procevents); void compileCSG(bool procevents); bool maybeSave(); bool checkModified(); diff --git a/src/ModuleCache.cc b/src/ModuleCache.cc new file mode 100644 index 0000000..e974a03 --- /dev/null +++ b/src/ModuleCache.cc @@ -0,0 +1,105 @@ +#include "ModuleCache.h" +#include "module.h" +#include "printutils.h" +#include "openscad.h" + +#include "boosty.h" +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> + +#include <stdio.h> +#include <fstream> +#include <sstream> +#include <time.h> +#include <sys/stat.h> + +/*! + FIXME: Implement an LRU scheme to avoid having an ever-growing module cache +*/ + +ModuleCache *ModuleCache::inst = NULL; + +static bool is_modified(const std::string &filename, const time_t &mtime) +{ + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(filename.c_str(), &st); + return (st.st_mtime > mtime); +} + +Module *ModuleCache::evaluate(const std::string &filename) +{ + Module *lib_mod = NULL; + + // Create cache ID + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(filename.c_str(), &st); + + std::string cache_id = str(boost::format("%x.%x") % st.st_mtime % st.st_size); + + // Lookup in cache + if (this->entries.find(filename) != this->entries.end() && + this->entries[filename].cache_id == cache_id) { +#ifdef DEBUG + PRINTB("Using cached library: %s (%s)", filename % cache_id); +#endif + lib_mod = &(*this->entries[filename].module); + + BOOST_FOREACH(const Module::IncludeContainer::value_type &item, lib_mod->includes) { + if (is_modified(item.first, item.second)) { + lib_mod = NULL; + break; + } + } + } + + // If cache lookup failed (non-existing or old timestamp), compile module + if (!lib_mod) { +#ifdef DEBUG + if (this->entries.find(filename) != this->entries.end()) { + PRINTB("Recompiling cached library: %s (%s)", filename % cache_id); + } + else { + PRINTB("Compiling library '%s'.", filename); + } +#endif + + std::ifstream ifs(filename.c_str()); + if (!ifs.is_open()) { + PRINTB("WARNING: Can't open library file '%s'\n", filename); + return NULL; + } + std::string text((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); + + print_messages_push(); + + cache_entry e = { NULL, cache_id }; + if (this->entries.find(filename) != this->entries.end()) { + delete this->entries[filename].module; + } + this->entries[filename] = e; + + std::string pathname = boosty::stringy(fs::path(filename).parent_path()); + lib_mod = dynamic_cast<Module*>(parse(text.c_str(), pathname.c_str(), 0)); + + if (lib_mod) { + this->entries[filename].module = lib_mod; + } else { + this->entries.erase(filename); + } + + print_messages_pop(); + } + + if (lib_mod) lib_mod->handleDependencies(); + + return lib_mod; +} + +void ModuleCache::clear() +{ + this->entries.clear(); +} + diff --git a/src/ModuleCache.h b/src/ModuleCache.h new file mode 100644 index 0000000..1e6373d --- /dev/null +++ b/src/ModuleCache.h @@ -0,0 +1,23 @@ +#include <string> +#include <boost/unordered_map.hpp> + +class ModuleCache +{ +public: + static ModuleCache *instance() { if (!inst) inst = new ModuleCache; return inst; } + class Module *evaluate(const std::string &filename); + size_t size() { return this->entries.size(); } + void clear(); + +private: + ModuleCache() {} + ~ModuleCache() {} + + static ModuleCache *inst; + + struct cache_entry { + class Module *module; + std::string cache_id; + }; + boost::unordered_map<std::string, cache_entry> entries; +}; diff --git a/src/context.cc b/src/context.cc index b9e685c..f96a45b 100644 --- a/src/context.cc +++ b/src/context.cc @@ -162,6 +162,7 @@ AbstractNode *Context::evaluate_module(const ModuleInstantiation &inst) const } if (this->usedlibs_p) { BOOST_FOREACH(const ModuleContainer::value_type &m, *this->usedlibs_p) { + assert(m.second); if (m.second->modules.find(inst.name()) != m.second->modules.end()) { Context ctx(this->parent, m.second); return m.second->modules[inst.name()]->evaluate(&ctx, &inst); diff --git a/src/lexer.l b/src/lexer.l index 5644ded..884275e 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -30,6 +30,7 @@ #include "printutils.h" #include "parsersettings.h" #include "parser_yacc.h" +#include "module.h" #include <assert.h> #include <boost/foreach.hpp> #include <boost/lexical_cast.hpp> @@ -52,7 +53,8 @@ int lexerget_lineno(void); static void yyunput(int, char*) __attribute__((unused)); #endif extern const char *parser_input_buffer; -extern const char *parser_source_path; +extern std::string parser_source_path; +extern Module *currmodule; #define YY_INPUT(buf,result,max_size) { \ if (yyin && yyin != stdin) { \ @@ -108,6 +110,7 @@ use[ \t\r\n>]*"<" { BEGIN(cond_use); } <cond_use>{ [^\t\r\n>]+ { filename = yytext; } ">" { + PRINTB("USE: %s", filename); BEGIN(INITIAL); fs::path usepath; if (boosty::is_absolute(fs::path(filename))) { @@ -119,9 +122,12 @@ use[ \t\r\n>]*"<" { BEGIN(cond_use); } usepath = boosty::absolute(fs::path(get_librarydir()) / filename); } } - handle_dep(usepath.string()); - parserlval.text = strdup(usepath.string().c_str()); - return TOK_USE; + /* Only accept regular files which exists */ + if (usepath.has_parent_path() && fs::exists(usepath)) { + handle_dep(usepath.string()); + parserlval.text = strdup(usepath.string().c_str()); + return TOK_USE; + } } } @@ -215,8 +221,10 @@ void includefile() filepath.clear(); path_stack.push_back(finfo.parent_path()); - handle_dep(boosty::absolute(finfo).string()); - yyin = fopen(boosty::absolute(finfo).string().c_str(), "r"); + std::string fullname = boosty::absolute(finfo).string(); + handle_dep(fullname); + currmodule->registerInclude(fullname); + yyin = fopen(fullname.c_str(), "r"); if (!yyin) { PRINTB("WARNING: Can't open input file '%s'.", filename); path_stack.pop_back(); diff --git a/src/mainwin.cc b/src/mainwin.cc index b371742..1057499 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -25,6 +25,7 @@ */ #include "PolySetCache.h" +#include "ModuleCache.h" #include "MainWindow.h" #include "openscad.h" // examplesdir #include "parsersettings.h" @@ -79,9 +80,8 @@ #include <algorithm> #include <boost/version.hpp> #include <boost/foreach.hpp> -#include <boost/lambda/lambda.hpp> -#include <boost/lambda/bind.hpp> -using namespace boost::lambda; +#include <boost/version.hpp> +#include <sys/stat.h> #ifdef ENABLE_CGAL @@ -200,8 +200,8 @@ MainWindow::MainWindow(const QString &filename) autoReloadTimer->setSingleShot(false); connect(autoReloadTimer, SIGNAL(timeout()), this, SLOT(checkAutoReload())); - connect(e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionCompile())); - connect(e_fps, SIGNAL(textChanged(QString)), this, SLOT(updatedFps())); + connect(this->e_tval, SIGNAL(textChanged(QString)), this, SLOT(actionCompile())); + connect(this->e_fps, SIGNAL(textChanged(QString)), this, SLOT(updatedFps())); animate_panel->hide(); @@ -489,7 +489,7 @@ MainWindow::openFile(const QString &new_filename) #endif setFileName(new_filename); - load(); + refreshDocument(); updateRecentFiles(); } @@ -545,11 +545,11 @@ void MainWindow::updateRecentFiles() void MainWindow::updatedFps() { bool fps_ok; - double fps = e_fps->text().toDouble(&fps_ok); + double fps = this->e_fps->text().toDouble(&fps_ok); animate_timer->stop(); if (fps_ok && fps > 0) { animate_timer->setSingleShot(false); - animate_timer->setInterval(int(1000 / e_fps->text().toDouble())); + animate_timer->setInterval(int(1000 / this->e_fps->text().toDouble())); animate_timer->start(); } } @@ -557,27 +557,28 @@ void MainWindow::updatedFps() void MainWindow::updateTVal() { bool fps_ok; - double fps = e_fps->text().toDouble(&fps_ok); + double fps = this->e_fps->text().toDouble(&fps_ok); if (fps_ok) { if (fps <= 0) { actionCompile(); } else { - double s = e_fsteps->text().toDouble(); - double t = e_tval->text().toDouble() + 1/s; + double s = this->e_fsteps->text().toDouble(); + double t = this->e_tval->text().toDouble() + 1/s; QString txt; txt.sprintf("%.5f", t >= 1.0 ? 0.0 : t); - e_tval->setText(txt); + this->e_tval->setText(txt); } } } -void MainWindow::load() +void MainWindow::refreshDocument() { setCurrentOutput(); if (!this->fileName.isEmpty()) { QFile file(this->fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - PRINTB("Failed to open file: %s (%s)", this->fileName.toStdString() % file.errorString().toStdString()); + PRINTB("Failed to open file %s: %s", + this->fileName.toStdString() % file.errorString().toStdString()); } else { QString text = QTextStream(&file).readAll(); @@ -599,42 +600,30 @@ AbstractNode *MainWindow::find_root_tag(AbstractNode *n) /*! Parse and evaluate the design => this->root_node + + Returns true if something was compiled, false if nothing was changed + and the root_node was left untouched. */ -void MainWindow::compile(bool procevents) +bool MainWindow::compile(bool reload, bool procevents) { - PRINT("Parsing design (AST generation)..."); - if (procevents) - QApplication::processEvents(); + if (!compileTopLevelDocument(reload)) return false; // Invalidate renderers before we kill the CSG tree this->glview->setRenderer(NULL); - if (this->opencsgRenderer) { - delete this->opencsgRenderer; - this->opencsgRenderer = NULL; - } - if (this->thrownTogetherRenderer) { - delete this->thrownTogetherRenderer; - this->thrownTogetherRenderer = NULL; - } + delete this->opencsgRenderer; + this->opencsgRenderer = NULL; + delete this->thrownTogetherRenderer; + this->thrownTogetherRenderer = NULL; // Remove previous CSG tree - if (this->root_module) { - delete this->root_module; - this->root_module = NULL; - } - - if (this->absolute_root_node) { - delete this->absolute_root_node; - this->absolute_root_node = NULL; - } + delete this->absolute_root_node; + this->absolute_root_node = NULL; this->root_raw_term.reset(); this->root_norm_term.reset(); - if (this->root_chain) { - delete this->root_chain; - this->root_chain = NULL; - } + delete this->root_chain; + this->root_chain = NULL; this->highlight_terms.clear(); delete this->highlights_chain; @@ -647,96 +636,43 @@ void MainWindow::compile(bool procevents) this->root_node = NULL; this->tree.setRoot(NULL); - // Initialize special variables - this->root_ctx.set_variable("$t", Value(e_tval->text().toDouble())); - - Value vpt; - vpt.type = Value::VECTOR; - vpt.append(new Value(-this->glview->object_trans_x)); - vpt.append(new Value(-this->glview->object_trans_y)); - vpt.append(new Value(-this->glview->object_trans_z)); - this->root_ctx.set_variable("$vpt", vpt); - - Value vpr; - vpr.type = Value::VECTOR; - vpr.append(new Value(fmodf(360 - this->glview->object_rot_x + 90, 360))); - vpr.append(new Value(fmodf(360 - this->glview->object_rot_y, 360))); - vpr.append(new Value(fmodf(360 - this->glview->object_rot_z, 360))); - root_ctx.set_variable("$vpr", vpr); - - // Parse - this->last_compiled_doc = editor->toPlainText(); - this->root_module = parse((this->last_compiled_doc + "\n" + - QString::fromStdString(commandline_commands)).toAscii().data(), - this->fileName.isEmpty() ? - "" : - QFileInfo(this->fileName).absolutePath().toLocal8Bit(), - false); - - // Error highlighting - if (this->highlighter) { - delete this->highlighter; - this->highlighter = NULL; - } - if (parser_error_pos >= 0) { - this->highlighter = new Highlighter(editor->document()); - } - - if (!this->root_module) { - if (!animate_panel->isVisible()) { -#ifdef _QCODE_EDIT_ - QDocumentCursor cursor = editor->cursor(); - cursor.setPosition(parser_error_pos); -#else - QTextCursor cursor = editor->textCursor(); - cursor.setPosition(parser_error_pos); - editor->setTextCursor(cursor); -#endif + if (this->root_module) { + // Evaluate CSG tree + PRINT("Compiling design (CSG Tree generation)..."); + if (procevents) QApplication::processEvents(); + + AbstractNode::resetIndexCounter(); + this->root_inst = ModuleInstantiation(); + this->absolute_root_node = this->root_module->evaluate(&this->root_ctx, &this->root_inst); + + if (this->absolute_root_node) { + // Do we have an explicit root node (! modifier)? + if (!(this->root_node = find_root_tag(this->absolute_root_node))) { + this->root_node = this->absolute_root_node; + } + // FIXME: Consider giving away ownership of root_node to the Tree, or use reference counted pointers + this->tree.setRoot(this->root_node); + // Dump the tree (to initialize caches). + // FIXME: We shouldn't really need to do this explicitly.. + this->tree.getString(*this->root_node); } - goto fail; } - // Evaluate CSG tree - PRINT("Compiling design (CSG Tree generation)..."); - if (procevents) - QApplication::processEvents(); - - AbstractNode::resetIndexCounter(); - this->root_inst = ModuleInstantiation(); - this->absolute_root_node = this->root_module->evaluate(&this->root_ctx, &this->root_inst); - - if (!this->absolute_root_node) - goto fail; - - // Do we have an explicit root node (! modifier)? - if (!(this->root_node = find_root_tag(this->absolute_root_node))) { - this->root_node = this->absolute_root_node; - } - // FIXME: Consider giving away ownership of root_node to the Tree, or use reference counted pointers - this->tree.setRoot(this->root_node); - // Dump the tree (to initialize caches). - // FIXME: We shouldn't really need to do this explicitly.. - this->tree.getString(*this->root_node); - - if (1) { - PRINT("Compilation finished."); - if (procevents) - QApplication::processEvents(); - } else { -fail: + if (!this->root_node) { if (parser_error_pos < 0) { PRINT("ERROR: Compilation failed! (no top level object found)"); } else { PRINT("ERROR: Compilation failed!"); } - if (procevents) - QApplication::processEvents(); + if (procevents) QApplication::processEvents(); } + + return true; } /*! Generates CSG tree for OpenCSG evaluation. - Assumes that the design has been parsed and evaluated + Assumes that the design has been parsed and evaluated (this->root_node is set) */ void MainWindow::compileCSG(bool procevents) { @@ -992,7 +928,7 @@ void MainWindow::actionSaveAs() void MainWindow::actionReload() { - if (checkModified()) load(); + if (checkModified()) refreshDocument(); } void MainWindow::hideEditor() @@ -1032,16 +968,105 @@ void MainWindow::pasteViewportRotation() cursor.insertText(txt); } -void MainWindow::checkAutoReload() +void MainWindow::updateTemporalVariables() +{ + this->root_ctx.set_variable("$t", Value(this->e_tval->text().toDouble())); + + Value vpt; + vpt.type = Value::VECTOR; + vpt.append(new Value(-this->glview->object_trans_x)); + vpt.append(new Value(-this->glview->object_trans_y)); + vpt.append(new Value(-this->glview->object_trans_z)); + this->root_ctx.set_variable("$vpt", vpt); + + Value vpr; + vpr.type = Value::VECTOR; + vpr.append(new Value(fmodf(360 - this->glview->object_rot_x + 90, 360))); + vpr.append(new Value(fmodf(360 - this->glview->object_rot_y, 360))); + vpr.append(new Value(fmodf(360 - this->glview->object_rot_z, 360))); + root_ctx.set_variable("$vpr", vpr); +} + +bool MainWindow::fileChangedOnDisk() { if (!this->fileName.isEmpty()) { - QString new_stinfo; - QFileInfo finfo(this->fileName); - new_stinfo = QString::number(finfo.size()) + QString::number(finfo.lastModified().toTime_t()); - if (new_stinfo != autoReloadInfo) - actionReloadCompile(); - autoReloadInfo = new_stinfo; + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(this->fileName.toLocal8Bit(), &st); + std::string newid = str(boost::format("%x.%x") % st.st_mtime % st.st_size); + + if (newid != this->autoReloadId) { + this->autoReloadId = newid; + return true; + } + } + return false; +} + +/*! + If reload is true, does a timestamp check on the document and tries to reload it. + Otherwise, just reparses the current document and any dependencies, updates the + GUI accordingly and populates this->root_module. + + Returns true if anything was compiled. +*/ +bool MainWindow::compileTopLevelDocument(bool reload) +{ + bool shouldcompiletoplevel = !reload; + + if (reload && fileChangedOnDisk() && checkModified()) { + shouldcompiletoplevel = true; + refreshDocument(); + } + + if (shouldcompiletoplevel) { + updateTemporalVariables(); + + this->last_compiled_doc = editor->toPlainText(); + std::string fulltext = + this->last_compiled_doc.toStdString() + "\n" + commandline_commands; + + delete this->root_module; + this->root_module = NULL; + + this->root_module = parse(fulltext.c_str(), + this->fileName.isEmpty() ? + "" : + QFileInfo(this->fileName).absolutePath().toLocal8Bit(), + false); + + // Error highlighting + delete this->highlighter; + this->highlighter = NULL; + + if (!this->root_module) { + this->highlighter = new Highlighter(editor->document()); + + if (!animate_panel->isVisible()) { +#ifdef _QCODE_EDIT_ + QDocumentCursor cursor = editor->cursor(); + cursor.setPosition(parser_error_pos); +#else + QTextCursor cursor = editor->textCursor(); + cursor.setPosition(parser_error_pos); + editor->setTextCursor(cursor); +#endif + } + } + } + + bool changed = shouldcompiletoplevel; + if (this->root_module) { + changed |= this->root_module->handleDependencies(); + if (changed) PRINTB("Module cache size: %d modules", ModuleCache::instance()->size()); } + + return changed; +} + +void MainWindow::checkAutoReload() +{ + if (!this->fileName.isEmpty()) actionReloadCompile(); } void MainWindow::autoReloadSet(bool on) @@ -1049,7 +1074,7 @@ void MainWindow::autoReloadSet(bool on) QSettings settings; settings.setValue("design/autoReload",designActionAutoReload->isChecked()); if (on) { - autoReloadInfo = QString(); + autoReloadId = ""; autoReloadTimer->start(200); } else { autoReloadTimer->stop(); @@ -1076,15 +1101,12 @@ void MainWindow::actionReloadCompile() { if (GuiLocker::isLocked()) return; GuiLocker lock; - - if (!checkModified()) return; - + setCurrentOutput(); console->clear(); - load(); - - setCurrentOutput(); - compile(true); + // PRINT("Parsing design (AST generation)..."); + // QApplication::processEvents(); + if (!compile(true, true)) return; if (this->root_node) compileCSG(true); // Go to non-CGAL view mode @@ -1106,11 +1128,12 @@ void MainWindow::actionCompile() { if (GuiLocker::isLocked()) return; GuiLocker lock; - setCurrentOutput(); console->clear(); - compile(!viewActionAnimate->isChecked()); + PRINT("Parsing design (AST generation)..."); + QApplication::processEvents(); + compile(false, !viewActionAnimate->isChecked()); if (this->root_node) compileCSG(!viewActionAnimate->isChecked()); // Go to non-CGAL view mode @@ -1128,8 +1151,8 @@ void MainWindow::actionCompile() if (viewActionAnimate->isChecked() && e_dump->isChecked()) { QImage img = this->glview->grabFrameBuffer(); QString filename; - double s = e_fsteps->text().toDouble(); - double t = e_tval->text().toDouble(); + double s = this->e_fsteps->text().toDouble(); + double t = this->e_tval->text().toDouble(); filename.sprintf("frame%05d.png", int(round(s*t))); img.save(filename, "PNG"); } @@ -1147,7 +1170,9 @@ void MainWindow::actionRenderCGAL() setCurrentOutput(); console->clear(); - compile(true); + PRINT("Parsing design (AST generation)..."); + QApplication::processEvents(); + compile(false, true); if (!this->root_module || !this->root_node) { return; @@ -1450,7 +1475,7 @@ void MainWindow::actionFlushCaches() #endif dxf_dim_cache.clear(); dxf_cross_cache.clear(); - Module::clear_library_cache(); + ModuleCache::instance()->clear(); } void MainWindow::viewModeActionsUncheck() @@ -1557,7 +1582,7 @@ void MainWindow::animateUpdate() { if (animate_panel->isVisible()) { bool fps_ok; - double fps = e_fps->text().toDouble(&fps_ok); + double fps = this->e_fps->text().toDouble(&fps_ok); if (fps_ok && fps <= 0 && !animate_timer->isActive()) { animate_timer->stop(); animate_timer->setSingleShot(true); @@ -1798,7 +1823,7 @@ void MainWindow::consoleOutput(const std::string &msg, void *userdata) void MainWindow::setCurrentOutput() { - set_output_handler(&MainWindow::consoleOutput, this); +// set_output_handler(&MainWindow::consoleOutput, this); } void MainWindow::clearCurrentOutput() diff --git a/src/module.cc b/src/module.cc index 6641ff7..69623dd 100644 --- a/src/module.cc +++ b/src/module.cc @@ -25,13 +25,16 @@ */ #include "module.h" +#include "ModuleCache.h" #include "node.h" #include "context.h" #include "expression.h" #include "function.h" #include "printutils.h" + #include <boost/foreach.hpp> #include <sstream> +#include <sys/stat.h> AbstractModule::~AbstractModule() { @@ -201,7 +204,33 @@ std::string Module::dump(const std::string &indent, const std::string &name) con return dump.str(); } -void Module::clear_library_cache() +void Module::registerInclude(const std::string &filename) +{ + struct stat st; + memset(&st, 0, sizeof(struct stat)); + stat(filename.c_str(), &st); + this->includes[filename] = st.st_mtime; +} + +/*! + Check if any dependencies have been modified and recompile them. + Returns true if anything was recompiled. +*/ +bool Module::handleDependencies() { - Module::libs_cache.clear(); + bool changed = false; + // Iterating manually since we want to modify the container while iterating + Module::ModuleContainer::iterator iter = this->usedlibs.begin(); + while (iter != this->usedlibs.end()) { + Module::ModuleContainer::iterator curr = iter++; + Module *oldmodule = curr->second; + curr->second = ModuleCache::instance()->evaluate(curr->first); + if (curr->second != oldmodule) changed = true; + PRINTB_NOCACHE(" %s: %p", curr->first % curr->second); + if (!curr->second) { + PRINTB_NOCACHE("WARNING: Failed to compile library '%s'.", curr->first); + this->usedlibs.erase(curr); + } + } + return changed; } diff --git a/src/module.h b/src/module.h index 6c6529b..cd25287 100644 --- a/src/module.h +++ b/src/module.h @@ -3,6 +3,7 @@ #include <string> #include <vector> +#include <list> #include <boost/unordered_map.hpp> #include "value.h" @@ -65,11 +66,12 @@ public: void addChild(ModuleInstantiation *ch) { this->children.push_back(ch); } - static Module *compile_library(const std::string &filename); - static void clear_library_cache(); - typedef boost::unordered_map<std::string, class Module*> ModuleContainer; ModuleContainer usedlibs; + void registerInclude(const std::string &filename); + typedef boost::unordered_map<std::string, time_t> IncludeContainer; + IncludeContainer includes; + bool handleDependencies(); std::vector<std::string> assignments_var; std::vector<Expression*> assignments_expr; @@ -87,11 +89,6 @@ public: protected: private: - struct libs_cache_ent { - Module *mod; - std::string cache_id, msg; - }; - static boost::unordered_map<std::string, libs_cache_ent> libs_cache; }; #endif diff --git a/src/openscad.cc b/src/openscad.cc index 11dad7c..f45f81a 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -255,34 +255,28 @@ int main(int argc, char **argv) Context root_ctx; register_builtin(root_ctx); - AbstractModule *root_module; + Module *root_module; ModuleInstantiation root_inst; AbstractNode *root_node; handle_dep(filename); - FILE *fp = fopen(filename, "rt"); - if (!fp) { - fprintf(stderr, "Can't open input file `%s'!\n", filename); + + std::ifstream ifs(filename); + if (!ifs.is_open()) { + fprintf(stderr, "Can't open input file '%s'!\n", filename); exit(1); - } else { - std::stringstream text; - char buffer[513]; - int ret; - while ((ret = fread(buffer, 1, 512, fp)) > 0) { - buffer[ret] = 0; - text << buffer; - } - fclose(fp); - text << "\n" << commandline_commands; - fs::path abspath = boosty::absolute( filename ); - std::string fpath = boosty::stringy(abspath.parent_path()); - root_module = parse(text.str().c_str(), fpath.c_str(), false); - if (!root_module) exit(1); } + std::string text((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); + text += "\n" + commandline_commands; + fs::path abspath = boosty::absolute(filename); + std::string parentpath = boosty::stringy(abspath.parent_path()); + root_module = parse(text.c_str(), parentpath.c_str(), false); + if (!root_module) exit(1); + root_module->handleDependencies(); - fs::path fpath = boosty::absolute( fs::path(filename) ); + fs::path fpath = boosty::absolute(fs::path(filename)); fs::path fparent = fpath.parent_path(); - fs::current_path( fparent ); + fs::current_path(fparent); AbstractNode::resetIndexCounter(); root_node = root_module->evaluate(&root_ctx, &root_inst); diff --git a/src/openscad.h b/src/openscad.h index dab14cd..8b49ba2 100644 --- a/src/openscad.h +++ b/src/openscad.h @@ -27,7 +27,7 @@ #ifndef OPENSCAD_H #define OPENSCAD_H -extern class AbstractModule *parse(const char *text, const char *path, int debug); +extern class Module *parse(const char *text, const char *path, int debug); extern int get_fragments_from_r(double r, double fn, double fs, double fa); #include <string> diff --git a/src/parser.y b/src/parser.y index 15a754b..2f4379a 100644 --- a/src/parser.y +++ b/src/parser.y @@ -43,7 +43,7 @@ #include <boost/foreach.hpp> #include <boost/filesystem.hpp> -using namespace boost::filesystem; +namespace fs = boost::filesystem; #include "boosty.h" int parser_error_pos = -1; @@ -56,7 +56,7 @@ int lexerlex_destroy(void); int lexerlex(void); std::vector<Module*> module_stack; -Module *module; +Module *currmodule; class ArgContainer { public: @@ -133,7 +133,7 @@ public: input: /* empty */ | - TOK_USE { module->usedlibs[$1] = NULL; } input | + TOK_USE { currmodule->usedlibs[$1] = NULL; } input | statement input ; inner_input: @@ -145,37 +145,38 @@ statement: '{' inner_input '}' | module_instantiation { if ($1) { - module->addChild($1); + currmodule->addChild($1); } else { delete $1; } } | TOK_ID '=' expr ';' { bool add_new_assignment = true; - for (size_t i = 0; i < module->assignments_var.size(); i++) { - if (module->assignments_var[i] != $1) + for (size_t i = 0; i < currmodule->assignments_var.size(); i++) { + if (currmodule->assignments_var[i] != $1) continue; - delete module->assignments_expr[i]; - module->assignments_expr[i] = $3; + delete currmodule->assignments_expr[i]; + currmodule->assignments_expr[i] = $3; add_new_assignment = false; } if (add_new_assignment) { - module->assignments_var.push_back($1); - module->assignments_expr.push_back($3); + currmodule->assignments_var.push_back($1); + currmodule->assignments_expr.push_back($3); free($1); } } | TOK_MODULE TOK_ID '(' arguments_decl optional_commas ')' { - Module *p = module; - module_stack.push_back(module); - module = new Module(); - p->modules[$2] = module; - module->argnames = $4->argnames; - module->argexpr = $4->argexpr; + Module *p = currmodule; + module_stack.push_back(currmodule); + currmodule = new Module(); + PRINTB_NOCACHE("New module: %s %p", $2 % currmodule); + p->modules[$2] = currmodule; + currmodule->argnames = $4->argnames; + currmodule->argexpr = $4->argexpr; free($2); delete $4; } statement { - module = module_stack.back(); + currmodule = module_stack.back(); module_stack.pop_back(); } | TOK_FUNCTION TOK_ID '(' arguments_decl optional_commas ')' '=' expr { @@ -183,7 +184,7 @@ statement: func->argnames = $4->argnames; func->argexpr = $4->argexpr; func->expr = $8; - module->functions[$2] = func; + currmodule->functions[$2] = func; free($2); delete $4; } ';' ; @@ -560,101 +561,33 @@ void yyerror (char const *s) { // FIXME: We leak memory on parser errors... PRINTB("Parser error in line %d: %s\n", lexerget_lineno() % s); - module = NULL; + currmodule = NULL; } extern void lexerdestroy(); extern FILE *lexerin; extern const char *parser_input_buffer; const char *parser_input_buffer; -const char *parser_source_path; +std::string parser_source_path; -AbstractModule *parse(const char *text, const char *path, int debug) +Module *parse(const char *text, const char *path, int debug) { lexerin = NULL; parser_error_pos = -1; parser_input_buffer = text; - parser_source_path = path; + parser_source_path = std::string(path); module_stack.clear(); - module = new Module(); + Module *rootmodule = currmodule = new Module(); + // PRINTB_NOCACHE("New module: %s %p", "root" % rootmodule); parserdebug = debug; parserparse(); lexerdestroy(); lexerlex_destroy(); - if (!module) - return NULL; - - // Iterating manually since we want to modify the container while iterating - Module::ModuleContainer::iterator iter = module->usedlibs.begin(); - while (iter != module->usedlibs.end()) { - Module::ModuleContainer::iterator curr = iter++; - curr->second = Module::compile_library(curr->first); - if (!curr->second) { - PRINTB("WARNING: Failed to compile library '%s'.", curr->first); - module->usedlibs.erase(curr); - } - } + if (!rootmodule) return NULL; parser_error_pos = -1; - return module; -} - -boost::unordered_map<std::string, Module::libs_cache_ent> Module::libs_cache; - -Module *Module::compile_library(const std::string &filename) -{ - struct stat st; - memset(&st, 0, sizeof(struct stat)); - stat(filename.c_str(), &st); - - std::stringstream idstream; - idstream << std::hex << st.st_mtime << "." << st.st_size; - std::string cache_id = idstream.str(); - - if (libs_cache.find(filename) != libs_cache.end() && libs_cache[filename].cache_id == cache_id) { - PRINTB("%s", libs_cache[filename].msg); - return &(*libs_cache[filename].mod); - } - - FILE *fp = fopen(filename.c_str(), "rt"); - if (!fp) { - fprintf(stderr, "WARNING: Can't open library file '%s'\n", filename.c_str()); - return NULL; - } - std::stringstream text; - char buffer[513]; - int ret; - while ((ret = fread(buffer, 1, 512, fp)) > 0) { - buffer[ret] = 0; - text << buffer; - } - fclose(fp); - - print_messages_push(); - - PRINTB("Compiling library '%s'.", filename); - libs_cache_ent e = { NULL, cache_id, std::string("WARNING: Library `") + filename + "' tries to recursively use itself!" }; - if (libs_cache.find(filename) != libs_cache.end()) - delete libs_cache[filename].mod; - libs_cache[filename] = e; - - Module *backup_mod = module; - std::string pathname = boosty::stringy( fs::path(filename).parent_path() ); - Module *lib_mod = dynamic_cast<Module*>(parse(text.str().c_str(), pathname.c_str(), 0)); - module = backup_mod; - - if (lib_mod) { - libs_cache[filename].mod = lib_mod; - libs_cache[filename].msg = print_messages_stack.back(); - } else { - libs_cache.erase(filename); - } - - print_messages_pop(); - - return lib_mod; + return rootmodule; } - |