diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp index b4e4cac92e3ab6edf32ad40d15cf92a281c0a780..56b4e559a24ca6c42ab7805760980f06531340c8 100644 --- a/indra/llui/llkeywords.cpp +++ b/indra/llui/llkeywords.cpp @@ -33,6 +33,7 @@ #include "llsdserialize.h" #include "lltexteditor.h" #include "llstl.h" +#include "llsdutil.h" inline bool LLKeywordToken::isHead(const llwchar* s) const { @@ -109,6 +110,7 @@ void LLKeywords::addToken(LLKeywordToken::ETokenType type, case LLKeywordToken::TT_LABEL: case LLKeywordToken::TT_SECTION: case LLKeywordToken::TT_TYPE: + case LLKeywordToken::TT_PREPROC: case LLKeywordToken::TT_WORD: mWordTokenMap[key] = new LLKeywordToken(type, color, key, tool_tip, LLWStringUtil::null); break; @@ -193,7 +195,7 @@ LLColor4 LLKeywords::getColorGroup(std::string_view key_in) script_colors.push_back(LLUIColorTable::instance().getColor("SyntaxLslConstant")); } - if (key_in == "functions") + if (key_in == "functions" || key_in == "preprocessor") { return script_colors[SyntaxLslFunction].get(); } @@ -242,6 +244,34 @@ LLColor4 LLKeywords::getColorGroup(std::string_view key_in) void LLKeywords::initialize(LLSD SyntaxXML) { mSyntax = SyntaxXML; + + std::string preproc_tokens = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "keywords_lsl_preproc.xml"); + if (gDirUtilp->fileExists(preproc_tokens)) + { + LLSD content; + llifstream file; + file.open(preproc_tokens.c_str()); + if (file.is_open()) + { + if(LLSDSerialize::fromXML(content, file) == LLSDParser::PARSE_FAILURE) + { + LL_INFOS() << "Failed to parse preproc token file" << LL_ENDL; + } + file.close(); + } + else + { + LL_WARNS("SyntaxLSL") << "Failed to open: " << preproc_tokens << LL_ENDL; + } + + if (content.isMap()) + { + if (content.has("preprocessor")) + { + mSyntax["preprocessor"] = llsd_clone(content["preprocessor"]); + } + } + } mLoaded = true; } @@ -314,6 +344,10 @@ void LLKeywords::processTokensGroup(const LLSD& tokens, std::string_view group) { token_type = LLKeywordToken::TT_TYPE; } + else if (group == "preprocessor") + { + token_type = LLKeywordToken::TT_PREPROC; + } color_group = getColorGroup(group); LL_DEBUGS("SyntaxLSL") << "Group: '" << group << "', using color: '" << color_group << "'" << LL_ENDL; @@ -680,10 +714,10 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW // check against words llwchar prev = cur > base ? *(cur-1) : 0; - if( !iswalnum( prev ) && (prev != '_') ) + if( !iswalnum( prev ) && (prev != '_') && (prev != '#')) { const llwchar* p = cur; - while( iswalnum( *p ) || (*p == '_') ) + while( *p && ( iswalnum( *p ) || (*p == '_') || (*p == '#') ) ) { p++; } diff --git a/indra/llui/llkeywords.h b/indra/llui/llkeywords.h index 9fce3af3e31b6f5600c048c28c05a4da86e96b95..9e3cc9370c2b109615a5ff92782a7286e2240e0a 100644 --- a/indra/llui/llkeywords.h +++ b/indra/llui/llkeywords.h @@ -68,7 +68,8 @@ class LLKeywordToken TT_FUNCTION, // WORD TT_LABEL, // LINE TT_SECTION, // WORD - TT_TYPE // WORD + TT_TYPE, // WORD + TT_PREPROC // WORD } ETokenType; LLKeywordToken( ETokenType type, const LLColor4& color, const LLWString& token, const LLWString& tool_tip, const LLWString& delimiter ) diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 0a58daf107a47abce6834566d7f3f5301c701cc2..b276ee740515ab172a8796d49c87617f28d205b4 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -320,8 +320,8 @@ bool LLTextBase::truncate() { BOOL did_truncate = FALSE; - // First rough check - if we're less than 1/4th the size, we're OK - if (getLength() >= S32(mMaxTextByteLength / 4)) + // First rough check - if we're less than 1/2th the size, we're OK + if (getLength() >= S32(mMaxTextByteLength / 2)) { // Have to check actual byte size S32 utf8_byte_size = 0; @@ -953,7 +953,8 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s getViewModel()->getEditableDisplay().insert(pos, wstr); - if ( truncate() ) + //HACK: If we are readonly we shouldn't need to truncate + if ( !mReadOnly && truncate() ) { insert_len = getLength() - old_len; } diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 4292777085dedf3782084205c1779138b872cac3..02982bb836e386cd26c747ad3c971b283c901274 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -143,6 +143,8 @@ set(viewer_SOURCE_FILES altoolalign.cpp alunzip.cpp alviewermenu.cpp + fslslpreproc.cpp + fslslpreprocviewer.cpp groupchatlistener.cpp llagentwearablesfetch.cpp llaccountingcostmanager.cpp @@ -853,6 +855,8 @@ set(viewer_HEADER_FILES altoolalign.h alunzip.h alviewermenu.h + fslslpreproc.h + fslslpreprocviewer.h groupchatlistener.h llaccountingcost.h llagentwearablesfetch.h diff --git a/indra/newview/app_settings/keywords_lsl_preproc.xml b/indra/newview/app_settings/keywords_lsl_preproc.xml new file mode 100644 index 0000000000000000000000000000000000000000..e1786022fd4fa1f38db0342c7fe12d9e2b673550 --- /dev/null +++ b/indra/newview/app_settings/keywords_lsl_preproc.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<llsd> + <map> + <key>preprocessor</key> + <map> + <key>#assert</key> + <map> + <key>tooltip</key> + <string>Allows definition of preprocessor variables that do not conflict with names in the program name space</string> + </map> + <key>#define</key> + <map> + <key>tooltip</key> + <string>When the preprocessor encounters this directive, it replaces any occurrence of identifier in the rest of the code by replacement</string> + </map> + <key>#elif</key> + <map> + <key>tooltip</key> + <string>Specify some condition to be met in order for the portion of code they surround to be compiled</string> + </map> + <key>#else</key> + <map> + <key>tooltip</key> + <string>Specify some condition to be met in order for the portion of code they surround to be compiled</string> + </map> + <key>#endif</key> + <map> + <key>tooltip</key> + <string>Closes an #if directive</string> + </map> + <key>#error</key> + <map> + <key>tooltip</key> + <string>Aborts the compilation process when it is found, generating a compilation the error that can be specified as its parameter</string> + </map> + <key>#ident</key> + <map> + <key>tooltip</key> + <string>Inserts a comment into the generated script</string> + </map> + <key>#sccs</key> + <map> + <key>tooltip</key> + <string>Inserts a comment into the generated script</string> + </map> + <key>#if</key> + <map> + <key>tooltip</key> + <string>Specifies some condition to be met in order for the portion of code they surround to be compiled</string> + </map> + <key>#ifdef</key> + <map> + <key>tooltip</key> + <string>Allows a section of a program to be compiled only if the macro that is specified as the parameter has been defined, no matter which its value is</string> + </map> + <key>#ifndef</key> + <map> + <key>tooltip</key> + <string>The code between #ifndef and #endif directives is only compiled if the specified identifier has not been previously defined</string> + </map> + <key>#import</key> + <map> + <key>tooltip</key> + <string>Instructs the preprocessor to look for type library files first in the directory of the file that contains the #import statement, and then in the directories of whatever files that include (#include) that file</string> + </map> + <key>#include</key> + <map> + <key>tooltip</key> + <string>When the preprocessor finds an #include directive it replaces it by the entire content of the specified file</string> + </map> + <key>#include_next</key> + <map> + <key>tooltip</key> + <string>Instructs the preprocessor to continue searching for the specified file name, and to include the subsequent instance encountered after the current directory</string> + </map> + <key>#line</key> + <map> + <key>tooltip</key> + <string>Controls the line number and file name</string> + </map> + <key>#pragma</key> + <map> + <key>tooltip</key> + <string>This directive is used to specify diverse options to the compiler. These options are specific for the platform and the compiler you use</string> + </map> + <key>#unassert</key> + <map> + <key>tooltip</key> + <string>Closes an #assert directive</string> + </map> + <key>#undef</key> + <map> + <key>tooltip</key> + <string>Undefines #define macro</string> + </map> + <key>#warning</key> + <map> + <key>tooltip</key> + <string>Generate a level one warning from a specific location in your code</string> + </map> + <key>#switch</key> + <map> + <key>tooltip</key> + <string>Allows selection among multiple sections of code, depending on the value of an integral expression</string> + </map> + <key>#case</key> + <map> + <key>tooltip</key> + <string>Used with #switch in a union</string> + </map> + <key>#break</key> + <map> + <key>tooltip</key> + <string>Stops processing further lines in a #switch directive</string> + </map> + </map> + <key>llsd-lsl-syntax-version</key> + <integer>2</integer><!-- increment only when the file format changes, not just the content --> + </map> +</llsd> \ No newline at end of file diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index 21e728cac6cfe14bf0da0649d57ca5cac6b40994..b613de9bc89f5756e04cb7a7c3a70403ad165885 100644 --- a/indra/newview/app_settings/settings_alchemy.xml +++ b/indra/newview/app_settings/settings_alchemy.xml @@ -343,6 +343,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>AlchemyInventoryScriptsMono</key> + <map> + <key>Comment</key> + <string>Control whether new scripts in inventory are mono(true) or lsl(false)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> <key>AlchemyMoonWalk</key> <map> <key>Comment</key> @@ -607,8 +618,85 @@ <key>Value</key> <integer>0</integer> </map> - <key>AlchemyPointAtPrivate</key> + <key>AlchemyLSLPreprocessor</key> + <map> + <key>Comment</key> + <string>LSL Preprocessor</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>AlchemyPreProcLSLOptimizer</key> + <map> + <key>Comment</key> + <string>LSL Optimizer</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>AlchemyPreProcLSLTextCompress</key> + <map> + <key>Comment</key> + <string>LSL Text Compress</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>AlchemyPreProcLSLLazyLists</key> <map> + <key>Comment</key> + <string>LSL Lazy Lists</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>AlchemyPreProcLSLSwitch</key> + <map> + <key>Comment</key> + <string>LSL Switch Statements</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>AlchemyPreProcEnableHDDInclude</key> + <map> + <key>Comment</key> + <string>Enable #include from local disk</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>AlchemyPreProcHDDIncludeLocation</key> + <map> + <key>Comment</key> + <string>Path for local disk includes</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string/> + </map> + <key>AlchemyPointAtPrivate</key> + <map> <key>Comment</key> <string>Disable setting current point at target</string> <key>Persist</key> @@ -761,8 +849,8 @@ <key>Value</key> <integer>1</integer> </map> - <key>OpenSimSearchURL</key> - <map> + <key>OpenSimSearchURL</key> + <map> <key>Comment</key> <string>OpenSim fallback search url</string> <key>Persist</key> @@ -771,7 +859,7 @@ <string>String</string> <key>Value</key> <string>http://search.metaverseink.com/opensim/results.jsp?query=[QUERY]&submit=[CATEGORY]</string> - </map> + </map> <key>RenderAutoMaskAlphaUseRMSE</key> <map> <key>Comment</key> @@ -816,28 +904,28 @@ <key>Value</key> <string></string> </map> - <key>RenderFocusPointLocked</key> - <map> - <key>Comment</key> - <string>Whether the focus point used for DoF is currently Locked in place</string> - <key>Persist</key> - <integer>0</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> - <key>RenderFocusPointFollowsPointer</key> - <map> - <key>Comment</key> - <string>Allows the Depth of Field focus to actively follow the mouse point</string> - <key>Persist</key> - <integer>0</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> + <key>RenderFocusPointLocked</key> + <map> + <key>Comment</key> + <string>Whether the focus point used for DoF is currently Locked in place</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>RenderFocusPointFollowsPointer</key> + <map> + <key>Comment</key> + <string>Allows the Depth of Field focus to actively follow the mouse point</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>RenderSharpenMethod</key> <map> <key>Comment</key> diff --git a/indra/newview/fslslpreproc.cpp b/indra/newview/fslslpreproc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3e7dadd8768ad0c9f81e7f374b78ed45879238c2 --- /dev/null +++ b/indra/newview/fslslpreproc.cpp @@ -0,0 +1,1586 @@ +/** + * @file fslslpreproc.cpp + * Copyright (c) 2010 + * + * Modular Systems All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. Neither the name Modular Systems nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY MODULAR SYSTEMS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MODULAR SYSTEMS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "llviewerprecompiledheaders.h" + +#include "fslslpreproc.h" + +#include "fslslpreprocviewer.h" +#include "llagent.h" +#include "llappviewer.h" +#include "llinventoryfunctions.h" +#include "lltrans.h" +#include "llfilesystem.h" +#include "llviewercontrol.h" +#include "llcompilequeue.h" +#include "llnotificationsutil.h" +#include "llvoavatarself.h" + +class ScriptMatches : public LLInventoryCollectFunctor +{ +public: + ScriptMatches(const std::string& name) + { + mName = name; + } + + virtual ~ScriptMatches() {} + + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item) + { + return (item && item->getName() == mName && item->getType() == LLAssetType::AT_LSL_TEXT); + } + +private: + std::string mName; +}; + +LLUUID FSLSLPreprocessor::findInventoryByName(std::string name) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + ScriptMatches namematches(name); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), cats, items, FALSE, namematches); + + if (!items.empty()) + { + return items.front()->getUUID(); + } + return LLUUID::null; +} + +std::map<std::string,LLUUID> FSLSLPreprocessor::cached_assetids; + +#include <boost/assert.hpp> +#include <boost/wave.hpp> +#include <boost/wave/cpplexer/cpp_lex_token.hpp> // token class +#include <boost/wave/cpplexer/cpp_lex_iterator.hpp> // lexer class +#include <boost/wave/preprocessing_hooks.hpp> +#include <boost/regex.hpp> +#include <boost/filesystem.hpp> + +using namespace boost::regex_constants; + +#define encode_start "//start_unprocessed_text\n/*" +#define encode_end std::string("*/\n//end_unprocessed_text") + +// Definitions to split the expressions into parts to improve readablility +// (using 'r' as namespace prefix for RE, to avoid conflicts) +// The code relies on none of these expressions having capturing groups. +#define rCMNT "//[^\\n]*+\\n|/\\*(?:(?!\\*/).)*+\\*/" // skip over single- or multi-line comments as a block +#define rSPC "[^][{}()<>@A-Za-z0-9_.,:;!~&|^\"=%/*+-]" +#define rREQ_SPC "(?:" rCMNT "|" rSPC ")++" +#define rOPT_SPC "(?:" rCMNT "|" rSPC ")*+" +#define rTYPE_ID "[a-z]++" +#define rIDENT "[A-Za-z_][A-Za-z0-9_]*+" +#define rCMNT_OR_STR rCMNT "|\"(?:[^\"\\\\]|\\\\[^\\n])*+\"" // skip over strings as a block too +#define rDOT_MATCHES_NEWLINE "(?s)" + +std::string FSLSLPreprocessor::encode(const std::string& script) +{ + std::string otext = FSLSLPreprocessor::decode(script); + + bool mono = mono_directive(script); + + static const boost::regex preproc_encode_regex("([/*])(?=[/*|])", boost::regex::perl); + otext = boost::regex_replace(otext, preproc_encode_regex, "$1|"); + + time_t utc_time = time_corrected(); + std::string timeStr ="["+LLTrans::getString ("TimeMonth")+"]/[" + +LLTrans::getString ("TimeDay")+"]/[" + +LLTrans::getString ("TimeYear")+"] [" + +LLTrans::getString ("TimeHour")+"]:[" + +LLTrans::getString ("TimeMin")+"]:[" + +LLTrans::getString("TimeSec")+"]"; + LLSD substitution; + substitution["datetime"] = (S32) utc_time; + LLStringUtil::format (timeStr, substitution); + + return fmt::format(FMT_COMPILE("//start_unprocessed_text\n/*{}*/\n//end_unprocessed_text\n//nfo_preprocessor_version 0\n//program_version {}\n//last_compiled {}\n{}"), otext, LLAppViewer::instance()->getWindowTitle(), timeStr, mono ? "//mono\n" : "//lsl2\n"); +} + +std::string FSLSLPreprocessor::decode(const std::string& script) +{ + static size_t startpoint = std::string_view(encode_start).length(); + + std::string tip = script.substr(0, startpoint); + if (tip != encode_start) + { + LL_DEBUGS("FSLSLPreprocessor") << "No start" << LL_ENDL; + //if(sp != -1)trigger warningg/error? + return script; + } + + size_t end = script.find(encode_end); + if (end == std::string::npos) + { + LL_DEBUGS("FSLSLPreprocessor") << "No end" << LL_ENDL; + return script; + } + + std::string data = script.substr(startpoint, end - startpoint); + LL_DEBUGS("FSLSLPreprocessor") << "data = " << data << LL_ENDL; + + static const boost::regex escape_regex("([/*])\\|", boost::regex::perl); + data = boost::regex_replace(data, escape_regex, "$1"); + + //data = curl_unescape(data.c_str(),data.length()); + + return data; +} + + +static std::string scopeript2(const std::string_view top, S32 fstart, char left = '{', char right = '}') +{ + if (fstart >= S32(top.length())) + { + return "begin out of bounds"; + } + + S32 cursor = fstart; + bool noscoped = true; + bool in_literal = false; + S32 count = 0; + char ltoken = ' '; + + do + { + char token = top[cursor]; + if (token == '"' && ltoken != '\\') + { + in_literal = !in_literal; + } + else if (token == '\\' && ltoken == '\\') + { + token = ' '; + } + else if (!in_literal) + { + if (token == left) + { + count += 1; + noscoped = false; + } + else if (token == right) + { + count -= 1; + noscoped = false; + } + } + ltoken = token; + cursor++; + } + while ((count > 0 || noscoped) && cursor < S32(top.length())); + + S32 end = (cursor - fstart); + if (end > S32(top.length())) + { + return "end out of bounds"; + } + + return std::string(top.substr(fstart,(cursor-fstart))); +} + +static void shredder(std::string& text) +{ + S32 cursor = 0; + if (text.empty()) + { + text = "No text to shredder."; + return; + } + + char ltoken = ' '; + do + { + char token = text[cursor]; + if (token == '"' && ltoken != '\\') + { + ltoken = token; + token = text[++cursor]; + while (cursor < S32(text.length())) + { + if (token == '\\' && ltoken == '\\') + { + token = ' '; + } + if (token == '"' && ltoken != '\\') + { + break; + } + ltoken = token; + ++cursor; + token = text[cursor]; + } + } + else if (token == '\\' && ltoken == '\\') + { + token = ' '; + } + + if (token != 0xA && token != 0x9 && ( + token < 0x20 || + token == '#' || + token == '$' || + token == '\\' || + token == '\'' || + token == '?' || + token >= 0x7F)) + { + text[cursor] = ' '; + } + ltoken = token; + ++cursor; + } + while (cursor < S32(text.length())); +} + +std::string FSLSLPreprocessor::lslopt(std::string script) +{ + + try + { + std::string bottom; + std::set<std::string> kept_functions; + std::map<std::string, std::string> functions; + std::vector<std::pair<std::string, std::string> > gvars; + + { // open new scope for local vars + + // Loop over every declaration in the script, classifying it according to type. + + static const boost::regex finddecls( + rDOT_MATCHES_NEWLINE + "(^" // Group 1: RE for a variable declaration. + rOPT_SPC // skip (but capture) leading whitespace and comments + // type<space or comments>identifier[<space or comments>] + rTYPE_ID rREQ_SPC "(" rIDENT ")" rOPT_SPC // Group 2: Identifier + "(?:=" // optionally with an assignment + // comments or strings or characters that are not a semicolon + "(?:" rCMNT_OR_STR "|[^;])++" + ")?;" // the whole assignment is optional, the semicolon isn't + ")" + "|" + "(^" // Group 3: RE for a function declaration (captures up to the identifier inclusive) + rOPT_SPC // skip (but capture) whitespace + // optionally: type<space or comments>, then ident + "(?:" rTYPE_ID rREQ_SPC ")?(" rIDENT ")" // Group 4: identifier + ")" + rOPT_SPC + "\\(" // this opening paren is the key for it to be a function + "|" + "(^" // Group 5: State default, possibly preceded by syntax errors + rOPT_SPC // skip (but capture) whitespace + "(?:" + rCMNT_OR_STR // skip strings and comments + "|(?!" + rCMNT_OR_STR + "|(?<![A-Za-z0-9_])default(?![A-Za-z0-9_])" + ")." // any character that does not start a comment/string/'default' + ")*+" // don't backtrack + "(?<![A-Za-z0-9_])default(?![A-Za-z0-9_])" + ")" + ); + + boost::smatch result; + + std::string top = std::string("\n") + script; + + while (boost::regex_search(top.cbegin(), top.cend(), result, finddecls)) + { + S32 len; + + if (result[1].matched) + { + // variable declaration + gvars.push_back(std::make_pair(result[2], result[0])); + len = std::distance(top.cbegin(), result[0].second); + } + else if (result[3].matched) + { + // function declaration + std::string funcname = result[4]; + std::string funcb = scopeript2(top, 0); + functions[funcname] = funcb; + len = funcb.length(); + } + else //if (result[5].matched) // assumed + { + // found end of globals or syntax error + bottom = top; + break; + } + + // Delete the declaration just found + top.erase(0, len); + } + + if (bottom.empty()) + { + return script; // don't optimize if there's no default state + } + } + + // Find function calls and add the used function declarations back. + // Each time a function is added to the script a new pass is done + // so that function calls inside the added functions are seen. + + bool repass; + do + { + repass = false; + std::map<std::string, std::string>::iterator func_it; + for (func_it = functions.begin(); func_it != functions.end(); func_it++) + { + + std::string funcname = func_it->first; + + if (kept_functions.find(funcname) == kept_functions.end()) + { + boost::smatch calls; + //funcname has to be [a-zA-Z0-9_]+, so we know it's safe + boost::regex findcalls(rDOT_MATCHES_NEWLINE + "(?<![A-Za-z0-9_])(" + funcname + ")" rOPT_SPC "\\(" // a call to the function... + "|(?:" + rCMNT_OR_STR // or comment or string... + "|(?!" + rCMNT_OR_STR + "|(?<![A-Za-z0-9_])" + funcname + rOPT_SPC "\\(" + ")." // or any other character that is not the start for a match of the above + ")" + ); + + std::string::const_iterator bstart = bottom.cbegin(); + while (boost::regex_search(bstart, bottom.cend(), calls, findcalls, boost::match_default)) + { + if (calls[1].matched) + { + std::string function = func_it->second; + kept_functions.insert(funcname); + bottom = function + bottom; + repass = true; + break; + } + else + { + bstart = calls[0].second; + } + } + } + } + } + while (repass); + + // Find variable invocations and add the declarations back if used. + std::vector<std::pair<std::string, std::string> >::reverse_iterator var_it; + for (var_it = gvars.rbegin(); var_it != gvars.rend(); var_it++) + { + const std::string& varname = var_it->first; + boost::regex findvcalls(std::string() + rDOT_MATCHES_NEWLINE + "(?<![a-zA-Z0-9_.])(" + varname + ")(?![a-zA-Z0-9_\"])" // invocation of the variable + "|(?:" rCMNT_OR_STR // a comment or string... + "|(?!" + rCMNT_OR_STR + "|(?<![a-zA-Z0-9_.])" + varname + "(?![a-zA-Z0-9_\"])" + ")." // or any other character that is not the start for a match of the above + ")" + ); + boost::smatch vcalls; + std::string::const_iterator bstart = bottom.cbegin(); + while (boost::regex_search(bstart, bottom.cend(), vcalls, findvcalls, boost::match_default)) + { + if (vcalls[1].matched) + { + bottom = var_it->second + bottom; + break; + } + else + { + bstart = vcalls[0].second; + } + } + } + + script = bottom; + } + catch (const boost::regex_error& e) + { + LLStringUtil::format_map_t args; + args["[WHAT]"] = e.what(); + std::string err = LLTrans::getString("fs_preprocessor_optimizer_regex_err", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + throw; + } + catch (const std::exception& e) + { + LLStringUtil::format_map_t args; + args["[WHAT]"] = e.what(); + std::string err = LLTrans::getString("fs_preprocessor_optimizer_exception", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + throw; + } + return script; +} + +std::string FSLSLPreprocessor::lslcomp(std::string script) +{ + try + { + shredder(script); + static const boost::regex comp_regex("(\\s+)", boost::regex::perl); + script = boost::regex_replace(script, comp_regex, "\n"); + } + catch (boost::regex_error& e) + { + LLStringUtil::format_map_t args; + args["[WHAT]"] = e.what(); + std::string err = LLTrans::getString("fs_preprocessor_compress_regex_err", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + throw; + } + catch (std::exception& e) + { + LLStringUtil::format_map_t args; + args["[WHAT]"] = e.what(); + std::string err = LLTrans::getString("fs_preprocessor_compress_exception", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + throw; + } + return script; +} + +struct ProcCacheInfo +{ + LLViewerInventoryItem* item; + FSLSLPreprocessor* self; +}; + +class trace_include_files : public boost::wave::context_policies::default_preprocessing_hooks +{ +public: + trace_include_files(FSLSLPreprocessor* proc) + : mProc(proc) + { + mAssetStack.push(LLUUID::null.asString()); + mFileStack.push(proc->mMainScriptName); + } + + template <typename ContextT> + bool found_include_directive(ContextT const& ctx, std::string const &filename, bool include_next) + { + std::string cfilename = filename.substr(1, filename.length() - 2); + LL_DEBUGS("FSLSLPreprocessor") << cfilename << ":found_include_directive" << LL_ENDL; + LLUUID item_id = FSLSLPreprocessor::findInventoryByName(cfilename); + if (item_id.notNull()) + { + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (item) + { + std::map<std::string,LLUUID>::iterator it = mProc->cached_assetids.find(cfilename); + bool not_cached = (it == mProc->cached_assetids.end()); + bool changed = true; + if (!not_cached) + { + changed = (mProc->cached_assetids[cfilename] != item->getAssetUUID()); + } + if (not_cached || changed) + { + std::set<std::string>::iterator it = mProc->caching_files.find(cfilename); + if (it == mProc->caching_files.end()) + { + if (not_cached) + { + LLStringUtil::format_map_t args; + args["[FILENAME]"] = cfilename; + mProc->display_message(LLTrans::getString("fs_preprocessor_cache_miss", args)); + } + else /*if(changed)*/ + { + LLStringUtil::format_map_t args; + args["[FILENAME]"] = cfilename; + mProc->display_message(LLTrans::getString("fs_preprocessor_cache_invalidated", args)); + } + //one is always true + mProc->caching_files.insert(cfilename); + ProcCacheInfo* info = new ProcCacheInfo; + info->item = item; + info->self = mProc; + LLPermissions perm(((LLInventoryItem*)item)->getPermissions()); + gAssetStorage->getInvItemAsset(LLHost(), + gAgent.getID(), + gAgent.getSessionID(), + perm.getOwner(), + LLUUID::null, + item->getUUID(), + LLUUID::null, + item->getType(), + &FSLSLPreprocessor::FSProcCacheCallback, + info, + TRUE); + return true; + } + } + } + } + else + { + //todo check on HDD in user defined dir for file in question + } + //++include_depth; + return false; + } + + template <typename ContextT> + void opened_include_file(ContextT const& ctx, + std::string const &relname, std::string const& absname, + bool is_system_include) + { + + ContextT& usefulctx = const_cast<ContextT&>(ctx); + std::string id; + std::string filename = boost::filesystem::path(relname).filename().string(); + std::map<std::string,LLUUID>::iterator it = mProc->cached_assetids.find(filename); + if (it != mProc->cached_assetids.end()) + { + id = mProc->cached_assetids[filename].asString(); + } + else + { + id = "NOT_IN_WORLD";//I guess, still need to add external includes atm + } + mAssetStack.push(id); + std::string macro = "__ASSETID__"; + usefulctx.remove_macro_definition(macro, true); + std::string def = llformat("%s=\"%s\"", macro.c_str(), id.c_str()); + usefulctx.add_macro_definition(def, false); + + mFileStack.push(filename); + macro = "__SHORTFILE__"; + usefulctx.remove_macro_definition(macro, true); + def = llformat("%s=\"%s\"", macro.c_str(), filename.c_str()); + usefulctx.add_macro_definition(def, false); + } + + + template <typename ContextT> + void returning_from_include_file(ContextT const& ctx) + { + ContextT& usefulctx = const_cast<ContextT&>(ctx); + if (mAssetStack.size() > 1) + { + mAssetStack.pop(); + std::string id = mAssetStack.top(); + std::string macro = "__ASSETID__"; + usefulctx.remove_macro_definition(macro, true); + std::string def = llformat("%s=\"%s\"", macro.c_str(), id.c_str()); + usefulctx.add_macro_definition(def, false); + + mFileStack.pop(); + std::string filename = mFileStack.top(); + macro = "__SHORTFILE__"; + usefulctx.remove_macro_definition(macro, true); + def = llformat("%s=\"%s\"", macro.c_str(), filename.c_str()); + usefulctx.add_macro_definition(def, false); + }//else wave did something really wrong + } + + template <typename ContextT, typename ExceptionT> + void throw_exception(ContextT const& ctx, ExceptionT const& e) + { + std::string err; + err = "warning: last line of file ends without a newline"; + if (!err.compare( e.description())) + { + err = "Ignoring warning: "; + err += e.description(); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + } + else + { + boost::throw_exception(e); + } + } + +private: + FSLSLPreprocessor* mProc; + std::stack<std::string> mAssetStack; + std::stack<std::string> mFileStack; +}; + +void cache_script(const std::string& name, std::string content) +{ + content += "\n";/*hack!*/ + LL_DEBUGS("FSLSLPreprocessor") << "writing " << name << " to cache" << LL_ENDL; + std::string path = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "lslpreproc", name); + llofstream outstream(path); + if(outstream.is_open()) + { + outstream.write(content.c_str(), content.length()); + } +} + +void FSLSLPreprocessor::FSProcCacheCallback(const LLUUID& iuuid, LLAssetType::EType type, void *userdata, S32 result, LLExtStat extstat) +{ + LLUUID uuid = iuuid; + LL_DEBUGS("FSLSLPreprocessor") << "cachecallback called" << LL_ENDL; + ProcCacheInfo* info = (ProcCacheInfo*)userdata; + LLViewerInventoryItem* item = info->item; + FSLSLPreprocessor* self = info->self; + if (item && self) + { + std::string name = item->getName(); + if (result == LL_ERR_NOERR) + { + LLFileSystem file(uuid, type); + if (file.open()) + { + S32 file_length = file.getSize(); + + std::string content; + content.resize(file_length + 1, 0); + file.read((U8*)&content[0], file_length); + + content = utf8str_removeCRLF(content); + content = self->decode(content); + /*content += llformat("\n#define __UP_ITEMID__ __ITEMID__\n#define __ITEMID__ %s\n",uuid.asString().c_str())+content; + content += "\n#define __ITEMID__ __UP_ITEMID__\n";*/ + //prolly wont work and ill have to be not lazy, but worth a try + + if (boost::filesystem::native(name)) + { + LL_DEBUGS("FSLSLPreprocessor") << "native name of " << name << LL_ENDL; + LLStringUtil::format_map_t args; + args["[FILENAME]"] = name; + self->display_message(LLTrans::getString("fs_preprocessor_cache_completed", args)); + cache_script(name, content); + std::set<std::string>::iterator loc = self->caching_files.find(name); + if (loc != self->caching_files.end()) + { + LL_DEBUGS("FSLSLPreprocessor") << "finalizing cache" << LL_ENDL; + self->caching_files.erase(loc); + //self->cached_files.insert(name); + if (uuid.isNull())uuid.generate(); + item->setAssetUUID(uuid); + self->cached_assetids[name] = uuid;//.insert(uuid.asString()); + self->start_process(); + } + else + { + LL_DEBUGS("FSLSLPreprocessor") << "something went wrong" << LL_ENDL; + } + } + else + { + LLStringUtil::format_map_t args; + args["[FILENAME]"] = name; + self->display_error(LLTrans::getString("fs_preprocessor_cache_unsafe", args)); + } + } + } + else + { + LLStringUtil::format_map_t args; + args["[FILENAME]"] = name; + self->display_error(LLTrans::getString("fs_preprocessor_caching_err", args)); + } + } + + if (info) + { + delete info; + } +} + +void FSLSLPreprocessor::preprocess_script(BOOL close, bool sync, bool defcache) +{ + mClose = close; + mSync = sync; + mDefinitionCaching = defcache; + caching_files.clear(); + LLStringUtil::format_map_t args; + display_message(LLTrans::getString("fs_preprocessor_starting")); + + LLFile::mkdir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,"") + gDirUtilp->getDirDelimiter() + "lslpreproc"); + std::string script = mCore->mEditor->getText(); + if (mMainScriptName.empty())//more sanity + { + const LLInventoryItem* item = NULL; + LLPreview* preview = (LLPreview*)mCore->mUserdata; + if (preview) + { + item = preview->getItem(); + } + + if (item) + { + mMainScriptName = item->getName(); + } + else + { + mMainScriptName = "(Unknown)"; + } + } + const std::string& name = mMainScriptName; + cached_assetids[name] = LLUUID::null; + cache_script(name, script); + //start the party + start_process(); +} + +void FSLSLPreprocessor::preprocess_script(const LLUUID& asset_id, LLScriptQueueData* data, LLAssetType::EType type, const std::string& script_data) +{ + if(!data) + { + return; + } + + mScript = FSLSLPreprocessor::decode(script_data); + mAssetID = asset_id; + mData = data; + mType = type; + + mDefinitionCaching = false; + caching_files.clear(); + LLStringUtil::format_map_t args; + display_message(LLTrans::getString("fs_preprocessor_starting")); + + LLFile::mkdir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,"") + gDirUtilp->getDirDelimiter() + "lslpreproc"); + + if (mData->mItem) + { + mMainScriptName = mData->mItem->getName(); + } + else + { + mMainScriptName = "(Unknown)"; + } + + const std::string& name = mMainScriptName; + cached_assetids[name] = LLUUID::null; + cache_script(name, mScript); + //start the party + start_process(); +} + +const std::string lazy_list_set_func("\ +list lazy_list_set(list L, integer i, list v)\n\ +{\n\ + while (llGetListLength(L) < i)\n\ + L = L + 0;\n\ + return llListReplaceList(L, v, i, i);\n\ +}\n\ +"); + +static void subst_lazy_references(std::string& script, std::string_view retype, std::string fn) +{ + std::string ref; + do + { + ref = script; + + static std::map<std::string, boost::regex, std::less<>> lazy_ref_cache; + auto iter = lazy_ref_cache.find(retype); + if(iter == lazy_ref_cache.end()) + { + boost::regex ref_regex(std::string(rDOT_MATCHES_NEWLINE + rCMNT_OR_STR "|" + "\\(" rOPT_SPC ) + std::string(retype) + std::string(rOPT_SPC "\\)" rOPT_SPC + // group 1: leading parenthesis + "(\\()?" + // group 2: identifier + rOPT_SPC "([a-zA-Z_][a-zA-Z0-9_]*+)" + rOPT_SPC "\\[" + // group 3: subindex expression + "((?:" + rCMNT_OR_STR + // group 4: recursive bracketed expression + "|(\\[(?:" rCMNT_OR_STR "|[^][]|(?4))*+\\])" // recursive bracketed expression (e.g. []!=[]) + "|[^][]" // or anything else + ")+?)" // (non-greedy) + "\\]" + // group 5: trailing parenthesis + "(\\))?" + )); + iter = lazy_ref_cache.emplace(retype, std::move(ref_regex)).first; + } + script = boost::regex_replace(script, iter->second, + // Boost supports conditions in format strings used in regex_replace. + // ?nX:Y means output X if group n matched, else output Y. $n means output group n. + // $& means output the whole match, which is used here to not alter the string. + // Parentheses are used for grouping; they have to be prefixed with \ to output them. + std::string("?2" // if group 2 matched, we have a (type)variable[index] to substitute + // if first paren is present, require the second (output the original text if not present) + "(?1(?5$1") + fn + std::string("\\($2,$3\\)$5:$&):") + // if first parent is not present, copy $5 verbatim (matched or not) + + fn + std::string("\\($2,$3\\)$5):" + // if $2 didn't match, output whatever matched (string or comment) + "$&"), boost::format_all); // format_all enables these features + } + while (script != ref); +} + +static std::string reformat_lazy_lists(const std::string& in_script) +{ + static const boost::regex lazy_list_regex(rDOT_MATCHES_NEWLINE + rCMNT_OR_STR + // exclude some keywords as possible identifiers that can + // be followed by an opening square bracket (FIRE-20278) + "|(?:return|do|else)(?![A-Za-z0-9_])" + // group 1: identifier + "|([a-zA-Z_][a-zA-Z0-9_]*+)" rOPT_SPC + // group 2: expression within brackets + "\\[((?:" rCMNT_OR_STR + // group 3: recursive bracketed expression + "|(\\[(?:" rCMNT_OR_STR "|[^][]|(?3))*+\\])" // recursive bracketed expression (e.g. []!=[]) + "|[^][]" // or anything else + ")++)\\]" rOPT_SPC + "=(?!=)" rOPT_SPC // = but not == + // group 4: right-hand side expression + "((?:" rCMNT_OR_STR + // group 5: recursive parenthesized expression + "|(\\((?:" rCMNT_OR_STR "|[^()]|(?5))*+\\))" // recursive parenthesized expression + "|[^()]" // or anything else + ")+?)" // non-greedy + "([;)])" // terminated only with a semicolon or a closing parenthesis + ); + std::string script = boost::regex_replace(in_script, lazy_list_regex, "?1$1=lazy_list_set\\($1,$2,[$4]\\)$6:$&", boost::format_all); + + // replace typed references followed by bracketed subindex with llList2XXXX, + // e.g. (rotation)mylist[3] is replaced with llList2Rot(mylist, (3)) + subst_lazy_references(script, "integer", "llList2Integer"); + subst_lazy_references(script, "float", "llList2Float"); + subst_lazy_references(script, "string", "llList2String"); + subst_lazy_references(script, "key", "llList2Key"); + subst_lazy_references(script, "vector", "llList2Vector"); + subst_lazy_references(script, "(?:rotation|quaternion)", "llList2Rot"); + subst_lazy_references(script, "list", "llList2List"); + + // add lazy_list_set function to top of script + // (it can be overriden by a user function if the optimizer is active) + script = fmt::format(FMT_COMPILE("{}\n{}"), utf8str_removeCRLF(lazy_list_set_func), script); + + return script; +} + + +static inline std::string randstr(S32 len, std::string_view chars) +{ + S32 clen = S32(chars.length()); + S32 built = 0; + std::string ret; + while (built < len) + { + S32 r = std::rand() / ( RAND_MAX / clen ); + r = r % clen;//sanity + ret += chars.at(r); + built += 1; + } + return ret; +} + +static inline std::string quicklabel() +{ + return fmt::format(FMT_COMPILE("c{}"), randstr(5, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")); +} + +static std::string reformat_switch_statements(std::string buffer, bool& lackDefault) +{ + try + { + static const boost::regex findswitches(rDOT_MATCHES_NEWLINE + rCMNT_OR_STR + "|" rSPC "++" // optimization to skip over blocks of whitespace faster + "|(?<![A-Za-z0-9_])(switch" rOPT_SPC "\\()" + "|." + ); + + boost::smatch matches; + std::string::const_iterator bstart = buffer.begin(); + + while (boost::regex_search(bstart, std::string::const_iterator(buffer.end()), matches, findswitches, boost::match_default)) + { + if (matches[1].matched) + { + S32 res = std::distance(buffer.cbegin(), matches[1].first); + + // slen excludes the "(" + S32 slen = std::distance(matches[1].first, matches[1].second) - 1; + + std::string arg = scopeript2(buffer, res + slen, '(', ')'); + + //arg *will have* () around it + if (arg == "begin out of bounds" || arg == "end out of bounds") + { + break; + } + LL_DEBUGS("FSLSLPreprocessor") << "arg=[" << arg << "]" << LL_ENDL;; + std::string rstate = scopeript2(buffer, res + slen + arg.length()); + S32 cutlen = slen + arg.length() + rstate.length(); + + // Call recursively to process nested switch statements (FIRE-10517) + rstate = reformat_switch_statements(rstate, lackDefault); + + //rip off the scope edges + S32 slicestart = rstate.find("{") + 1; + rstate = rstate.substr(slicestart, (rstate.rfind("}") - slicestart) - 1); + LL_DEBUGS("FSLSLPreprocessor") << "rstate=[" << rstate << "]" << LL_ENDL; + + static const boost::regex findcases(rDOT_MATCHES_NEWLINE + rCMNT_OR_STR "|" rSPC "++|(?<![A-Za-z0-9_])(case" rREQ_SPC ")|."); + + boost::smatch statematches; + + std::map<std::string, std::string> ifs; + std::string::const_iterator rstart = rstate.begin(); + + while (boost::regex_search(rstart, std::string::const_iterator(rstate.end()), statematches, findcases, boost::match_default)) + { + if (statematches[1].matched) + { + S32 case_start = std::distance(rstate.cbegin(), statematches[1].first); + S32 next_curl = rstate.find("{", case_start + 1); + S32 next_semi = rstate.find(":", case_start + 1); + S32 case_end = (next_semi == -1) ? next_curl : + (next_curl < next_semi&& next_curl != -1) ? next_curl : next_semi; + S32 caselen = std::distance(statematches[1].first, statematches[1].second); + if (case_end != -1) + { + std::string casearg = rstate.substr(case_start + caselen, case_end - (case_start + caselen)); + LL_DEBUGS("FSLSLPreprocessor") << "casearg=[" << casearg << "]" << LL_ENDL; + std::string label = quicklabel(); + ifs[casearg] = label; + LL_DEBUGS("FSLSLPreprocessor") << "BEFORE[" << rstate << "]" << LL_ENDL; + bool addcurl = (case_end == next_curl ? 1 : 0); + label = "@" + label + ";\n"; + if (addcurl) + { + label += "{"; + } + rstate.erase(case_start, (case_end - case_start) + 1); + rstate.insert(case_start, label); + LL_DEBUGS("FSLSLPreprocessor") << "AFTER[" << rstate << "]" << LL_ENDL; + rstart = rstate.begin() + (case_start + label.length()); + } + else + { + LL_DEBUGS("FSLSLPreprocessor") << "error in regex case_end != -1" << LL_ENDL; + rstate.erase(case_start, caselen); + rstate.insert(case_start, "error; cannot find { or :"); + rstart = rstate.begin() + (case_start + std::strlen("error; cannot find { or :")); + } + } + else + { + rstart = statematches[0].second; + } + } + + std::string deflt = quicklabel(); + bool hasdflt = false; + std::string defstate; + + static const boost::regex defstate_regex(rDOT_MATCHES_NEWLINE + rCMNT_OR_STR "|" rSPC "++" + "|(?<![A-Za-z0-9_])(default)(?:" rOPT_SPC ":|(" rOPT_SPC "\\{))" + , boost::regex::perl); + + defstate = boost::regex_replace(rstate, defstate_regex, "?1@" + deflt + ";$2:$&", boost::format_all); + if (defstate != rstate) + { + hasdflt = true; + rstate = defstate; + } + std::string argl; + std::string jumptable = "{"; + + std::map<std::string, std::string>::iterator ifs_it; + for (ifs_it = ifs.begin(); ifs_it != ifs.end(); ifs_it++) + { + jumptable += fmt::format(FMT_COMPILE("if({} == ({}))jump {};\n"), arg, ifs_it->first, ifs_it->second); + } + std::string brk = quicklabel(); + if (!hasdflt) + { + // Add jump to break position if there's no default (FIRE-17710) + deflt = brk; + lackDefault = true; + } + jumptable += "jump " + deflt + ";\n"; + + rstate = jumptable + rstate + "\n"; + + static const boost::regex defstate_regex2(rDOT_MATCHES_NEWLINE + rCMNT_OR_STR "|" + "(?<![A-Za-z0-9_])break(" rOPT_SPC ";)" + ); + defstate = boost::regex_replace(rstate, defstate_regex2, "?1jump " + brk + "$1:$&", boost::format_all); + if (defstate != rstate || !hasdflt) + { + rstate = defstate; + rstate += "\n@" + brk + ";\n"; + } + rstate += "}"; + + LL_DEBUGS("FSLSLPreprocessor") << "replacing[" << buffer.substr(res, cutlen) << "] with [" << rstate << "]" << LL_ENDL; + buffer.erase(res, cutlen); + buffer.insert(res, rstate); + + bstart = buffer.begin() + (res + rstate.length()); + + } + else /* not matches[1].matched */ + { + // Found a token that is not "switch" - skip it + bstart = matches[0].second; + } + } + } + catch (...) + { + LL_WARNS("FSLSLPreprocessor") << "unexpected exception caught; buffer=[" << buffer << "]" << LL_ENDL; + throw; + } + + return buffer; +} + +void FSLSLPreprocessor::start_process() +{ + if (mWaving) + { + LL_WARNS("FSLSLPreprocessor") << "already waving?" << LL_ENDL; + return; + } + + mWaving = true; + bool lackDefault = false; + boost::wave::util::file_position_type current_position; + std::string input; + if (mStandalone) + { + input = mScript; + } + else + { + input = mCore->mEditor->getText(); + } + std::string rinput = input; + bool preprocessor_enabled = true; + + // Simple check for the "do not preprocess" marker. This logic will NOT survive a conversion into some form of sectional preprocessing as discussed in FIRE-9335, but will serve the basic use case given therein. + { + std::string::size_type location_index = rinput.find("//fspreprocessor off"); + + if (location_index != std::string::npos) + { + std::string section_scanned = input.substr(0, location_index); // Used to compute the line number at which the marker was found. + LLStringUtil::format_map_t args; + args["[LINENUMBER]"] = llformat("%d", std::count(section_scanned.begin(), section_scanned.end(), '\n')); + display_message(LLTrans::getString("fs_preprocessor_disabled_by_script_marker", args)); + preprocessor_enabled = false; + } + } + + // Convert multiline strings for preprocessor + if (preprocessor_enabled) + { + std::ostringstream oaux; + // Simple DFA to parse the code for strings and comments, + // replacing newlines with '\n' inside strings so that the + // C preprocessor can interpret it correctly. + // states: 0=normal, 1=seen '"', 2=seen '"...\', + // 3=seen '/', 4=seen '/*', 5=seen '/*...*', 6=seen '//' + int state = 0; + int nlines = 0; + for (std::string::iterator it = input.begin(); it != input.end(); it++) + { + switch (state) + { + case 1: // inside string, no '\' seen last + if (*it == '\n') + { + // we're inside a string and detected a newline; + // replace with "\\n" + oaux << "\\n"; + nlines++; + continue; // don't store the newline itself + } + else if (*it == '\\') + { + state = 2; + } + else if (*it == '"') + { + oaux << '"'; + // add as many newlines as the string had, + // to respect original line numbers + while (nlines) + { + oaux << '\n'; + nlines--; + } + state = 0; + continue; + } + break; + case 2: // inside string, '\' seen last + // just eat the escaped character + state = 1; + break; + case 3: // in code, '/' seen last + if (*it == '*') + { + state = 4; // multiline comment + } + else if (*it == '/') + { + state = 6; // single-line comment + } + else + { + state = 0; // it was just a slash + } + break; + case 4: // inside multiline comment, no '*' seen last + if (*it == '*') + { + state = 5; + } + break; + case 5: // inside multiline comment, '*' seen last + if (*it == '/') + { + state = 0; + } + else if (*it != '*') + { + state = 4; + } + break; + case 6: // inside single line comment ('//' style) + if (*it == '\n') + { + state = 0; + } + break; + default: // normal code + if (*it == '"') + { + state = 1; + } + else if (*it == '/') + { + state = 3; + } + } + oaux << *it; + } + input = oaux.str(); + } + + //Make sure wave does not complain about missing newline at end of script. + input += "\n"; + std::string output; + + std::string name = mMainScriptName; + static LLCachedControl<bool> lazy_lists_cc(gSavedSettings, "AlchemyPreProcLSLLazyLists"); + static LLCachedControl<bool> use_switch_cc(gSavedSettings, "AlchemyPreProcLSLSwitch"); + static LLCachedControl<bool> use_optimizer(gSavedSettings, "AlchemyPreProcLSLOptimizer"); + static LLCachedControl<bool> enable_hdd_include(gSavedSettings, "AlchemyPreProcEnableHDDInclude"); + static LLCachedControl<bool> use_compression(gSavedSettings, "AlchemyPreProcLSLTextCompress"); + bool lazy_lists = lazy_lists_cc; + bool use_switch = use_switch_cc; + bool errored = false; + if (preprocessor_enabled) + { + std::string settings; + settings = LLTrans::getString("fs_preprocessor_settings_list_prefix") + " preproc"; + if (lazy_lists) + { + settings = settings + " LazyLists"; + } + if (use_switch) + { + settings = settings + " Switches"; + } + if (use_optimizer) + { + settings = settings + " Optimize"; + } + if (enable_hdd_include) + { + settings = settings + " HDDInclude"; + } + if (use_compression) + { + settings = settings + " Compress"; + } + //display the settings + display_message(settings); + + LL_DEBUGS("FSLSLPreprocessor") << settings << LL_ENDL; + std::string err; + try + { + trace_include_files tracer(this); + typedef boost::wave::cpplexer::lex_token<> token_type; + typedef boost::wave::cpplexer::lex_iterator<token_type> lex_iterator_type; + typedef boost::wave::context<std::string::iterator, lex_iterator_type, boost::wave::iteration_context_policies::load_file_to_string, trace_include_files > + context_type; + + context_type ctx(input.begin(), input.end(), name.c_str(), tracer); + ctx.set_language(boost::wave::enable_long_long(ctx.get_language())); + ctx.set_language(boost::wave::enable_prefer_pp_numbers(ctx.get_language())); + ctx.set_language(boost::wave::enable_variadics(ctx.get_language())); + + std::string path = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,"") + gDirUtilp->getDirDelimiter() + "lslpreproc" + gDirUtilp->getDirDelimiter(); + ctx.add_include_path(path.c_str()); + if (enable_hdd_include) + { + std::string hddpath = gSavedSettings.getString("AlchemyPreProcHDDIncludeLocation"); + if (!hddpath.empty()) + { + ctx.add_include_path(hddpath.c_str()); + ctx.add_sysinclude_path(hddpath.c_str()); + } + } + std::string def = llformat("__AGENTKEY__=\"%s\"", gAgentID.asString().c_str());//legacy because I used it earlier + ctx.add_macro_definition(def, false); + def = llformat("__AGENTID__=\"%s\"", gAgentID.asString().c_str()); + ctx.add_macro_definition(def, false); + def = llformat("__AGENTIDRAW__=%s", gAgentID.asString().c_str()); + ctx.add_macro_definition(def, false); + std::string aname = gAgentAvatarp->getFullname(); + def = llformat("__AGENTNAME__=\"%s\"", aname.c_str()); + ctx.add_macro_definition(def, false); + def = llformat("__ASSETID__=%s", LLUUID::null.asString().c_str()); + ctx.add_macro_definition(def, false); + def = llformat("__SHORTFILE__=\"%s\"", name.c_str()); + ctx.add_macro_definition(def, false); + def = llformat("__UNIXTIME__=%i", (S32)time_corrected()); + ctx.add_macro_definition(def, false); + + ctx.add_macro_definition("list(...)=((list)(__VA_ARGS__))", false); + ctx.add_macro_definition("float(...)=((float)(__VA_ARGS__))", false); + ctx.add_macro_definition("integer(...)=((integer)(__VA_ARGS__))", false); + ctx.add_macro_definition("key(...)=((key)(__VA_ARGS__))", false); + ctx.add_macro_definition("rotation(...)=((rotation)(__VA_ARGS__))", false); + ctx.add_macro_definition("quaternion(...)=((quaternion)(__VA_ARGS__))", false); + ctx.add_macro_definition("string(...)=((string)(__VA_ARGS__))", false); + ctx.add_macro_definition("vector(...)=((vector)(__VA_ARGS__))", false); + + context_type::iterator_type first = ctx.begin(); + context_type::iterator_type last = ctx.end(); + + while (first != last) + { + if (caching_files.size() != 0) + { + mWaving = false; + return; + } + current_position = (*first).get_position(); + + std::string token = std::string((*first).get_value().c_str());//stupid boost bitching even though we know its a std::string + + if (token == "#line") + { + token = "//#line"; + } + + output += token; + + if (!lazy_lists) + { + lazy_lists = ctx.is_defined_macro(std::string("USE_LAZY_LISTS")); + } + + if (!use_switch) + { + use_switch = ctx.is_defined_macro(std::string("USE_SWITCHES")); + } + ++first; + } + } + catch(boost::wave::cpp_exception const& e) + { + errored = true; + // some preprocessing error + LLStringUtil::format_map_t args; + args["[ERR_NAME]"] = e.file_name(); + args["[LINENUMBER]"] = llformat("%d", e.line_no() - 1); + args["[ERR_DESC]"] = e.description(); + std::string err = LLTrans::getString("fs_preprocessor_cpp_exception", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + } + catch(boost::wave::cpplexer::lexing_exception const& e) + { + // lexing preprocessing error + boost::wave::cpplexer::util::severity severity_level = e.severity_level(e.get_errorcode()); + errored = (severity_level != boost::wave::cpplexer::util::severity_warning && severity_level != boost::wave::cpplexer::util::severity_remark); + LLStringUtil::format_map_t args; + std::string severity_text = e.severity_text(e.get_errorcode()); + LLStringUtil::toUpper(severity_text); + args["[SEVERITY]"] = severity_text; + args["[ERR_NAME]"] = e.file_name(); + args["[LINENUMBER]"] = llformat("%d", e.line_no() - 1); + args["[ERR_DESC]"] = e.description(); + std::string err = LLTrans::getString("fs_preprocessor_lexing_exception", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + } + catch(std::exception const& e) + { + errored = true; + LLStringUtil::format_map_t args; + args["[ERR_NAME]"] = std::string(current_position.get_file().c_str()); + args["[LINENUMBER]"] = llformat("%d", current_position.get_line()); + args["[ERR_DESC]"] = e.what(); + display_error(LLTrans::getString("fs_preprocessor_exception", args)); + } + catch (...) + { + errored = true; + LLStringUtil::format_map_t args; + args["[ERR_NAME]"] = std::string(current_position.get_file().c_str()); + args["[LINENUMBER]"] = llformat("%d", current_position.get_line()); + std::string err = LLTrans::getString("fs_preprocessor_error", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + } + } + + if (preprocessor_enabled && !errored) + { + if (lazy_lists) + { + try + { + display_message(LLTrans::getString("fs_preprocessor_lazylist_start")); + try + { + output = reformat_lazy_lists(output); + } + catch (boost::regex_error& e) + { + LLStringUtil::format_map_t args; + args["[WHAT]"] = e.what(); + std::string err = LLTrans::getString("fs_preprocessor_lazylist_regex_err", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + throw; + } + catch (std::exception& e) + { + LLStringUtil::format_map_t args; + args["[WHAT]"] = e.what(); + std::string err = LLTrans::getString("fs_preprocessor_lazylist_exception", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + throw; + } + } + catch(...) + { + errored = true; + display_error(LLTrans::getString("fs_preprocessor_lazylist_unexpected_exception")); + } + } + + if (use_switch) + { + try + { + display_message(LLTrans::getString("fs_preprocessor_switchstatement_start")); + try + { + output = reformat_switch_statements(output, lackDefault); + } + catch (boost::regex_error& e) + { + LLStringUtil::format_map_t args; + args["[WHAT]"] = e.what(); + std::string err = LLTrans::getString("fs_preprocessor_switchstatement_regex_err", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + throw; + } + catch (std::exception& e) + { + LLStringUtil::format_map_t args; + args["[WHAT]"] = e.what(); + std::string err = LLTrans::getString("fs_preprocessor_switchstatement_exception", args); + LL_WARNS("FSLSLPreprocessor") << err << LL_ENDL; + display_error(err); + throw; + } + } + catch(...) + { + errored = true; + display_error(LLTrans::getString("fs_preprocessor_switchstatement_unexpected_exception")); + } + } + } + + if (!mDefinitionCaching) + { + if (!errored) + { + if (preprocessor_enabled && use_optimizer) + { + display_message(LLTrans::getString("fs_preprocessor_optimizer_start")); + try + { + output = lslopt(output); + } + catch(...) + { + errored = true; + display_error(LLTrans::getString("fs_preprocessor_optimizer_unexpected_exception")); + } + } + } + else + { + // FIRE-31718: Preprocessor crashes viewer on recursive #include + + // Truncate the resulting preprocessed script to something the text field can handle without + // freezing for so long the viewer disconnects. The usual script source code limit is 64kB so + // let's play it safe and allow twice as much here. The script is most likely already unusable + // at this point due to the preprocessor bailing out with an error earlier, so a truncated + // version doesn't hurt more than it already did. + if (output.size() > 128 * 1024) + { + output.resize(128 * 1024); + display_error(LLTrans::getString("fs_preprocessor_truncated")); + } + } + + if (!errored) + { + if (preprocessor_enabled && use_compression) + { + display_message(LLTrans::getString("fs_preprocessor_compress_exception")); + try + { + output = lslcomp(output); + } + catch(...) + { + errored = true; + display_error(LLTrans::getString("fs_preprocessor_compress_unexpected_exception")); + } + } + } + + if (preprocessor_enabled) + { + output = encode(rinput) + "\n\n" + output; + } + else + { + output = rinput; + } + + if (mStandalone) + { + LLFloaterCompileQueue::scriptPreprocComplete(mAssetID, mData, mType, output); + } + else + { + FSLSLPreProcViewer* outfield = mCore->mPostEditor; + if (outfield) + { + outfield->setText(LLStringExplicit(output)); + } + mCore->mPostScript = output; + mCore->enableSave(TRUE); // The preprocessor run forces a change. (For FIRE-10173) -Sei + mCore->doSaveComplete((void*)mCore, mClose, mSync); + } + } + //if (lackDefault) + //{ + // LLNotificationsUtil::add("DefaultLabelMissing"); + //} + mWaving = false; +} + +void FSLSLPreprocessor::display_message(const std::string& err) +{ + if (mStandalone) + { + LLFloaterCompileQueue::scriptLogMessage(mData, err); + } + else + { + mCore->mErrorList->setCommentText(err); + } +} + +void FSLSLPreprocessor::display_error(const std::string& err) +{ + if (mStandalone) + { + LLFloaterCompileQueue::scriptLogMessage(mData, err); + } + else + { + LLSD row; + row["columns"][0]["value"] = err; + row["columns"][0]["font"] = "SANSSERIF_SMALL"; + mCore->mErrorList->addElement(row); + } +} + + +bool FSLSLPreprocessor::mono_directive(std::string const& text, bool agent_inv) +{ + bool domono = agent_inv; + + if (text.find("//mono\n") != std::string::npos) + { + domono = true; + } + else if (text.find("//lsl2\n") != std::string::npos) + { + domono = false; + } + return domono; +} diff --git a/indra/newview/fslslpreproc.h b/indra/newview/fslslpreproc.h new file mode 100644 index 0000000000000000000000000000000000000000..61ebe50a33b2dadb8d7501f6e29acc6a8046162c --- /dev/null +++ b/indra/newview/fslslpreproc.h @@ -0,0 +1,106 @@ +/** + * @file fslslpreproc.h + * Copyright (c) 2010 + * + * Modular Systems All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. Neither the name Modular Systems nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY MODULAR SYSTEMS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MODULAR SYSTEMS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FS_LSLPREPROC_H +#define FS_LSLPREPROC_H + +#include "llviewerprecompiledheaders.h" +#include "llpreviewscript.h" + +#define DARWINPREPROC +//force preproc on mac + +struct LLScriptQueueData; + +class FSLSLPreprocessor +{ + LOG_CLASS(FSLSLPreprocessor); +public: + + FSLSLPreprocessor(LLScriptEdCore* corep) + : mCore(corep), mWaving(false), mClose(FALSE), mSync(false), mStandalone(false) + {} + + FSLSLPreprocessor() + : mWaving(false), mClose(FALSE), mSync(false), mStandalone(true) + {} + + static bool mono_directive(std::string const& text, bool agent_inv = true); + std::string encode(const std::string& script); + std::string decode(const std::string& script); + + std::string lslopt(std::string script); + std::string lslcomp(std::string script); + + static LLUUID findInventoryByName(std::string name); + static void FSProcCacheCallback(const LLUUID& uuid, LLAssetType::EType type, + void *userdata, S32 result, LLExtStat extstat); + void preprocess_script(BOOL close = FALSE, bool sync = false, bool defcache = false); + void preprocess_script(const LLUUID& asset_id, LLScriptQueueData* data, LLAssetType::EType type, const std::string& script_data); + void start_process(); + void display_message(const std::string& err); + void display_error(const std::string& err); + + //dual function, determines if files have been modified this session and if we have cached them + //also assetids exposed in-preprocessing as a predefined macro for use in include once style include files, e.g. #define THISFILE file_ ## __ASSETIDRAW__ + //in case it isn't obvious, the viewer only sets the asset id on a successful script save (of a full perm script), or in preproc on-cache + //so this is only applicable to fully permissive scripts; which is just fine, since if it isn't full perm it isn't really useful as a include anyway. + //in the event of a no-trans script (only less than full thats readable), the server sends null key, and we will set a random uuid. + //This uuid should be overwritten if they edit that script, whether with the real id or null key is irrelevant in this case. + //theoretically, if the asset IDs were exposed for full perm scripts without downloading the script at least once, it would save unnecessary caching + //as this isn't the case I'm not going to preserve this structure across logins. + + //(it seems rather dumb that readable scripts don't show the asset id without a DL, but thats beside the point.) + static std::map<std::string, LLUUID> cached_assetids; + + static std::map<std::string, std::string> decollided_literals; + + std::set<std::string> caching_files; + std::set<std::string> defcached_files; + bool mDefinitionCaching; + + LLScriptEdCore* mCore; + bool mWaving; + BOOL mClose; + bool mSync; + std::string mMainScriptName; + + // Compile queue + bool mStandalone; + std::string mScript; + LLUUID mAssetID; + LLScriptQueueData* mData; + LLAssetType::EType mType; +}; + +#endif // FS_LSLPREPROC_H diff --git a/indra/newview/fslslpreprocviewer.cpp b/indra/newview/fslslpreprocviewer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..24e7edeb5ac4be52b8d99c71ae3f37819dcdaefb --- /dev/null +++ b/indra/newview/fslslpreprocviewer.cpp @@ -0,0 +1,57 @@ +/** + * @file fslslpreprocviewer.cpp + * @brief Specialized LLScriptEditor class for displaying LSL preprocessor output + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (c) 2016 Ansariel Hiller @ Second Life + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "fslslpreprocviewer.h" + +static LLDefaultChildRegistry::Register<FSLSLPreProcViewer> r("lsl_preproc_viewer"); + +FSLSLPreProcViewer::FSLSLPreProcViewer(const Params& p) +: LLScriptEditor(p) +{ +} + +BOOL FSLSLPreProcViewer::handleKeyHere(KEY key, MASK mask ) +{ + // Normal key handling + BOOL handled = handleNavigationKey( key, mask ) + || handleSelectionKey(key, mask) + || handleControlKey(key, mask); + + if (handled) + { + resetCursorBlink(); + needsScroll(); + } + + return handled; +} + +BOOL FSLSLPreProcViewer::handleUnicodeCharHere(llwchar uni_char) +{ + return FALSE; +} diff --git a/indra/newview/fslslpreprocviewer.h b/indra/newview/fslslpreprocviewer.h new file mode 100644 index 0000000000000000000000000000000000000000..bccf2b33e28515e41c8c0903bacbe724277dd025 --- /dev/null +++ b/indra/newview/fslslpreprocviewer.h @@ -0,0 +1,60 @@ +/** + * @file fslslpreprocviewer.h + * @brief Specialized LLScriptEditor class for displaying LSL preprocessor output + * + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (c) 2016 Ansariel Hiller @ Second Life + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ + */ + +#ifndef FS_LSLPREPROCVIEWER_H +#define FS_LSLPREPROCVIEWER_H + +#include "llscripteditor.h" + +class FSLSLPreProcViewer : public LLScriptEditor +{ +public: + + struct Params : public LLInitParam::Block<Params, LLScriptEditor::Params> + { + Params() + {} + }; + + virtual ~FSLSLPreProcViewer() = default; + + virtual BOOL handleKeyHere(KEY key, MASK mask ); + virtual BOOL handleUnicodeCharHere(llwchar uni_char); + + virtual BOOL canCut() const { return false; } + virtual BOOL canPaste() const { return false; } + virtual BOOL canUndo() const { return false; } + virtual BOOL canRedo() const { return false; } + virtual BOOL canPastePrimary() const { return false; } + virtual BOOL canDoDelete() const { return false; } + +protected: + friend class LLUICtrlFactory; + FSLSLPreProcViewer(const Params& p); +}; + +#endif // FS_LSLPREPROCVIEWER_H diff --git a/indra/newview/llcompilequeue.cpp b/indra/newview/llcompilequeue.cpp index a5c9400ed25f47cf846c75dcbb885b6be32a4063..9a0b570986ba9d025660dcb771aed6af443120fc 100644 --- a/indra/newview/llcompilequeue.cpp +++ b/indra/newview/llcompilequeue.cpp @@ -53,6 +53,7 @@ #include "lldir.h" #include "llnotificationsutil.h" #include "llviewerstats.h" +#include "llfilesystem.h" #include "lluictrlfactory.h" #include "lltrans.h" @@ -62,6 +63,9 @@ #include "llviewerassetupload.h" #include "llcorehttputil.h" +#include "fslslpreproc.h" +#include "llsdutil.h" + namespace { @@ -102,14 +106,19 @@ namespace class HandleScriptUserData { public: - HandleScriptUserData(const std::string &pumpname) : - mPumpname(pumpname) + HandleScriptUserData(const std::string &pumpname, LLScriptQueueData* data) : + mPumpname(pumpname), + mData(data) { } + HandleScriptUserData() = default; const std::string &getPumpName() const { return mPumpname; } + LLScriptQueueData* getData() const { return mData; } + private: std::string mPumpname; + LLScriptQueueData* mData; }; @@ -159,23 +168,6 @@ class LLQueuedScriptAssetUpload : public LLScriptAssetUpload std::string mScriptName; }; -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -struct LLScriptQueueData -{ - LLUUID mQueueID; - LLUUID mTaskId; - LLPointer<LLInventoryItem> mItem; - LLHost mHost; - LLUUID mExperienceId; - std::string mExperiencename; - LLScriptQueueData(const LLUUID& q_id, const LLUUID& task_id, LLInventoryItem* item) : - mQueueID(q_id), mTaskId(task_id), mItem(new LLInventoryItem(item)) {} - -}; - ///---------------------------------------------------------------------------- /// Class LLFloaterScriptQueue ///---------------------------------------------------------------------------- @@ -256,6 +248,10 @@ LLFloaterCompileQueue::LLFloaterCompileQueue(const LLSD& key) setTitle(LLTrans::getString("CompileQueueTitle")); setStartString(LLTrans::getString("CompileQueueStart")); + if(gSavedSettings.getBOOL("AlchemyLSLPreprocessor")) + { + mLSLProc = std::make_unique<FSLSLPreprocessor>(); + } } LLFloaterCompileQueue::~LLFloaterCompileQueue() @@ -311,6 +307,30 @@ void LLFloaterCompileQueue::handleScriptRetrieval(const LLUUID& assetId, { result["message"] = LLTrans::getString("CompileQueueUnknownFailure"); } + + delete ((HandleScriptUserData *)userData)->getData(); + } + else if (gSavedSettings.getBOOL("AlchemyLSLPreprocessor")) + { + LLScriptQueueData* data = ((HandleScriptUserData *)userData)->getData(); + LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance<LLFloaterCompileQueue>("compile_queue", data->mQueueID); + + if (queue && queue->mLSLProc) + { + LLFileSystem file(assetId, type); + if (file.open()) + { + S32 file_length = file.getSize(); + std::vector<char> script_data(file_length + 1); + file.read((U8*)&script_data[0], file_length); + // put a EOS at the end + script_data[file_length] = 0; + + queue->addProcessingMessage("CompileQueuePreprocessing", LLSD().with("SCRIPT", data->mItem->getName())); + queue->mLSLProc->preprocess_script(assetId, data, type, LLStringExplicit(&script_data[0])); + } + } + result["preproc"] = true; } LLEventPumps::instance().post(((HandleScriptUserData *)userData)->getPumpName(), result); @@ -427,8 +447,17 @@ bool LLFloaterCompileQueue::processScript(LLHandle<LLFloaterCompileQueue> hfloat } { - HandleScriptUserData userData(pump.getName()); - + HandleScriptUserData userData; + if (gSavedSettings.getBOOL("AlchemyLSLPreprocessor")) + { + // Need to dump some stuff into an LLScriptQueueData struct for the LSL PreProc. + LLScriptQueueData* datap = new LLScriptQueueData(hfloater.get()->getKey().asUUID(), object->getID(), experienceId, item); + userData = HandleScriptUserData(pump.getName(), datap); + } + else + { + userData = HandleScriptUserData(pump.getName(), NULL); + } // request the asset gAssetStorage->getInvItemAsset(LLHost(), @@ -470,6 +499,12 @@ bool LLFloaterCompileQueue::processScript(LLHandle<LLFloaterCompileQueue> hfloat return true; } + if (result.has("preproc")) + { + // LSL Preprocessor handles it from here on + return true; + } + LLUUID assetId = result["asset_id"]; std::string url = object->getRegion()->getCapability("UpdateScriptTask"); @@ -884,3 +919,135 @@ void LLFloaterScriptQueue::objectScriptProcessingQueueCoro(std::string action, L LL_DEBUGS("SCRIPTQ") << "LLExeceptionStaleHandle caught! Floater has most likely been closed." << LL_ENDL; } } + +class LLScriptAssetUploadWithId: public LLScriptAssetUpload +{ +public: + LLScriptAssetUploadWithId( LLUUID taskId, LLUUID itemId, TargetType_t targetType, + bool isRunning, std::string scriptName, LLUUID queueId, LLUUID exerienceId, std::string buffer, taskUploadFinish_f finish ) + : LLScriptAssetUpload( taskId, itemId, targetType, isRunning, exerienceId, buffer, finish), + mScriptName(scriptName), + mQueueId(queueId) + { + } + + virtual LLSD prepareUpload() + { + LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance<LLFloaterCompileQueue>("compile_queue", LLSD(mQueueId)); + if (queue) + { + LLStringUtil::format_map_t args; + args["OBJECT_NAME"] = getScriptName(); + std::string message = queue->getString("Compiling", args); + + queue->addStringMessage(message); + } + + return LLBufferedAssetUploadInfo::prepareUpload(); + } + + std::string getScriptName() const { return mScriptName; } + +private: + void setScriptName(const std::string &scriptName) { mScriptName = scriptName; } + + LLUUID mQueueId; + std::string mScriptName; +}; + +/*static*/ +void LLFloaterCompileQueue::finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, std::string scriptName, LLUUID queueId) +{ + LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance<LLFloaterCompileQueue>("compile_queue", LLSD(queueId)); + if (queue) + { + // Bytecode save completed + if (response["compiled"]) + { + std::string message = std::string("Compilation of \"") + scriptName + std::string("\" succeeded"); + LL_INFOS() << message << LL_ENDL; + LLStringUtil::format_map_t args; + args["OBJECT_NAME"] = scriptName; + queue->addStringMessage(queue->getString("CompileSuccess", args)); + } + else + { + LLSD compile_errors = response["errors"]; + for (LLSD::array_const_iterator line = compile_errors.beginArray(); + line < compile_errors.endArray(); line++) + { + std::string str = line->asString(); + str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); + queue->addStringMessage(str); + } + LL_INFOS() << response["errors"] << LL_ENDL; + } + } +} + +// This is the callback after the script has been processed by preproc +// static +void LLFloaterCompileQueue::scriptPreprocComplete(const LLUUID& asset_id, LLScriptQueueData* data, LLAssetType::EType type, const std::string& script_text) +{ + LL_INFOS() << "LLFloaterCompileQueue::scriptPreprocComplete()" << LL_ENDL; + if (!data) + { + return; + } + LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance<LLFloaterCompileQueue>("compile_queue", data->mQueueID); + + if (queue) + { + std::string filename; + std::string uuid_str; + asset_id.toString(uuid_str); + filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str) + llformat(".%s",LLAssetType::lookup(type)); + + const bool is_running = true; + LLViewerObject* object = gObjectList.findObject(data->mTaskId); + if (object) + { + std::string scriptName = data->mItem->getName(); + std::string url = object->getRegion()->getCapability("UpdateScriptTask"); + if (!url.empty()) + { + queue->addProcessingMessage("CompileQueuePreprocessingComplete", LLSD().with("SCRIPT", scriptName)); + + LLBufferedAssetUploadInfo::taskUploadFinish_f proc = boost::bind(&LLFloaterCompileQueue::finishLSLUpload, _1, _2, _3, _4, + scriptName, data->mQueueID); + + LLResourceUploadInfo::ptr_t uploadInfo( new LLScriptAssetUploadWithId( + data->mTaskId, + data->mItem->getUUID(), + (queue->mMono) ? LLScriptAssetUpload::MONO : LLScriptAssetUpload::LSL2, + is_running, + scriptName, + data->mQueueID, + data->mExperienceId, + script_text, + proc)); + + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + } + else + { + queue->addStringMessage(LLTrans::getString("CompileQueueServiceUnavailable")); + } + } + } + delete data; +} + +// static +void LLFloaterCompileQueue::scriptLogMessage(LLScriptQueueData* data, std::string message) +{ + if (!data) + { + return; + } + LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance<LLFloaterCompileQueue>("compile_queue", data->mQueueID); + if (queue) + { + queue->addStringMessage(message); + } +} diff --git a/indra/newview/llcompilequeue.h b/indra/newview/llcompilequeue.h index 2d0886c3da64ee4c77907d464be7881845cab5d2..472cd61dc7dd81cadb45edb6d6416715208e2f17 100644 --- a/indra/newview/llcompilequeue.h +++ b/indra/newview/llcompilequeue.h @@ -36,6 +36,25 @@ #include "llevents.h" class LLScrollListCtrl; +class FSLSLPreprocessor; + +struct LLScriptQueueData +{ + LLUUID mQueueID; + LLUUID mTaskId; + LLPointer<LLInventoryItem> mItem; + LLUUID mExperienceId; + std::string mExperiencename; + + LLScriptQueueData(const LLUUID& q_id, const LLUUID& task_id, const LLUUID& experience_id, LLInventoryItem* item) : + mQueueID(q_id), + mTaskId(task_id), + mExperienceId(experience_id), + mItem(new LLInventoryItem(item)) + { } + +}; +// </FS:KC> //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLFloaterScriptQueue @@ -126,6 +145,9 @@ class LLFloaterCompileQueue final : public LLFloaterScriptQueue void experienceIdsReceived( const LLSD& content ); BOOL hasExperience(const LLUUID& id)const; + static void finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, std::string scriptName, LLUUID queueId); + static void scriptPreprocComplete(const LLUUID& asset_id, LLScriptQueueData* data, LLAssetType::EType type, const std::string& script_text); + static void scriptLogMessage(LLScriptQueueData* data, std::string message); protected: LLFloaterCompileQueue(const LLSD& key); virtual ~LLFloaterCompileQueue(); @@ -142,6 +164,8 @@ class LLFloaterCompileQueue final : public LLFloaterScriptQueue static void processExperienceIdResults(LLSD result, LLUUID parent); //uuid_list_t mAssetIds; // list of asset IDs processed. uuid_list_t mExperienceIds; + + std::unique_ptr<FSLSLPreprocessor> mLSLProc; }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/indra/newview/llfloatergotoline.cpp b/indra/newview/llfloatergotoline.cpp index ad690b4ad5a6dc0e136817807d878183ea3d0615..3d18a27f12273a8314f11d17d0b37d661e829e77 100644 --- a/indra/newview/llfloatergotoline.cpp +++ b/indra/newview/llfloatergotoline.cpp @@ -107,14 +107,14 @@ void LLFloaterGotoLine::handleBtnGoto() row = getChild<LLUICtrl>("goto_line")->getValue().asInteger(); if (row >= 0) { - if (mEditorCore && mEditorCore->mEditor) + if (mEditorCore && mEditorCore->mCurrentEditor) { - mEditorCore->mEditor->deselect(); + mEditorCore->mCurrentEditor->deselect(); // [SL:KB] - Patch: UI-ScriptGoToLine | Checked: 2013-12-31 (Catznip-3.6) - mEditorCore->mEditor->scrollTo(row, column); + mEditorCore->mCurrentEditor->scrollTo(row, column); // [/SL:KB] -// mEditorCore->mEditor->setCursor(row, column); - mEditorCore->mEditor->setFocus(TRUE); +// mEditorCore->mCurrentEditor->setCursor(row, column); + mEditorCore->mCurrentEditor->setFocus(TRUE); } } } @@ -145,20 +145,20 @@ void LLFloaterGotoLine::onGotoBoxCommit() row = getChild<LLUICtrl>("goto_line")->getValue().asInteger(); if (row >= 0) { - if (mEditorCore && mEditorCore->mEditor) + if (mEditorCore && mEditorCore->mCurrentEditor) { // [SL:KB] - Patch: UI-ScriptGoToLine | Checked: 2013-12-31 (Catznip-3.6) - mEditorCore->mEditor->scrollTo(row, column); + mEditorCore->mCurrentEditor->scrollTo(row, column); // [/SL:KB] -// mEditorCore->mEditor->setCursor(row, column); +// mEditorCore->mCurrentEditor->setCursor(row, column); S32 rownew = 0; S32 columnnew = 0; - mEditorCore->mEditor->getCurrentLineAndColumn( &rownew, &columnnew, FALSE ); // don't include wordwrap + mEditorCore->mCurrentEditor->getCurrentLineAndColumn( &rownew, &columnnew, FALSE ); // don't include wordwrap if (rownew == row && columnnew == column) { - mEditorCore->mEditor->deselect(); - mEditorCore->mEditor->setFocus(TRUE); + mEditorCore->mCurrentEditor->deselect(); + mEditorCore->mCurrentEditor->setFocus(TRUE); sInstance->closeFloater(); } //else do nothing (if the cursor-position didn't change) } diff --git a/indra/newview/llfloaterscriptedprefs.cpp b/indra/newview/llfloaterscriptedprefs.cpp index 2484a08626ab83d9fa439d509a147d557a775281..9b1baedfe13e4af2cac5f8c824d5e284600ed085 100644 --- a/indra/newview/llfloaterscriptedprefs.cpp +++ b/indra/newview/llfloaterscriptedprefs.cpp @@ -30,7 +30,11 @@ #include "llcolorswatch.h" #include "llscripteditor.h" +#include "lldirpicker.h" +#include "llviewercontrol.h" +#include "llfloaterreg.h" +#include "llpreviewscript.h" LLFloaterScriptEdPrefs::LLFloaterScriptEdPrefs(const LLSD& key) : LLFloater(key) @@ -38,6 +42,7 @@ LLFloaterScriptEdPrefs::LLFloaterScriptEdPrefs(const LLSD& key) { mCommitCallbackRegistrar.add("ScriptPref.applyUIColor", boost::bind(&LLFloaterScriptEdPrefs::applyUIColor, this ,_1, _2)); mCommitCallbackRegistrar.add("ScriptPref.getUIColor", boost::bind(&LLFloaterScriptEdPrefs::getUIColor, this ,_1, _2)); + mCommitCallbackRegistrar.add("ScriptPref.SetPreprocInclude", boost::bind(&LLFloaterScriptEdPrefs::setPreprocInclude, this)); } BOOL LLFloaterScriptEdPrefs::postBuild() @@ -48,6 +53,8 @@ BOOL LLFloaterScriptEdPrefs::postBuild() mEditor->initKeywords(); mEditor->loadKeywords(); } + + getChild<LLButton>("close_btn")->setClickedCallback(boost::bind(&LLFloaterScriptEdPrefs::closeFloater, this, false)); return TRUE; } @@ -63,3 +70,18 @@ void LLFloaterScriptEdPrefs::getUIColor(LLUICtrl* ctrl, const LLSD& param) LLColorSwatchCtrl* color_swatch = dynamic_cast<LLColorSwatchCtrl*>(ctrl); color_swatch->setOriginal(LLUIColorTable::instance().getColor(param.asString())); } +void LLFloaterScriptEdPrefs::setPreprocInclude() +{ + std::string proposed_name(gSavedSettings.getString("AlchemyPreProcHDDIncludeLocation")); + (new LLDirPickerThread(boost::bind(&LLFloaterScriptEdPrefs::changePreprocIncludePath, this, _1, _2), proposed_name))->getFile(); +} + +void LLFloaterScriptEdPrefs::changePreprocIncludePath(const std::vector<std::string>& filenames, const std::string& proposed_name) +{ + std::string dir_name = filenames[0]; + if (!dir_name.empty() && dir_name != proposed_name) + { + std::string new_top_folder(gDirUtilp->getBaseFileName(dir_name)); + gSavedSettings.setString("AlchemyPreProcHDDIncludeLocation", dir_name); + } +} diff --git a/indra/newview/llfloaterscriptedprefs.h b/indra/newview/llfloaterscriptedprefs.h index ee04081335af38dfd3f137a383909ee848b9a206..fb449839ecc5a1faeec9957c6e1ffc5f21542b33 100644 --- a/indra/newview/llfloaterscriptedprefs.h +++ b/indra/newview/llfloaterscriptedprefs.h @@ -45,6 +45,9 @@ class LLFloaterScriptEdPrefs final : public LLFloater void applyUIColor(LLUICtrl* ctrl, const LLSD& param); void getUIColor(LLUICtrl* ctrl, const LLSD& param); + void setPreprocInclude(); + void changePreprocIncludePath(const std::vector<std::string>& filenames, const std::string& proposed_name); + LLScriptEditor* mEditor; }; diff --git a/indra/newview/llfloatersearchreplace.cpp b/indra/newview/llfloatersearchreplace.cpp index b0d014a1f6c2c139c73ee99ca9188673e15d2bb8..0d7bab126657ac3f9ec0439b29ca63045d71b1f5 100644 --- a/indra/newview/llfloatersearchreplace.cpp +++ b/indra/newview/llfloatersearchreplace.cpp @@ -136,11 +136,11 @@ BOOL LLFloaterSearchReplace::handleKeyHere(KEY key, MASK mask) } //static -void LLFloaterSearchReplace::show(LLTextEditor* pEditor) +LLFloaterSearchReplace* LLFloaterSearchReplace::show(LLTextEditor* pEditor) { LLFloaterSearchReplace* pSelf = LLFloaterReg::getTypedInstance<LLFloaterSearchReplace>("search_replace"); if ( (!pSelf) || (!pEditor) ) - return; + return NULL; LLFloater *pDependeeNew = NULL, *pDependeeOld = pSelf->getDependee(); LLView* pView = pEditor->getParent(); @@ -170,6 +170,14 @@ void LLFloaterSearchReplace::show(LLTextEditor* pEditor) pSelf->m_EditorHandle = pEditor->getHandle(); pSelf->openFloater(); + + return pSelf; +} + +//static +LLFloaterSearchReplace* LLFloaterSearchReplace::findInstance() +{ + return LLFloaterReg::findTypedInstance<LLFloaterSearchReplace>("search_replace"); } LLTextEditor* LLFloaterSearchReplace::getEditor() const @@ -213,4 +221,10 @@ void LLFloaterSearchReplace::onReplaceAllClick() } } +void LLFloaterSearchReplace::setCanReplace(bool can_replace) +{ + m_pReplaceEditor->setEnabled(can_replace); + getChild<LLButton>("replace_btn")->setEnabled(can_replace); + getChild<LLButton>("replace_all_btn")->setEnabled(can_replace); +} // ============================================================================ diff --git a/indra/newview/llfloatersearchreplace.h b/indra/newview/llfloatersearchreplace.h index 4a53047b26522aa4d238413352829cebdb5337d1..2f887a5d56447023671f9d97efa1ea9fd941d32f 100644 --- a/indra/newview/llfloatersearchreplace.h +++ b/indra/newview/llfloatersearchreplace.h @@ -46,14 +46,17 @@ class LLFloaterSearchReplace : public LLFloater /*virtual*/ BOOL postBuild(); /*virtual*/ void onOpen(const LLSD& sdKey); /*virtual*/ void onClose(bool fQuiting); + void setCanReplace(bool can_replace); /* * Member functions */ public: - static void show(LLTextEditor* pEditor); -protected: + static LLFloaterSearchReplace* show(LLTextEditor* pEditor); + static LLFloaterSearchReplace* findInstance(); LLTextEditor* getEditor() const; + +protected: void refreshHighlight(); void onSearchClick(); void onSearchKeystroke(); diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 37d94ef3503b677b9df1ac3f55a5614da7fe1c2e..6010fade6050ccc9843d6088ff98ec1f0c33e707 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -94,6 +94,10 @@ #include "rlvhandler.h" #include "rlvlocks.h" // [/RLVa:KB] +// NaCl - LSL Preprocessor +#include "fslslpreproc.h" +#include "fslslpreprocviewer.h" +// NaCl End const std::string HELLO_LSL = "default\n" @@ -346,7 +350,7 @@ LLScriptEdCore::LLScriptEdCore( const std::string& sample, const LLHandle<LLFloater>& floater_handle, void (*load_callback)(void*), - void (*save_callback)(void*, BOOL), + void (*save_callback)(void*, BOOL, bool), // void (*search_replace_callback) (void* userdata), void* userdata, bool live, @@ -367,8 +371,12 @@ LLScriptEdCore::LLScriptEdCore( mLiveHelpHistorySize(0), mEnableSave(FALSE), mLiveFile(NULL), + mLSLPreprocEnabled(LLCachedControl<bool>(gSavedSettings,"AlchemyLSLPreprocessor", FALSE)), mLive(live), mContainer(container), + mPostEditor(NULL), + mCurrentEditor(NULL), + mPreprocTab(NULL), mHasScriptData(FALSE), mScriptRemoved(FALSE), mSaveDialogShown(FALSE) @@ -376,7 +384,15 @@ LLScriptEdCore::LLScriptEdCore( setFollowsAll(); setBorderVisible(FALSE); - setXMLFilename("panel_script_ed.xml"); + if (gSavedSettings.getBOOL("AlchemyLSLPreprocessor")) + { + setXMLFilename("panel_script_ed_preproc.xml"); + mLSLProc = std::make_unique<FSLSLPreprocessor>(this); + } + else + { + setXMLFilename("panel_script_ed.xml"); + } llassert_always(mContainer != NULL); } @@ -397,6 +413,10 @@ LLScriptEdCore::~LLScriptEdCore() { mSyntaxIDConnection.disconnect(); } + if (mTogglePreprocConnection.connected()) + { + mTogglePreprocConnection.disconnect(); + } } void LLLiveLSLEditor::experienceChanged() @@ -450,6 +470,7 @@ void LLLiveLSLEditor::onToggleExperience( LLUICtrl *ui, void* userdata ) BOOL LLScriptEdCore::postBuild() { + mLineCol=getChild<LLTextBox>("line_col"); // [SL:KB] - Patch: Build-ScriptEditor | Checked: 2014-01-29 (Catznip-3.6) mMenuBar = getChild<LLMenuBarGL>("script_menu"); // [/SL:KB] @@ -462,8 +483,24 @@ BOOL LLScriptEdCore::postBuild() mEditor = getChild<LLScriptEditor>("Script Editor"); + mCurrentEditor = mEditor; + if (mLSLPreprocEnabled) + { + mPostEditor = getChild<FSLSLPreProcViewer>("Post Editor"); + mPostEditor->setFollowsAll(); + mPostEditor->setEnabled(TRUE); + + mPreprocTab = getChild<LLTabContainer>("Tabset"); + mPreprocTab->setCommitCallback(boost::bind(&LLScriptEdCore::onPreprocTabChanged, this, _2)); + } + + mTogglePreprocConnection = gSavedSettings.getControl("AlchemyLSLPreprocessor")->getSignal()->connect([&](LLControlVariable* control, const LLSD&, const LLSD&){ + mErrorList->setCommentText(LLTrans::getString("preproc_toggle_warning")); + mErrorList->deleteAllItems(); // Make it visible + }); + childSetCommitCallback("lsl errors", &LLScriptEdCore::onErrorList, this); - childSetAction("Save_btn", boost::bind(&LLScriptEdCore::doSave,this,FALSE)); + childSetAction("Save_btn", boost::bind(&LLScriptEdCore::doSave,this,FALSE, true)); childSetAction("Edit_btn", boost::bind(&LLScriptEdCore::openInExternalEditor, this)); initMenu(); @@ -480,6 +517,7 @@ BOOL LLScriptEdCore::postBuild() void LLScriptEdCore::processKeywords() { LL_DEBUGS("SyntaxLSL") << "Processing keywords" << LL_ENDL; + mFunctions->clearRows(); mEditor->clearSegments(); mEditor->initKeywords(); mEditor->loadKeywords(); @@ -510,6 +548,13 @@ void LLScriptEdCore::processKeywords() { mFunctions->add(*iter); } + + if (mLSLPreprocEnabled && mPostEditor) + { + mPostEditor->clearSegments(); + mPostEditor->initKeywords(); + mPostEditor->loadKeywords(); + } } void LLScriptEdCore::initMenu() @@ -518,50 +563,52 @@ void LLScriptEdCore::initMenu() LLMenuItemCallGL* menuItem; menuItem = getChild<LLMenuItemCallGL>("Save"); - menuItem->setClickCallback(boost::bind(&LLScriptEdCore::doSave, this, FALSE)); + menuItem->setClickCallback(boost::bind(&LLScriptEdCore::doSave, this, FALSE, true)); menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::hasChanged, this)); menuItem = getChild<LLMenuItemCallGL>("Revert All Changes"); - menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnUndoChanges, this)); - menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::hasChanged, this)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ onBtnUndoChanges(); }); + menuItem->setEnableCallback([&](LLUICtrl*, const LLSD&){ return (mCurrentEditor == mEditor && hasChanged()); }); menuItem = getChild<LLMenuItemCallGL>("Undo"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::undo, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canUndo, mEditor)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ mCurrentEditor->undo(); }); + menuItem->setEnableCallback([&](LLUICtrl*, const LLSD&){ return mCurrentEditor->canUndo(); }); menuItem = getChild<LLMenuItemCallGL>("Redo"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::redo, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canRedo, mEditor)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ mCurrentEditor->redo(); }); + menuItem->setEnableCallback([&](LLUICtrl*, const LLSD&){ return mCurrentEditor->canRedo(); }); menuItem = getChild<LLMenuItemCallGL>("Cut"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::cut, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canCut, mEditor)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ mCurrentEditor->cut(); }); + menuItem->setEnableCallback([&](LLUICtrl*, const LLSD&){ return mCurrentEditor->canCut(); }); menuItem = getChild<LLMenuItemCallGL>("Copy"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::copy, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canCopy, mEditor)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ mCurrentEditor->copy(); }); + menuItem->setEnableCallback([&](LLUICtrl*, const LLSD&){ return mCurrentEditor->canCopy(); }); menuItem = getChild<LLMenuItemCallGL>("Paste"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::paste, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canPaste, mEditor)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ mCurrentEditor->paste(); }); + menuItem->setEnableCallback([&](LLUICtrl*, const LLSD&){ return mCurrentEditor->canPaste(); }); // [SL:KB] - Patch: Build-ScriptEditor | Checked: 2014-01-29 (Catznip-3.6) menuItem = getChild<LLMenuItemCallGL>("Delete"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::doDelete, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canDoDelete, mEditor)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ mCurrentEditor->doDelete(); }); + menuItem->setEnableCallback([&](LLUICtrl*, const LLSD&){ return mCurrentEditor->canUndo(); }); // [/SL:KB] menuItem = getChild<LLMenuItemCallGL>("Select All"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::selectAll, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canSelectAll, mEditor)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ mCurrentEditor->selectAll(); }); + menuItem->setEnableCallback([&](LLUICtrl*, const LLSD&){ return mCurrentEditor->canSelectAll(); }); menuItem = getChild<LLMenuItemCallGL>("Deselect"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::deselect, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canDeselect, mEditor)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ mCurrentEditor->deselect(); }); + menuItem->setEnableCallback([&](LLUICtrl*, const LLSD&){ return mCurrentEditor->canDeselect(); }); menuItem = getChild<LLMenuItemCallGL>("Search / Replace..."); // [SL:KB] - Patch: UI-FloaterSearchReplace | Checked: 2010-10-26 (Catznip-2.3) - menuItem->setClickCallback(boost::bind(&LLFloaterSearchReplace::show, mEditor)); + menuItem->setClickCallback([&](LLUICtrl*, const LLSD&){ + LLFloaterSearchReplace* floater = LLFloaterSearchReplace::show(mCurrentEditor); + floater->setCanReplace(mCurrentEditor == mEditor); }); // [/SL:KB] // menuItem->setClickCallback(boost::bind(&LLFloaterScriptSearch::show, this)); @@ -580,15 +627,57 @@ void LLScriptEdCore::initMenu() menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::enableSaveToFileMenu, this)); } + +// NaCl - LSL Preprocessor +void LLScriptEdCore::onPreprocTabChanged(const std::string& tab_name) +{ + mCurrentEditor = (tab_name == "Preprocessed" ? mPostEditor : mEditor); + LLFloaterSearchReplace* search_floater = LLFloaterSearchReplace::findInstance(); + if (search_floater && (search_floater->getEditor() == mEditor || search_floater->getEditor() == mPostEditor)) + { + search_floater->setCanReplace(mCurrentEditor == mEditor); + } + childSetEnabled("Insert...", mCurrentEditor == mEditor); +} +// NaCl End + void LLScriptEdCore::setScriptText(const std::string& text, BOOL is_valid) { if (mEditor) { - mEditor->setText(text); + // NaCl - LSL Preprocessor + std::string ntext = text; + if (mLSLPreprocEnabled && mLSLProc) + { + if (mPostEditor) + { + mPostEditor->setText(ntext); + } + ntext = mLSLProc->decode(ntext); + } + LLStringUtil::replaceTabsWithSpaces(ntext, mEditor->spacesPerTab()); + // NaCl End + mEditor->setText(ntext); mHasScriptData = is_valid; } } +// NaCl - LSL Preprocessor +std::string LLScriptEdCore::getScriptText() +{ + if (mLSLPreprocEnabled && mPostEditor) + { + //return mPostEditor->getText(); + return mPostScript; + } + else if (mEditor) + { + return mEditor->getText(); + } + return std::string(); +} +// NaCl End + void LLScriptEdCore::makeEditorPristine() { if (mEditor) @@ -637,23 +726,10 @@ bool LLScriptEdCore::loadScriptText(const std::string& filename) // return true; } -bool LLScriptEdCore::writeToFile(const std::string& filename) +bool LLScriptEdCore::writeToFile(const std::string& filename, bool unprocessed) { // [SL:KB] - Patch: Build-AssetRecovery | Checked: 2013-07-28 (Catznip-3.6) - if (!mEditor->writeToFile(filename)) - { - LL_WARNS() << "Unable to write to " << filename << LL_ENDL; - - LLSD row; - row["columns"][0]["value"] = "Error writing to local file. Is your hard drive full?"; - row["columns"][0]["font"] = "SANSSERIF_SMALL"; - mErrorList->addElement(row); - return false; - } - return true; -// [/SL:KB] -// LLFILE* fp = LLFile::fopen(filename, "wb"); -// if (!fp) +// if (!mEditor->writeToFile(filename)) // { // LL_WARNS() << "Unable to write to " << filename << LL_ENDL; // @@ -663,18 +739,37 @@ bool LLScriptEdCore::writeToFile(const std::string& filename) // mErrorList->addElement(row); // return false; // } -// -// std::string utf8text = mEditor->getText(); -// -// // Special case for a completely empty script - stuff in one space so it can store properly. See SL-46889 -// if (utf8text.size() == 0) -// { -// utf8text = " "; -// } -// -// fputs(utf8text.c_str(), fp); -// fclose(fp); // return true; +// [/SL:KB] + LLFILE* fp = LLFile::fopen(filename, "wb"); + if (!fp) + { + LL_WARNS() << "Unable to write to " << filename << LL_ENDL; + + LLSD row; + row["columns"][0]["value"] = "Error writing to local file. Is your hard drive full?"; + row["columns"][0]["font"] = "SANSSERIF_SMALL"; + mErrorList->addElement(row); + return false; + } + + // NaCl - LSL Preprocessor + std::string utf8text; + if(!unprocessed) + utf8text = getScriptText(); + else + utf8text = mEditor->getText(); + // NaCl End + + // Special case for a completely empty script - stuff in one space so it can store properly. See SL-46889 + if (utf8text.size() == 0) + { + utf8text = " "; + } + + fputs(utf8text.c_str(), fp); + fclose(fp); + return true; } void LLScriptEdCore::sync() @@ -687,7 +782,7 @@ void LLScriptEdCore::sync() if (LLFile::stat(tmp_file, &s) == 0) // file exists { mLiveFile->ignoreNextUpdate(); - writeToFile(tmp_file); + writeToFile(tmp_file, mLSLPreprocEnabled); } } } @@ -714,11 +809,23 @@ void LLScriptEdCore::draw() args["[LINE]"] = llformat ("%d", line); args["[COLUMN]"] = llformat ("%d", column); cursor_pos = LLTrans::getString("CursorPos", args); - getChild<LLUICtrl>("line_col")->setValue(cursor_pos); + mLineCol->setValue(cursor_pos); + } + else if (mPostEditor && mPostEditor->hasFocus()) + { + S32 line = 0; + S32 column = 0; + mPostEditor->getCurrentLineAndColumn( &line, &column, FALSE ); // don't include wordwrap + LLStringUtil::format_map_t args; + std::string cursor_pos; + args["[LINE]"] = llformat ("%d", line); + args["[COLUMN]"] = llformat ("%d", column); + cursor_pos = LLTrans::getString("CursorPos", args); + mLineCol->setValue(cursor_pos); } else { - getChild<LLUICtrl>("line_col")->setValue(LLStringUtil::null); + mLineCol->setValue(LLStringUtil::null); } updateDynamicHelp(); @@ -1059,44 +1166,37 @@ void LLScriptEdCore::onBtnInsertFunction(LLUICtrl *ui, void* userdata) self->setHelpPage(self->mFunctions->getSimple()); } -void LLScriptEdCore::doSave( BOOL close_after_save ) +void LLScriptEdCore::doSave(BOOL close_after_save, bool sync /*= true*/) +{ + mErrorList->deleteAllItems(); + mErrorList->setCommentText(std::string()); + + if (mLSLPreprocEnabled && mLSLProc) + { + LL_INFOS() << "passing to preproc" << LL_ENDL; + mLSLProc->preprocess_script(close_after_save, sync); + } + else + { + if( mSaveCallback ) + { + mSaveCallback( mUserdata, close_after_save, sync ); + } + } +} + +void LLScriptEdCore::doSaveComplete( void* userdata, BOOL close_after_save, bool sync) { add(LLStatViewer::LSL_SAVES, 1); if( mSaveCallback ) { - mSaveCallback( mUserdata, close_after_save ); + mSaveCallback( mUserdata, close_after_save, sync ); } } void LLScriptEdCore::openInExternalEditor() { - delete mLiveFile; // deletes file - - // Generate a suitable filename - std::string script_name = mScriptName; - std::string forbidden_chars = "<>:\"\\/|?*"; - for (std::string::iterator c = forbidden_chars.begin(); c != forbidden_chars.end(); c++) - { - script_name.erase(std::remove(script_name.begin(), script_name.end(), *c), script_name.end()); - } - std::string filename = mContainer->getTmpFileName(script_name); - - // Save the script to a temporary file. - if (!writeToFile(filename)) - { - // In case some characters from script name are forbidden - // and not accounted for, name is too long or some other issue, - // try file that doesn't include script name - script_name.clear(); - filename = mContainer->getTmpFileName(script_name); - writeToFile(filename); - } - - // Start watching file changes. - mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLScriptEdContainer::onExternalChange, mContainer, _1)); - mLiveFile->addToEventTimer(); - // Open it in external editor. { LLExternalEditor ed; @@ -1119,6 +1219,35 @@ void LLScriptEdCore::openInExternalEditor() return; } + // Script contents clobbered when Edit button + // clicked with preprocessor active. Fix from NaCl (code moved + // from above). + delete mLiveFile; // deletes file + + // Generate a suitable filename + std::string script_name = mScriptName; + std::string forbidden_chars = "<>:\"\\/|?*"; + for (std::string::iterator c = forbidden_chars.begin(); c != forbidden_chars.end(); c++) + { + script_name.erase(std::remove(script_name.begin(), script_name.end(), *c), script_name.end()); + } + std::string filename = mContainer->getTmpFileName(script_name); + + // Save the script to a temporary file. + if (!writeToFile(filename, mLSLPreprocEnabled)) + { + // In case some characters from script name are forbidden + // and not accounted for, name is too long or some other issue, + // try file that doesn't include script name + script_name.clear(); + filename = mContainer->getTmpFileName(script_name); + writeToFile(filename, mLSLPreprocEnabled); + } + + // Start watching file changes. + mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLScriptEdContainer::onExternalChange, mContainer, _1)); + mLiveFile->addToEventTimer(); + status = ed.run(filename); if (status != LLExternalEditor::EC_SUCCESS) { @@ -1154,11 +1283,24 @@ void LLScriptEdCore::onErrorList(LLUICtrl*, void* user_data) sscanf(line.c_str(), "%d %d", &row, &column); //LL_INFOS() << "LLScriptEdCore::onErrorList() - " << row << ", " //<< column << LL_ENDL; + if (gSavedSettings.getBOOL("AlchemyLSLPreprocessor") && self->mPostEditor && self->mPreprocTab) + { + self->mPreprocTab->selectTabByName("Preprocessed"); + self->getChild<LLPanel>("Preprocessed")->setFocus(TRUE); + self->mPostEditor->setFocus(TRUE); +// [SL:KB] - Patch: UI-ScriptGoToLine | Checked: 2013-12-31 (Catznip-3.6) + self->mPostEditor->scrollTo(row, column); +// [/SL:KB] +// self->mPostEditor->setCursor(row, column); + } + else + { // [SL:KB] - Patch: UI-ScriptGoToLine | Checked: 2013-12-31 (Catznip-3.6) - self->mEditor->scrollTo(row, column); + self->mEditor->scrollTo(row, column); // [/SL:KB] -// self->mEditor->setCursor(row, column); - self->mEditor->setFocus(TRUE); +// self->mEditor->setCursor(row, column); + self->mEditor->setFocus(TRUE); + } } } @@ -1217,6 +1359,27 @@ void LLScriptEdCore::deleteBridges() // virtual BOOL LLScriptEdCore::handleKeyHere(KEY key, MASK mask) { + bool just_control = MASK_CONTROL == (mask & MASK_MODIFIERS); + + if(('S' == key) && just_control) + { + if(mSaveCallback) + { + // don't close after saving + // NaCl - LSL Preprocessor + if (!hasChanged()) + { + LL_INFOS("Scriptsave") << "Save Not Needed" << LL_ENDL; + return TRUE; + } + doSave(FALSE); + // NaCl End + + //mSaveCallback(mUserdata, FALSE); + } + + return TRUE; + } // [SL:KB] - Patch: Build-ScriptEditor | Checked: 2014-01-29 (Catznip-3.6) if (mMenuBar->handleAcceleratorKey(key, mask)) { @@ -1302,12 +1465,12 @@ void LLScriptEdCore::saveScriptToFile(const std::vector<std::string>& filenames, LLScriptEdCore* self = (LLScriptEdCore*)data; if (self) { - std::string filename = filenames[0]; - std::string scriptText = self->mEditor->getText(); + const std::string& filename = filenames[0]; + const std::string& scriptText = self->mEditor->getText(); llofstream fout(filename.c_str()); fout << (scriptText); fout.close(); - self->mSaveCallback(self->mUserdata, FALSE); + self->mSaveCallback(self->mUserdata, FALSE, true); } } @@ -1498,7 +1661,7 @@ void LLScriptEdContainer::onBackupTimer() { if (mBackupFilename.empty()) mBackupFilename = getBackupFileName(); - mScriptEd->writeToFile(mBackupFilename); + mScriptEd->writeToFile(mBackupFilename, true); } } // [/SL:KB] @@ -1533,7 +1696,13 @@ bool LLScriptEdContainer::onExternalChange(const std::string& filename) } // Disable sync to avoid recursive load->save->load calls. - saveIfNeeded(false); + // LSL preprocessor + // Don't call saveIfNeeded directly, as we might have to run the + // preprocessor first. saveIfNeeded will be invoked via callback. Make sure + // to pass sync = false - we don't need to update the external editor in this + // case or the next save will be ignored! + //saveIfNeeded(false); + mScriptEd->doSave(FALSE, false); return true; } @@ -1611,8 +1780,8 @@ void LLPreviewLSL::draw() void LLPreviewLSL::callbackLSLCompileSucceeded() { LL_INFOS() << "LSL Bytecode saved" << LL_ENDL; - mScriptEd->mErrorList->setCommentText(LLTrans::getString("CompileSuccessful")); - mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete")); + mScriptEd->mErrorList->addCommentText(LLTrans::getString("CompileSuccessful")); + mScriptEd->mErrorList->addCommentText(LLTrans::getString("SaveComplete")); // [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2) // Script was successfully saved so delete our backup copy if we have one and the editor is still pristine @@ -1743,11 +1912,11 @@ void LLPreviewLSL::onLoad(void* userdata) } // static -void LLPreviewLSL::onSave(void* userdata, BOOL close_after_save) +void LLPreviewLSL::onSave(void* userdata, BOOL close_after_save, bool sync) { LLPreviewLSL* self = (LLPreviewLSL*)userdata; self->mCloseAfterSave = close_after_save; - self->saveIfNeeded(); + self->saveIfNeeded(sync); } /*static*/ @@ -1783,7 +1952,6 @@ void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/) } mPendingUploads = 0; - mScriptEd->mErrorList->deleteAllItems(); mScriptEd->mEditor->makePristine(); if (sync) @@ -1795,21 +1963,45 @@ void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/) const LLInventoryItem *inv_item = getItem(); // save it out to asset server std::string url = gAgent.getRegion()->getCapability("UpdateScriptAgent"); + + // NaCL - LSL Preprocessor + mScriptEd->enableSave(FALSE); // Clear the enable save flag (FIRE-10173) + bool inventory_mono = gSavedSettings.getBOOL("AlchemyInventoryScriptsMono"); + if (gSavedSettings.getBOOL("AlchemyLSLPreprocessor")) + { + bool mono_directive = FSLSLPreprocessor::mono_directive(mScriptEd->getScriptText(), inventory_mono); + if (mono_directive != inventory_mono) + { + std::string message; + if (mono_directive) + { + message = LLTrans::getString("fs_preprocessor_mono_directive_override"); + } + else + { + message = LLTrans::getString("fs_preprocessor_lsl2_directive_override"); + } + inventory_mono = mono_directive; + mScriptEd->mErrorList->addCommentText(message); + } + } + // NaCl End + if(inv_item) { getWindow()->incBusyCount(); mPendingUploads++; if (!url.empty()) { - std::string buffer(mScriptEd->mEditor->getText()); + std::string buffer(mScriptEd->getScriptText()); LLUUID old_asset_id = inv_item->getAssetUUID().isNull() ? mScriptEd->getAssetID() : inv_item->getAssetUUID(); - LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLScriptAssetUpload>(mItemUUID, buffer, [old_asset_id](LLUUID itemId, LLUUID, LLUUID, LLSD response) { LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); LLPreviewLSL::finishedLSLUpload(itemId, response); - })); + }, + inventory_mono ? LLScriptAssetUpload::MONO : LLScriptAssetUpload::LSL2)); LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); } @@ -1980,10 +2172,8 @@ void LLLiveLSLEditor::callbackLSLCompileSucceeded(const LLUUID& task_id, bool is_script_running) { LL_DEBUGS() << "LSL Bytecode saved" << LL_ENDL; - mScriptEd->mErrorList->setCommentText(LLTrans::getString("CompileSuccessful")); - mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete")); - getChild<LLCheckBoxCtrl>("running")->set(is_script_running); - mIsSaving = FALSE; + mScriptEd->mErrorList->addCommentText(LLTrans::getString("CompileSuccessful")); + mScriptEd->mErrorList->addCommentText(LLTrans::getString("SaveComplete")); // [SL:KB] - Patch: Build-ScriptRecover | Checked: 2011-11-23 (Catznip-3.2) // Script was successfully saved so delete our backup copy if we have one and the editor is still pristine @@ -1993,6 +2183,8 @@ void LLLiveLSLEditor::callbackLSLCompileSucceeded(const LLUUID& task_id, } // [/SL:KB] + getChild<LLCheckBoxCtrl>("running")->set(is_script_running); + mIsSaving = FALSE; closeIfNeeded(); } @@ -2023,6 +2215,7 @@ void LLLiveLSLEditor::callbackLSLCompileFailed(const LLSD& compile_errors) } // [/SL:KB] + mIsSaving = FALSE; closeIfNeeded(); } @@ -2151,6 +2344,14 @@ void LLLiveLSLEditor::onLoadComplete(const LLUUID& asset_id, { if( LL_ERR_NOERR == status ) { + if( !instance->getItem() ) + { + LL_WARNS() << "getItem() returns 0, item went away while loading script()" << LL_ENDL; + instance->mAssetStatus = PREVIEW_ASSET_ERROR; + delete floater_key; + return; + } + instance->loadScriptText(asset_id, type); instance->mScriptEd->setEnableEditing(TRUE); instance->mAssetStatus = PREVIEW_ASSET_LOADED; @@ -2433,14 +2634,13 @@ void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) // save the script mScriptEd->enableSave(FALSE); mScriptEd->mEditor->makePristine(); - mScriptEd->mErrorList->deleteAllItems(); - mScriptEd->mEditor->makePristine(); if (sync) { mScriptEd->sync(); } bool isRunning = getChild<LLCheckBoxCtrl>("running")->get(); + mIsSaving = TRUE; getWindow()->incBusyCount(); mPendingUploads++; @@ -2448,7 +2648,7 @@ void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) if (!url.empty()) { - std::string buffer(mScriptEd->mEditor->getText()); + std::string buffer(mScriptEd->getScriptText()); LLUUID old_asset_id = mScriptEd->getAssetID(); LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLScriptAssetUpload>(mObjectUUID, mItemUUID, @@ -2486,14 +2686,13 @@ void LLLiveLSLEditor::onLoad(void* userdata) } // static -void LLLiveLSLEditor::onSave(void* userdata, BOOL close_after_save) +void LLLiveLSLEditor::onSave(void* userdata, BOOL close_after_save, bool sync) { LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; if(self) { self->mCloseAfterSave = close_after_save; - self->mScriptEd->mErrorList->setCommentText(""); - self->saveIfNeeded(); + self->saveIfNeeded(sync); } } diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 20dbb1c0b515421941f6e976a47653fb17bb1ce6..e9757a45e9da997a724b813a9a69bd343d8dc3af 100644 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -52,6 +52,8 @@ class LLViewerInventoryItem; class LLScriptEdContainer; class LLFloaterGotoLine; class LLFloaterExperienceProfile; +class FSLSLPreprocessor; +class FSLSLPreProcViewer; class LLLiveLSLFile final : public LLLiveFile { @@ -79,6 +81,9 @@ class LLScriptEdCore final : public LLPanel // friend class LLFloaterScriptSearch; friend class LLScriptEdContainer; friend class LLFloaterGotoLine; + // NaCl - LSL Preprocessor + friend class FSLSLPreprocessor; + // NaCl End protected: // Supposed to be invoked only by the container. @@ -87,7 +92,7 @@ class LLScriptEdCore final : public LLPanel const std::string& sample, const LLHandle<LLFloater>& floater_handle, void (*load_callback)(void* userdata), - void (*save_callback)(void* userdata, BOOL close_after_save), + void (*save_callback)(void* userdata, BOOL close_after_save, bool sync), // void (*search_replace_callback)(void* userdata), void* userdata, bool live, @@ -107,12 +112,16 @@ class LLScriptEdCore final : public LLPanel bool canLoadOrSaveToFile( void* userdata ); void setScriptText(const std::string& text, BOOL is_valid); + // NaCL - LSL Preprocessor + std::string getScriptText(); + void doSaveComplete(void* userdata, BOOL close_after_save, bool sync); + // NaCl End void makeEditorPristine(); bool loadScriptText(const std::string& filename); - bool writeToFile(const std::string& filename); + bool writeToFile(const std::string& filename, bool unprocessed); void sync(); - void doSave( BOOL close_after_save ); + void doSave(BOOL close_after_save, bool sync = true); bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response); bool handleReloadFromServerDialog(const LLSD& notification, const LLSD& response); @@ -146,6 +155,11 @@ class LLScriptEdCore final : public LLPanel LLUUID getAssetID() { return mAssetID; } private: + // NaCl - LSL Preprocessor + LLCachedControl<bool> mLSLPreprocEnabled; + boost::signals2::connection mTogglePreprocConnection; + void onPreprocTabChanged(const std::string& tab_name); + // NaCl End void onBtnDynamicHelp(); void onBtnUndoChanges(); @@ -175,7 +189,7 @@ class LLScriptEdCore final : public LLPanel // [/SL:KB] LLScriptEditor* mEditor; void (*mLoadCallback)(void* userdata); - void (*mSaveCallback)(void* userdata, BOOL close_after_save); + void (*mSaveCallback)(void* userdata, BOOL close_after_save, bool sync); // void (*mSearchReplaceCallback) (void* userdata); void* mUserdata; LLComboBox *mFunctions; @@ -195,6 +209,15 @@ class LLScriptEdCore final : public LLPanel BOOL mSaveDialogShown; LLUUID mAssetID; + LLTextBox* mLineCol; + // NaCl - LSL Preprocessor + std::unique_ptr<FSLSLPreprocessor> mLSLProc; + FSLSLPreProcViewer* mPostEditor; + LLScriptEditor* mCurrentEditor; + LLTabContainer* mPreprocTab; + std::string mPostScript; + // NaCl End + LLScriptEdContainer* mContainer; // parent view public: @@ -246,7 +269,7 @@ class LLPreviewLSL final : public LLScriptEdContainer // static void onSearchReplace(void* userdata); static void onLoad(void* userdata); - static void onSave(void* userdata, BOOL close_after_save); + static void onSave(void* userdata, BOOL close_after_save, bool sync); static void onLoadComplete(const LLUUID& uuid, LLAssetType::EType type, @@ -311,7 +334,7 @@ class LLLiveLSLEditor final : public LLScriptEdContainer // static void onSearchReplace(void* userdata); static void onLoad(void* userdata); - static void onSave(void* userdata, BOOL close_after_save); + static void onSave(void* userdata, BOOL close_after_save, bool sync); static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 288125331a3767da364924052bbead4fda626dd6..f6c11d773164a7df67d643b96b7f8ee761666fde 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -749,10 +749,10 @@ LLUUID LLBufferedAssetUploadInfo::finishUpload(LLSD &result) //========================================================================= -LLScriptAssetUpload::LLScriptAssetUpload(LLUUID itemId, std::string buffer, invnUploadFinish_f finish): +LLScriptAssetUpload::LLScriptAssetUpload(LLUUID itemId, std::string buffer, invnUploadFinish_f finish, TargetType_t targetType): LLBufferedAssetUploadInfo(itemId, LLAssetType::AT_LSL_TEXT, buffer, finish), mExerienceId(), - mTargetType(LSL2), + mTargetType(targetType), mIsRunning(false) { } @@ -773,7 +773,7 @@ LLSD LLScriptAssetUpload::generatePostBody() if (getTaskId().isNull()) { body["item_id"] = getItemId(); - body["target"] = "lsl2"; + body["target"] = (getTargetType() == MONO) ? "mono" : "lsl2"; } else { diff --git a/indra/newview/llviewerassetupload.h b/indra/newview/llviewerassetupload.h index e027fa640bafd7a5fd91c76641d3f1eec3d759a5..1278a1d1bb163e6af24aeea7881975fde568f0bb 100644 --- a/indra/newview/llviewerassetupload.h +++ b/indra/newview/llviewerassetupload.h @@ -219,7 +219,7 @@ class LLScriptAssetUpload : public LLBufferedAssetUploadInfo MONO }; - LLScriptAssetUpload(LLUUID itemId, std::string buffer, invnUploadFinish_f finish); + LLScriptAssetUpload(LLUUID itemId, std::string buffer, invnUploadFinish_f finish, TargetType_t targetType = MONO); LLScriptAssetUpload(LLUUID taskId, LLUUID itemId, TargetType_t targetType, bool isRunning, LLUUID exerienceId, std::string buffer, taskUploadFinish_f finish); diff --git a/indra/newview/skins/default/xui/en/floater_script_ed_prefs.xml b/indra/newview/skins/default/xui/en/floater_script_ed_prefs.xml index 8e4bcb3eb0da36a7da43f7f3b704a6d2d637dab9..769a944f92d6422cffe1398957d8fd79211f88c5 100644 --- a/indra/newview/skins/default/xui/en/floater_script_ed_prefs.xml +++ b/indra/newview/skins/default/xui/en/floater_script_ed_prefs.xml @@ -2,7 +2,7 @@ <floater legacy_header_height="18" can_resize="false" - height="354" + height="528" layout="topleft" name="floater_script_colors" help_topic="script_colors" @@ -324,13 +324,12 @@ <script_editor left="8" right="-8" - top="176" - bottom="-8" + top_pad="15" type="string" length="1" follows="left|top|right|bottom" font="Monospace" - height="100" + height="165" ignore_tab="false" layout="topleft" max_length="300" @@ -356,4 +355,105 @@ default } } </script_editor> + + <text + follows="left|top" + height="15" + layout="topleft" + left="12" + name="prefs_label" + top_pad="6" + width="150"> + Script Editor Options: + </text> + <check_box + control_name="AlchemyLSLPreprocessor" + follows="left|top" + height="16" + top_pad="2" + label="Enable LSL preprocessor" + tool_tip="When checked, the LSL preprocessor is enabled." + name="preproc_checkbox" /> + <check_box + control_name="AlchemyPreProcLSLOptimizer" + enabled_control="AlchemyLSLPreprocessor" + follows="left|top" + height="16" + top_pad="2" + left_delta="16" + label="Script optimizer" + tool_tip="When checked, the LSL preprocessor will optimize space used by scripts." + name="preprocoptimizer_checkbox" /> + <check_box + control_name="AlchemyPreProcLSLSwitch" + enabled_control="AlchemyLSLPreprocessor" + follows="left|top" + height="16" + top_pad="2" + label="switch() statement" + tool_tip="When checked, the LSL preprocessor will allow the use of the switch() statement for script flow control." + name="preprocswitch_checkbox" /> + <check_box + control_name="AlchemyPreProcLSLLazyLists" + enabled_control="AlchemyLSLPreprocessor" + follows="left|top" + height="16" + top_pad="2" + label="Lazy lists" + tool_tip="When checked, the LSL preprocessor will allow the use of syntax extensions for list handling." + name="preproclazy_checkbox" /> + <check_box + control_name="AlchemyPreProcEnableHDDInclude" + enabled_control="AlchemyLSLPreprocessor" + follows="left|top" + top_pad="2" + height="16" + label="#includes from local disk" + tool_tip="When checked, the LSL preprocessor will allow #include statements to reference files on your local disk." + name="preprocinclude_checkbox" /> + <text + follows="left|top" + height="16" + type="string" + length="1" + layout="topleft" + name="lslpreprocinclude_textbox" + top_pad="2" + width="256"> + Preprocessor include path: + </text> + <line_editor + control_name="AlchemyPreProcHDDIncludeLocation" + border_style="line" + border_thickness="1" + follows="left|top" + height="23" + enabled_control="AlchemyPreProcEnableHDDInclude" + max_length_chars="4096" + name="preprocinclude_location" + top_pad="0" + width="235" /> + <button + follows="left|top" + height="23" + enabled_control="AlchemyPreProcEnableHDDInclude" + label="..." + label_selected="Browse" + left_pad="5" + name="SetPreprocInclude" + top_delta="0" + width="30"> + <button.commit_callback + function="ScriptPref.SetPreprocInclude" /> + </button> + + <button + follows="top|left" + height="23" + layout="topleft" + top_pad="6" + left="115" + name="close_btn" + width="100" + label="OK" /> </floater> diff --git a/indra/newview/skins/default/xui/en/language_settings.xml b/indra/newview/skins/default/xui/en/language_settings.xml index cf3c5ccd45c861e7803b8c4e11de0b8a275055d0..4b7cb84044d5564952fd3afaebdd01d95af8bfc1 100644 --- a/indra/newview/skins/default/xui/en/language_settings.xml +++ b/indra/newview/skins/default/xui/en/language_settings.xml @@ -37,6 +37,7 @@ <string name="TimeHour">hour,datetime,slt</string> <string name="TimeMin">min,datetime,slt</string> + <string name="TimeSec">second,datetime,slt</string> <string name="TimeYear">year,datetime,slt</string> <string name="TimeDay">day,datetime,slt</string> <string name="TimeMonth">mthnum,datetime,slt</string> diff --git a/indra/newview/skins/default/xui/en/panel_script_ed.xml b/indra/newview/skins/default/xui/en/panel_script_ed.xml index a70ae1180f53f0b2a8431ccdb93a1256c9af7214..436834a34bc6ddbe587952ab9d92dd70bc8010a1 100644 --- a/indra/newview/skins/default/xui/en/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/en/panel_script_ed.xml @@ -70,7 +70,7 @@ <menu_item_separator layout="topleft" /> <menu_item_call - label="Colors..." + label="Script Preferences..." layout="topleft" name="Colors"> <menu_item_call.on_click @@ -129,6 +129,11 @@ layout="topleft" name="Select All" shortcut="control|A" /> + <menu_item_call + enabled="false" + label="Deselect" + layout="topleft" + name="Deselect" /> <menu_item_separator layout="topleft" name="separator3" /> diff --git a/indra/newview/skins/default/xui/en/panel_script_ed_preproc.xml b/indra/newview/skins/default/xui/en/panel_script_ed_preproc.xml new file mode 100644 index 0000000000000000000000000000000000000000..0306a4afa089a8dd713b93186d04973261d52ad0 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_script_ed_preproc.xml @@ -0,0 +1,295 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + bevel_style="none" + border_style="line" + follows="left|top|right|bottom" + height="522" + layout="topleft" + left="0" + name="script panel" + width="497"> + <panel.string + name="loading"> + Loading... + </panel.string> + <panel.string + name="can_not_view"> + You can not view or edit this script, since it has been set as "no copy". You need full permissions to view or edit a script inside an object. + </panel.string> + <panel.string + name="public_objects_can_not_run"> + Public Objects cannot run scripts + </panel.string> + <panel.string + name="script_running"> + Running + </panel.string> + <panel.string + name="Title"> + Script: [NAME] + </panel.string> + <menu_bar + bg_visible="false" + follows="left|top" + height="18" + layout="topleft" + left="0" + mouse_opaque="false" + name="script_menu" + width="476"> + <menu + top="0" + height="62" + label="File" + layout="topleft" + left="0" + mouse_opaque="false" + name="File" + width="138"> + <menu_item_call + label="Save" + layout="topleft" + name="Save" + shortcut="control|S" /> + <menu_item_separator + layout="topleft" /> + <menu_item_call + label="Revert All Changes" + layout="topleft" + name="Revert All Changes" /> + <menu_item_separator + layout="topleft" /> + <menu_item_call + label="Load from file..." + layout="topleft" + name="LoadFromFile" /> + <menu_item_call + label="Save to file..." + layout="topleft" + name="SaveToFile" /> + <menu_item_separator + layout="topleft" /> + <menu_item_call + label="Script Preferences..." + layout="topleft" + name="Colors"> + <menu_item_call.on_click + function="Floater.Toggle" + parameter="script_colors"/> + </menu_item_call> + </menu> + <menu + top="0" + height="198" + label="Edit" + layout="topleft" + mouse_opaque="false" + name="Edit" + width="139"> + <menu_item_call + enabled="false" + label="Undo" + layout="topleft" + name="Undo" + shortcut="control|Z" /> + <menu_item_call + enabled="false" + label="Redo" + layout="topleft" + name="Redo" + shortcut="control|Y" /> + <menu_item_separator + layout="topleft" /> + <menu_item_call + enabled="false" + label="Cut" + layout="topleft" + name="Cut" + shortcut="control|X" /> + <menu_item_call + enabled="false" + label="Copy" + layout="topleft" + name="Copy" + shortcut="control|C" /> + <menu_item_call + enabled="false" + label="Paste" + layout="topleft" + name="Paste" + shortcut="control|V" /> + <menu_item_call + enabled="false" + label="Delete" + layout="topleft" + name="Delete" + shortcut="Del" /> + <menu_item_call + label="Select All" + layout="topleft" + name="Select All" + shortcut="control|A" /> + <menu_item_call + enabled="false" + label="Deselect" + layout="topleft" + name="Deselect" /> + <menu_item_separator + layout="topleft" + name="separator3" /> + <menu_item_call + label="Search / Replace..." + layout="topleft" + name="Search / Replace..." + shortcut="control|F" /> + <menu_item_call + label="Go to line..." + layout="topleft" + name="Go to line..." + shortcut="control|G" /> + </menu> + <menu + top="0" + height="34" + label="LSL Reference" + layout="topleft" + mouse_opaque="false" + name="Help" + width="112"> + <menu_item_call + label="Keyword Help..." + layout="topleft" + name="Keyword Help..." + shortcut="shift|F1" /> + </menu> + </menu_bar> + <tab_container + follows="all" + halign="left" + height="378" + left="0" + top_pad="2" + name="Tabset" + tab_position="bottom" + tab_width="100" + tab_padding_right="0" + tab_height="16" + width="487"> + <panel + bevel_style="none" + border_style="line" + follows="left|top|right|bottom" + height="377" + layout="topleft" + left="0" + top="0" + name="Script" + label="Script" + width="487"> + <script_editor + left="0" + top="0" + type="string" + length="1" + follows="left|top|right|bottom" + font="Monospace" + height="376" + ignore_tab="false" + layout="topleft" + max_length="262144" + name="Script Editor" + text_color="ScriptText" + default_color="ScriptText" + bg_writeable_color="ScriptBackground" + bg_focus_color="ScriptBackground" + text_readonly_color="ScriptText" + bg_readonly_color="ScriptBackground" + bg_selected_color="ScriptSelectedColor" + cursor_color="ScriptCursorColor" + width="487" + enable_tooltip_paste="true" + word_wrap="true" + show_context_menu="true"> + Loading... + </script_editor> + </panel> + <panel + bevel_style="none" + border_style="line" + follows="left|top|right|bottom" + height="377" + layout="topleft" + left="0" + name="Preprocessed" + label="Preprocessed" + width="487"> + <lsl_preproc_viewer + left="0" + top="0" + type="string" + length="1" + follows="left|top|right|bottom" + font="Monospace" + height="376" + ignore_tab="false" + layout="topleft" + max_length="262144" + name="Post Editor" + text_color="ScriptText" + default_color="ScriptText" + bg_writeable_color="ScriptBackground" + bg_focus_color="ScriptBackground" + text_readonly_color="ScriptText" + bg_readonly_color="ScriptBackground" + bg_selected_color="ScriptSelectedColor" + cursor_color="ScriptCursorColor" + width="487" + word_wrap="true" + show_context_menu="true"> + Loading... + </lsl_preproc_viewer> + </panel> + </tab_container> + <scroll_list + top_pad="10" + left="0" + follows="left|right|bottom" + height="60" + layout="topleft" + name="lsl errors" + width="487" /> + <text + follows="left|bottom" + height="12" + layout="topleft" + left="0" + name="line_col" + width="128" /> + <combo_box + follows="left|bottom" + height="23" + label="Insert..." + layout="topleft" + name="Insert..." + width="128" /> + <button + follows="right|bottom" + height="23" + label="Save" + label_selected="Save" + layout="topleft" + top_pad="-35" + right="487" + name="Save_btn" + width="81" /> + <button + enabled="false" + follows="right|bottom" + height="23" + label="Edit..." + layout="topleft" + top_pad="-23" + right="400" + name="Edit_btn" + width="81" /> +</panel> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index af2711776210ef55728eb9f9332d91bd4b0788c2..ab30919ed8a751b9ef104f54c67650a1dad7cc58 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -4402,4 +4402,45 @@ and report the problem. <string name="NotAvailableOnPlatform">Not availalbe on this platform</string> <string name="GridInfoTitle">GRID INFO</string> + <!-- <FS:Cron> FIRE-9335 --> + <!-- <LSL Preprocessor --> + <string name="preproc_toggle_warning">Toggling the preprocessor will not take full effect until you close and reopen this editor.</string> + <string name="fs_preprocessor_starting">[APP_NAME] preprocessor starting...</string> + <string name="fs_preprocessor_not_supported">Warning: Preprocessor not supported in this build. ([WHERE])</string> + <string name="fs_preprocessor_disabled_by_script_marker">[APP_NAME] preprocessor disabled by directive at line [LINENUMBER].</string> + <string name="fs_preprocessor_settings_list_prefix">Settings:</string> + <string name="fs_preprocessor_cpp_exception">[ERR_NAME] ([LINENUMBER]): [ERR_DESC]</string> + <string name="fs_preprocessor_lexing_exception">[SEVERITY]: [ERR_NAME] ([LINENUMBER]): [ERR_DESC]</string> + <string name="fs_preprocessor_exception">[ERR_NAME] ([LINENUMBER]): exception caught: [ERR_DESC]</string> + <string name="fs_preprocessor_error">[ERR_NAME] ([LINENUMBER]): unexpected exception caught.</string> + <string name="fs_preprocessor_lsl2_directive_override">Detected compile-as-LSL2 directive overriding preference setting.</string> + <string name="fs_preprocessor_mono_directive_override">Detected compile-as-Mono directive overriding preference setting.</string> + <!-- LSL Optimizer --> + <string name="fs_preprocessor_optimizer_start">Optimizing out unreferenced user-defined functions and global variables.</string> + <string name="fs_preprocessor_optimizer_regex_err">Not a valid regular expression: '[WHAT]'; LSL optimization skipped.</string> + <string name="fs_preprocessor_optimizer_exception">Exception caught: '[WHAT]'; LSL optimization skipped.</string> + <string name="fs_preprocessor_optimizer_unexpected_exception">Unexpected exception in LSL optimizer; not applied.</string> + <!-- LSL Compressor --> + <string name="fs_preprocessor_compress_start">Compressing script text by removing unnecessary space.</string> + <string name="fs_preprocessor_compress_regex_err">Not a valid regular expression: '[WHAT]'; LSL compression skipped.</string> + <string name="fs_preprocessor_compress_exception">Exception caught: '[WHAT]'; LSL compression skipped.</string> + <string name="fs_preprocessor_compress_unexpected_exception">Unexpected exception in LSL compressor; not applied.</string> + <!-- LSL Lazy lists --> + <string name="fs_preprocessor_lazylist_start">Applying lazy list conversion.</string> + <string name="fs_preprocessor_lazylist_regex_err">Not a valid regular expression: '[WHAT]'; Lazy list converter skipped.</string> + <string name="fs_preprocessor_lazylist_exception">Exception caught: '[WHAT]'; Lazy list converter skipped.</string> + <string name="fs_preprocessor_lazylist_unexpected_exception">Unexpected exception in lazy list converter; not applied.</string> + <!-- LSL switch statement --> + <string name="fs_preprocessor_switchstatement_start">Applying switch statement conversion.</string> + <string name="fs_preprocessor_switchstatement_regex_err">Not a valid regular expression: '[WHAT]'; Switch statement converter skipped.</string> + <string name="fs_preprocessor_switchstatement_exception">Exception caught: '[WHAT]'; Switch statement converter skipped.</string> + <string name="fs_preprocessor_switchstatement_unexpected_exception">Unexpected exception in switch statement converter; not applied.</string> + <!-- LSL Cache --> + <string name="fs_preprocessor_cache_miss">Caching included file: '[FILENAME]'</string> + <string name="fs_preprocessor_cache_invalidated">Included file '[FILENAME]' has changed, recaching.</string> + <string name="fs_preprocessor_cache_completed">Caching completed for '[FILENAME]'</string> + <string name="fs_preprocessor_cache_unsafe">Error: script named '[FILENAME]' isn't safe to copy to the filesystem. This include will fail.</string> + <string name="fs_preprocessor_caching_err">Error caching included file '[FILENAME]'</string> + <string name="fs_preprocessor_truncated">Warning: Preprocessor output truncated due to excessive script text size. This script will most likely not work.</string> + <!-- </FS:Cron> --> </strings>