diff options
32 files changed, 2420 insertions, 2078 deletions
diff --git a/SConstruct b/SConstruct index 72de2e2004..365463ebad 100644 --- a/SConstruct +++ b/SConstruct @@ -61,7 +61,7 @@ platform_arg = ARGUMENTS.get("platform", ARGUMENTS.get("p", False)) if (os.name=="posix"): pass elif (os.name=="nt"): - if (not methods.msvc_is_detected() or platform_arg=="android"): + if ( os.getenv("VCINSTALLDIR")==None or platform_arg=="android"): custom_tools=['mingw'] env_base=Environment(tools=custom_tools); diff --git a/bin/tests/test_string.cpp b/bin/tests/test_string.cpp index 2e8f5c3494..4990c58896 100644 --- a/bin/tests/test_string.cpp +++ b/bin/tests/test_string.cpp @@ -31,7 +31,6 @@ //#include "math_funcs.h" #include <stdio.h> #include "os/os.h" -#include "drivers/nrex/regex.h" #include "core/io/ip_address.h" #include "test_string.h" @@ -462,18 +461,8 @@ bool test_25() { bool test_26() { - OS::get_singleton()->print("\n\nTest 26: RegEx\n"); - RegEx regexp("(.*):(.*)"); - - int res = regexp.find("name:password"); - printf("\tmatch: %s\n", (res>=0)?"true":"false"); - - printf("\t%i captures:\n", regexp.get_capture_count()); - for (int i = 0; i<regexp.get_capture_count(); i++) - { - printf("%ls\n", regexp.get_capture(i).c_str()); - } - return (res>=0); + //TODO: Do replacement RegEx test + return true; }; struct test_27_data { diff --git a/core/math/math_funcs.cpp b/core/math/math_funcs.cpp index 64615fe6b4..46c0218707 100644 --- a/core/math/math_funcs.cpp +++ b/core/math/math_funcs.cpp @@ -41,37 +41,16 @@ static uint32_t Q[4096]; #endif uint32_t Math::rand_from_seed(uint32_t *seed) { - -#if 1 - uint32_t k; - uint32_t s = (*seed); - if (s == 0) - s = 0x12345987; - k = s / 127773; - s = 16807 * (s - k * 127773) - 2836 * k; -// if (s < 0) -// s += 2147483647; - (*seed) = s; - return (s & Math::RANDOM_MAX); -#else - *seed = *seed * 1103515245 + 12345; - return (*seed % ((unsigned int)RANDOM_MAX + 1)); -#endif + // Xorshift31 PRNG + if ( *seed == 0 ) *seed = Math::RANDOM_MAX; + (*seed) ^= (*seed) << 13; + (*seed) ^= (*seed) >> 17; + (*seed) ^= (*seed) << 5; + return (*seed) & Math::RANDOM_MAX; } void Math::seed(uint32_t x) { -#if 0 - int i; - - Q[0] = x; - Q[1] = x + PHI; - Q[2] = x + PHI + PHI; - - for (i = 3; i < 4096; i++) - Q[i] = Q[i - 3] ^ Q[i - 2] ^ PHI ^ i; -#else default_seed=x; -#endif } void Math::randomize() { @@ -82,12 +61,12 @@ void Math::randomize() { uint32_t Math::rand() { - return rand_from_seed(&default_seed)&0x7FFFFFFF; + return rand_from_seed(&default_seed); } double Math::randf() { - return (double)rand() / (double)RANDOM_MAX; + return (double)rand() / (double)Math::RANDOM_MAX; } double Math::sin(double p_x) { diff --git a/core/os/input_event.cpp b/core/os/input_event.cpp index 9982767be1..7350be824a 100644 --- a/core/os/input_event.cpp +++ b/core/os/input_event.cpp @@ -50,7 +50,8 @@ bool InputEvent::operator==(const InputEvent &p_event) const { case MOUSE_MOTION: return mouse_motion.x == p_event.mouse_motion.x && mouse_motion.y == p_event.mouse_motion.y - && mouse_motion.relative_x == p_event.mouse_motion.relative_y + && mouse_motion.relative_x == p_event.mouse_motion.relative_x + && mouse_motion.relative_y == p_event.mouse_motion.relative_y && mouse_motion.button_mask == p_event.mouse_motion.button_mask && key.mod == p_event.key.mod; case MOUSE_BUTTON: diff --git a/doc/base/classes.xml b/doc/base/classes.xml index 13d2b8f619..977b44763e 100644 --- a/doc/base/classes.xml +++ b/doc/base/classes.xml @@ -32517,6 +32517,7 @@ would be read as [code]"(?:\\.|[^"])*"[/code] Currently supported features: * Capturing [code]()[/code] and non-capturing [code](?:)[/code] groups + * Named capturing groups [code](?P<name>)[/code] * Any character [code].[/code] * Shorthand character classes [code]\w \W \s \S \d \D[/code] * User-defined character classes such as [code][A-Za-z][/code] @@ -32525,7 +32526,7 @@ * Lazy (non-greedy) quantifiers [code]*?[/code] * Beginning [code]^[/code] and end [code]$[/code] anchors * Alternation [code]|[/code] - * Backreferences [code]\1[/code] and [code]\g{1}[/code] + * Backreferences [code]\1[/code], [code]\g{1}[/code], and [code]\g<name>[/code] * POSIX character classes [code][[:alnum:]][/code] * Lookahead [code](?=)[/code], [code](?!)[/code] and lookbehind [code](?<=)[/code], [code](?<!)[/code] * ASCII [code]\xFF[/code] and Unicode [code]\uFFFF[/code] code points (in a style similar to Python) @@ -32534,7 +32535,7 @@ <methods> <method name="clear"> <description> - This method resets the state of the object, as it was freshly created. Namely, it unassigns the regular expression of this object, and forgets all captures made by the last [method find]. + This method resets the state of the object, as it was freshly created. Namely, it unassigns the regular expression of this object. </description> </method> <method name="compile"> @@ -32542,15 +32543,41 @@ </return> <argument index="0" name="pattern" type="String"> </argument> - <argument index="1" name="capture" type="int" default="9"> - </argument> <description> - Compiles and assign the regular expression pattern to use. The limit on the number of capturing groups can be specified or made unlimited if negative. + Compiles and assign the regular expression pattern to use. </description> </method> - <method name="find" qualifiers="const"> + <method name="get_group_count" qualifiers="const"> <return type="int"> </return> + <description> + Returns the number of numeric capturing groups. + </description> + </method> + <method name="get_names" qualifiers="const"> + <return type="Array"> + </return> + <description> + Returns an array of names of named capturing groups. + </description> + </method> + <method name="get_pattern" qualifiers="const"> + <return type="String"> + </return> + <description> + Returns the expression used to compile the code. + </description> + </method> + <method name="is_valid" qualifiers="const"> + <return type="bool"> + </return> + <description> + Returns whether this object has a valid regular expression assigned. + </description> + </method> + <method name="search" qualifiers="const"> + <return type="Object"> + </return> <argument index="0" name="text" type="String"> </argument> <argument index="1" name="start" type="int" default="0"> @@ -32558,45 +32585,98 @@ <argument index="2" name="end" type="int" default="-1"> </argument> <description> - This method tries to find the pattern within the string, and returns the position where it was found. It also stores any capturing group (see [method get_capture]) for further retrieval. + Searches the text for the compiled pattern. Returns a [RegExMatch] container of the first matching reult if found, otherwise null. The region to search within can be specified without modifying where the start and end anchor would be. </description> </method> - <method name="get_capture" qualifiers="const"> + <method name="sub" qualifiers="const"> <return type="String"> </return> - <argument index="0" name="capture" type="int"> + <argument index="0" name="text" type="String"> + </argument> + <argument index="1" name="replacement" type="String"> + </argument> + <argument index="2" name="all" type="bool" default="false"> + </argument> + <argument index="3" name="start" type="int" default="0"> + </argument> + <argument index="4" name="end" type="int" default="-1"> </argument> <description> - Returns a captured group. A captured group is the part of a string that matches a part of the pattern delimited by parentheses (unless they are non-capturing parentheses [i](?:)[/i]). + Searches the text for the compiled pattern and replaces it with the specified string. Escapes and backreferences such as [code]\1[/code] and [code]\g<name>[/code] expanded and resolved. By default only the first instance is replaced but it can be changed for all instances (global replacement). The region to search within can be specified without modifying where the start and end anchor would be. </description> </method> - <method name="get_capture_count" qualifiers="const"> - <return type="int"> + </methods> + <constants> + </constants> +</class> +<class name="RegExMatch" inherits="Reference" category="Core"> + <brief_description> + </brief_description> + <description> + </description> + <methods> + <method name="expand" qualifiers="const"> + <return type="String"> </return> + <argument index="0" name="template" type="String"> + </argument> <description> - Returns the number of capturing groups. A captured group is the part of a string that matches a part of the pattern delimited by parentheses (unless they are non-capturing parentheses [i](?:)[/i]). + Using results from the search, returns the specified string with escapes and backreferences such as [code]\1[/code] and [code]\g<name>[/code] expanded and resolved. </description> </method> - <method name="get_capture_start" qualifiers="const"> + <method name="get_end" qualifiers="const"> <return type="int"> </return> - <argument index="0" name="capture" type="int"> + <argument index="0" name="name" type="Variant" default="0"> </argument> <description> + Returns the end position of the match in the string. An interger can be specified for numeric groups or a string for named groups. Returns -1 if that group wasn't found or doesn't exist. Defaults to 0 (whole pattern). </description> </method> - <method name="get_captures" qualifiers="const"> - <return type="StringArray"> + <method name="get_group_array" qualifiers="const"> + <return type="Array"> </return> <description> - Return a list of all the captures made by the regular expression. + Returns an array of the results of the numeric groups. </description> </method> - <method name="is_valid" qualifiers="const"> - <return type="bool"> + <method name="get_group_count" qualifiers="const"> + <return type="int"> </return> <description> - Returns whether this object has a valid regular expression assigned. + Returns the number of numeric capturing groups. + </description> + </method> + <method name="get_name_dict" qualifiers="const"> + <return type="Dictionary"> + </return> + <description> + Returns a dictionary containing the named capturing groups and their results. + </description> + </method> + <method name="get_names" qualifiers="const"> + <return type="Array"> + </return> + <description> + Returns an array of names of named capturing groups. + </description> + </method> + <method name="get_start" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="name" type="Variant" default="0"> + </argument> + <description> + Returns the starting position of the match in the string. An interger can be specified for numeric groups or a string for named groups. Returns -1 if that group wasn't found or doesn't exist. Defaults to 0 (whole pattern). + </description> + </method> + <method name="get_string" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="name" type="Variant" default="0"> + </argument> + <description> + Returns the result of the match in the string. An interger can be specified for numeric groups or a string for named groups. Returns -1 if that group wasn't found or doesn't exist. Defaults to 0 (whole pattern). </description> </method> </methods> diff --git a/drivers/SCsub b/drivers/SCsub index 1f1509efa8..ab2c991f24 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -25,7 +25,6 @@ SConscript('gl_context/SCsub'); # Core dependencies SConscript("png/SCsub"); -SConscript("nrex/SCsub"); # Tools override # FIXME: Should likely be integrated in the tools/ codebase diff --git a/drivers/nrex/README.md b/drivers/nrex/README.md deleted file mode 100644 index 7a942b2452..0000000000 --- a/drivers/nrex/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# NREX: Node RegEx - -[![Build Status](https://travis-ci.org/leezh/nrex.svg?branch=master)](https://travis-ci.org/leezh/nrex) - -** Version 0.2 ** - -Small node-based regular expression library. It only does text pattern -matchhing, not replacement. To use add the files `nrex.hpp`, `nrex.cpp` -and `nrex_config.h` to your project and follow the example: - - nrex regex; - regex.compile("^(fo+)bar$"); - - nrex_result captures[regex.capture_size()]; - if (regex.match("foobar", captures)) - { - std::cout << captures[0].start << std::endl; - std::cout << captures[0].length << std::endl; - } - -More details about its use is documented in `nrex.hpp` - -Currently supported features: - * Capturing `()` and non-capturing `(?:)` groups - * Any character `.` (includes newlines) - * Shorthand caracter classes `\w\W\s\S\d\D` - * POSIX character classes such as `[[:alnum:]]` - * Bracket expressions such as `[A-Za-z]` - * Simple quantifiers `?`, `*` and `+` - * Range quantifiers `{0,1}` - * Lazy (non-greedy) quantifiers `*?` - * Begining `^` and end `$` anchors - * Word boundaries `\b` - * Alternation `|` - * ASCII `\xFF` code points - * Unicode `\uFFFF` code points - * Positive `(?=)` and negative `(?!)` lookahead - * Positive `(?<=)` and negative `(?<!)` lookbehind (fixed length and no alternations) - * Backreferences `\1` and `\g{1}` (limited by default to 9 - can be unlimited) - -## License - -Copyright (c) 2015-2016, Zher Huei Lee -All rights reserved. - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would - be appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. - - -# Changes - -## Version 0.2 (2016-08-04) - * Fixed capturing groups matching to invalid results - * Fixed parents of recursive quantifiers not expanding properly - * Fixed LookAhead sometimes adding to result - * More verbose unit testing - -## Version 0.1 (2015-12-04) - * Initial release diff --git a/drivers/nrex/nrex.cpp b/drivers/nrex/nrex.cpp deleted file mode 100644 index 69e04285e3..0000000000 --- a/drivers/nrex/nrex.cpp +++ /dev/null @@ -1,1496 +0,0 @@ -// NREX: Node RegEx -// Version 0.2 -// -// Copyright (c) 2015-2016, Zher Huei Lee -// All rights reserved. -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would -// be appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, and must not -// be misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source -// distribution. -// - -#include "nrex.hpp" - -#ifdef NREX_UNICODE -#include <wctype.h> -#include <wchar.h> -#define NREX_ISALPHANUM iswalnum -#define NREX_ISSPACE iswspace -#define NREX_STRLEN wcslen -#else -#include <ctype.h> -#include <string.h> -#define NREX_ISALPHANUM isalnum -#define NREX_ISSPACE isspace -#define NREX_STRLEN strlen -#endif - -#ifdef NREX_THROW_ERROR -#define NREX_COMPILE_ERROR(M) throw nrex_compile_error(M) -#else -#define NREX_COMPILE_ERROR(M) reset(); return false -#endif - -#ifndef NREX_NEW -#define NREX_NEW(X) new X -#define NREX_NEW_ARRAY(X, N) new X[N] -#define NREX_DELETE(X) delete X -#define NREX_DELETE_ARRAY(X) delete[] X -#endif - -template<typename T> -class nrex_array -{ - private: - T* _data; - unsigned int _reserved; - unsigned int _size; - public: - nrex_array() - : _data(NREX_NEW_ARRAY(T, 2)) - , _reserved(2) - , _size(0) - { - } - - nrex_array(unsigned int reserved) - : _data(NREX_NEW_ARRAY(T, reserved ? reserved : 1)) - , _reserved(reserved ? reserved : 1) - , _size(0) - { - } - - ~nrex_array() - { - NREX_DELETE_ARRAY(_data); - } - - unsigned int size() const - { - return _size; - } - - void reserve(unsigned int size) - { - if (size < _size) { - size = _size; - } - if (size == 0) { - size = 1; - } - T* old = _data; - _data = NREX_NEW_ARRAY(T, size); - _reserved = size; - for (unsigned int i = 0; i < _size; ++i) - { - _data[i] = old[i]; - } - NREX_DELETE_ARRAY(old); - } - - void push(T item) - { - if (_size == _reserved) - { - reserve(_reserved * 2); - } - _data[_size] = item; - _size++; - } - - const T& top() const - { - return _data[_size - 1]; - } - - const T& operator[] (unsigned int i) const - { - return _data[i]; - } - - void pop() - { - if (_size > 0) - { - --_size; - } - } -}; - -static int nrex_parse_hex(nrex_char c) -{ - if ('0' <= c && c <= '9') - { - return int(c - '0'); - } - else if ('a' <= c && c <= 'f') - { - return int(c - 'a') + 10; - } - else if ('A' <= c && c <= 'F') - { - return int(c - 'A') + 10; - } - return -1; -} - -static nrex_char nrex_unescape(const nrex_char*& c) -{ - switch (c[1]) - { - case '0': ++c; return '\0'; - case 'a': ++c; return '\a'; - case 'e': ++c; return '\e'; - case 'f': ++c; return '\f'; - case 'n': ++c; return '\n'; - case 'r': ++c; return '\r'; - case 't': ++c; return '\t'; - case 'v': ++c; return '\v'; - case 'b': ++c; return '\b'; - case 'x': - { - int point = 0; - for (int i = 2; i <= 3; ++i) - { - int res = nrex_parse_hex(c[i]); - if (res == -1) - { - return '\0'; - } - point = (point << 4) + res; - } - c = &c[3]; - return nrex_char(point); - } - case 'u': - { - int point = 0; - for (int i = 2; i <= 5; ++i) - { - int res = nrex_parse_hex(c[i]); - if (res == -1) - { - return '\0'; - } - point = (point << 4) + res; - } - c = &c[5]; - return nrex_char(point); - } - } - return (++c)[0]; -} - -struct nrex_search -{ - const nrex_char* str; - nrex_result* captures; - int end; - bool complete; - nrex_array<int> lookahead_pos; - - nrex_char at(int pos) - { - return str[pos]; - } - - nrex_search(const nrex_char* str, nrex_result* captures, int lookahead) - : str(str) - , captures(captures) - , end(0) - , lookahead_pos(lookahead) - { - } -}; - -struct nrex_node -{ - nrex_node* next; - nrex_node* previous; - nrex_node* parent; - bool quantifiable; - int length; - - nrex_node(bool quantify = false) - : next(NULL) - , previous(NULL) - , parent(NULL) - , quantifiable(quantify) - , length(-1) - { - } - - virtual ~nrex_node() - { - if (next) - { - NREX_DELETE(next); - } - } - - virtual int test(nrex_search* s, int pos) const - { - return next ? next->test(s, pos) : -1; - } - - virtual int test_parent(nrex_search* s, int pos) const - { - if (next) - { - pos = next->test(s, pos); - } - if (pos >= 0) - { - s->complete = true; - } - if (parent && pos >= 0) - { - pos = parent->test_parent(s, pos); - } - if (pos < 0) - { - s->complete = false; - } - return pos; - } - - void increment_length(int amount, bool subtract = false) - { - if (amount >= 0 && length >= 0) - { - if (!subtract) - { - length += amount; - } - else - { - length -= amount; - } - } - else - { - length = -1; - } - if (parent) - { - parent->increment_length(amount, subtract); - } - } -}; - -enum nrex_group_type -{ - nrex_group_capture, - nrex_group_non_capture, - nrex_group_bracket, - nrex_group_look_ahead, - nrex_group_look_behind, -}; - -struct nrex_node_group : public nrex_node -{ - nrex_group_type type; - int id; - bool negate; - nrex_array<nrex_node*> childset; - nrex_node* back; - - nrex_node_group(nrex_group_type type, int id = 0) - : nrex_node(true) - , type(type) - , id(id) - , negate(false) - , back(NULL) - { - if (type != nrex_group_bracket) - { - length = 0; - } - else - { - length = 1; - } - if (type == nrex_group_look_ahead || type == nrex_group_look_behind) - { - quantifiable = false; - } - } - - virtual ~nrex_node_group() - { - for (unsigned int i = 0; i < childset.size(); ++i) - { - NREX_DELETE(childset[i]); - } - - } - - int test(nrex_search* s, int pos) const - { - int old_start; - if (type == nrex_group_capture) - { - old_start = s->captures[id].start; - s->captures[id].start = pos; - } - for (unsigned int i = 0; i < childset.size(); ++i) - { - s->complete = false; - int offset = 0; - if (type == nrex_group_look_behind) - { - if (pos < length) - { - return -1; - } - offset = length; - } - if (type == nrex_group_look_ahead) - { - s->lookahead_pos.push(pos); - } - int res = childset[i]->test(s, pos - offset); - if (type == nrex_group_look_ahead) - { - s->lookahead_pos.pop(); - } - if (s->complete) - { - return res; - } - if (negate) - { - if (res < 0) - { - res = pos + 1; - } - else - { - return -1; - } - if (i + 1 < childset.size()) - { - continue; - } - } - if (res >= 0) - { - if (type == nrex_group_capture) - { - s->captures[id].length = res - pos; - } - else if (type == nrex_group_look_ahead || type == nrex_group_look_behind) - { - res = pos; - } - return next ? next->test(s, res) : res; - } - } - if (type == nrex_group_capture) - { - s->captures[id].start = old_start; - } - return -1; - } - - virtual int test_parent(nrex_search* s, int pos) const - { - if (type == nrex_group_capture) - { - s->captures[id].length = pos - s->captures[id].start; - } - if (type == nrex_group_look_ahead) - { - pos = s->lookahead_pos[id]; - } - return nrex_node::test_parent(s, pos); - } - - void add_childset() - { - if (childset.size() > 0 && type != nrex_group_bracket) - { - length = -1; - } - back = NULL; - } - - void add_child(nrex_node* node) - { - node->parent = this; - node->previous = back; - if (back && type != nrex_group_bracket) - { - back->next = node; - } - else - { - childset.push(node); - } - if (type != nrex_group_bracket) - { - increment_length(node->length); - } - back = node; - } - - nrex_node* swap_back(nrex_node* node) - { - if (!back) - { - add_child(node); - return NULL; - } - nrex_node* old = back; - if (!old->previous) - { - childset.pop(); - } - if (type != nrex_group_bracket) - { - increment_length(old->length, true); - } - back = old->previous; - add_child(node); - return old; - } - - void pop_back() - { - if (back) - { - nrex_node* old = back; - if (!old->previous) - { - childset.pop(); - } - if (type != nrex_group_bracket) - { - increment_length(old->length, true); - } - back = old->previous; - NREX_DELETE(old); - } - } -}; - -struct nrex_node_char : public nrex_node -{ - nrex_char ch; - - nrex_node_char(nrex_char c) - : nrex_node(true) - , ch(c) - { - length = 1; - } - - int test(nrex_search* s, int pos) const - { - if (s->end <= pos || 0 > pos || s->at(pos) != ch) - { - return -1; - } - return next ? next->test(s, pos + 1) : pos + 1; - } -}; - -struct nrex_node_range : public nrex_node -{ - nrex_char start; - nrex_char end; - - nrex_node_range(nrex_char s, nrex_char e) - : nrex_node(true) - , start(s) - , end(e) - { - length = 1; - } - - int test(nrex_search* s, int pos) const - { - if (s->end <= pos || 0 > pos) - { - return -1; - } - nrex_char c = s->at(pos); - if (c < start || end < c) - { - return -1; - } - return next ? next->test(s, pos + 1) : pos + 1; - } -}; - -enum nrex_class_type -{ - nrex_class_none, - nrex_class_alnum, - nrex_class_alpha, - nrex_class_blank, - nrex_class_cntrl, - nrex_class_digit, - nrex_class_graph, - nrex_class_lower, - nrex_class_print, - nrex_class_punct, - nrex_class_space, - nrex_class_upper, - nrex_class_xdigit, - nrex_class_word -}; - -static bool nrex_compare_class(const nrex_char** pos, const char* text) -{ - unsigned int i = 0; - for (i = 0; text[i] != '\0'; ++i) - { - if ((*pos)[i] != text[i]) - { - return false; - } - } - if ((*pos)[i++] != ':' || (*pos)[i] != ']') - { - return false; - } - *pos = &(*pos)[i]; - return true; -} - -#define NREX_COMPARE_CLASS(POS, NAME) if (nrex_compare_class(POS, #NAME)) return nrex_class_ ## NAME - -static nrex_class_type nrex_parse_class(const nrex_char** pos) -{ - NREX_COMPARE_CLASS(pos, alnum); - NREX_COMPARE_CLASS(pos, alpha); - NREX_COMPARE_CLASS(pos, blank); - NREX_COMPARE_CLASS(pos, cntrl); - NREX_COMPARE_CLASS(pos, digit); - NREX_COMPARE_CLASS(pos, graph); - NREX_COMPARE_CLASS(pos, lower); - NREX_COMPARE_CLASS(pos, print); - NREX_COMPARE_CLASS(pos, punct); - NREX_COMPARE_CLASS(pos, space); - NREX_COMPARE_CLASS(pos, upper); - NREX_COMPARE_CLASS(pos, xdigit); - NREX_COMPARE_CLASS(pos, word); - return nrex_class_none; -} - -struct nrex_node_class : public nrex_node -{ - nrex_class_type type; - - nrex_node_class(nrex_class_type t) - : nrex_node(true) - , type(t) - { - length = 1; - } - - int test(nrex_search* s, int pos) const - { - if (s->end <= pos || 0 > pos) - { - return -1; - } - if (!test_class(s->at(pos))) - { - return -1; - } - return next ? next->test(s, pos + 1) : pos + 1; - } - - bool test_class(nrex_char c) const - { - if ((0 <= c && c <= 0x1F) || c == 0x7F) - { - if (type == nrex_class_cntrl) - { - return true; - } - } - else if (c < 0x7F) - { - if (type == nrex_class_print) - { - return true; - } - else if (type == nrex_class_graph && c != ' ') - { - return true; - } - else if ('0' <= c && c <= '9') - { - switch (type) - { - case nrex_class_alnum: - case nrex_class_digit: - case nrex_class_xdigit: - case nrex_class_word: - return true; - default: - break; - } - } - else if ('A' <= c && c <= 'Z') - { - switch (type) - { - case nrex_class_alnum: - case nrex_class_alpha: - case nrex_class_upper: - case nrex_class_word: - return true; - case nrex_class_xdigit: - if (c <= 'F') - { - return true; - } - default: - break; - } - } - else if ('a' <= c && c <= 'z') - { - switch (type) - { - case nrex_class_alnum: - case nrex_class_alpha: - case nrex_class_lower: - case nrex_class_word: - return true; - case nrex_class_xdigit: - if (c <= 'f') - { - return true; - } - default: - break; - } - } - } - switch (c) - { - case ' ': - case '\t': - if (type == nrex_class_blank) - { - return true; - } - case '\r': - case '\n': - case '\f': - if (type == nrex_class_space) - { - return true; - } - break; - case '_': - if (type == nrex_class_word) - { - return true; - } - case ']': - case '[': - case '!': - case '"': - case '#': - case '$': - case '%': - case '&': - case '\'': - case '(': - case ')': - case '*': - case '+': - case ',': - case '.': - case '/': - case ':': - case ';': - case '<': - case '=': - case '>': - case '?': - case '@': - case '\\': - case '^': - case '`': - case '{': - case '|': - case '}': - case '~': - case '-': - if (type == nrex_class_punct) - { - return true; - } - break; - default: - break; - } - return false; - } -}; - -static bool nrex_is_shorthand(nrex_char repr) -{ - switch (repr) - { - case 'W': - case 'w': - case 'D': - case 'd': - case 'S': - case 's': - return true; - } - return false; -} - -struct nrex_node_shorthand : public nrex_node -{ - nrex_char repr; - - nrex_node_shorthand(nrex_char c) - : nrex_node(true) - , repr(c) - { - length = 1; - } - - int test(nrex_search* s, int pos) const - { - if (s->end <= pos || 0 > pos) - { - return -1; - } - bool found = false; - bool invert = false; - nrex_char c = s->at(pos); - switch (repr) - { - case '.': - found = true; - break; - case 'W': - invert = true; - case 'w': - if (c == '_' || NREX_ISALPHANUM(c)) - { - found = true; - } - break; - case 'D': - invert = true; - case 'd': - if ('0' <= c && c <= '9') - { - found = true; - } - break; - case 'S': - invert = true; - case 's': - if (NREX_ISSPACE(c)) - { - found = true; - } - break; - } - if (found == invert) - { - return -1; - } - return next ? next->test(s, pos + 1) : pos + 1; - } -}; - -static bool nrex_is_quantifier(nrex_char repr) -{ - switch (repr) - { - case '?': - case '*': - case '+': - case '{': - return true; - } - return false; -} - -struct nrex_node_quantifier : public nrex_node -{ - int min; - int max; - bool greedy; - nrex_node* child; - - nrex_node_quantifier(int min, int max) - : nrex_node() - , min(min) - , max(max) - , greedy(true) - , child(NULL) - { - } - - virtual ~nrex_node_quantifier() - { - if (child) - { - NREX_DELETE(child); - } - } - - int test(nrex_search* s, int pos) const - { - return test_step(s, pos, 0, pos); - } - - int test_step(nrex_search* s, int pos, int level, int start) const - { - if (pos > s->end) - { - return -1; - } - if (!greedy && level > min) - { - int res = pos; - if (next) - { - res = next->test(s, res); - } - if (s->complete) - { - return res; - } - if (res >= 0 && parent->test_parent(s, res) >= 0) - { - return res; - } - } - if (max >= 0 && level > max) - { - return -1; - } - if (level > 1 && level > min + 1 && pos == start) - { - return -1; - } - int res = pos; - if (level >= 1) - { - res = child->test(s, pos); - if (s->complete) - { - return res; - } - } - if (res >= 0) - { - int res_step = test_step(s, res, level + 1, start); - if (res_step >= 0) - { - return res_step; - } - else if (greedy && level >= min) - { - if (next) - { - res = next->test(s, res); - } - if (s->complete) - { - return res; - } - if (res >= 0 && parent->test_parent(s, res) >= 0) - { - return res; - } - } - } - return -1; - } - - virtual int test_parent(nrex_search* s, int pos) const - { - s->complete = false; - return pos; - } -}; - -struct nrex_node_anchor : public nrex_node -{ - bool end; - - nrex_node_anchor(bool end) - : nrex_node() - , end(end) - { - length = 0; - } - - int test(nrex_search* s, int pos) const - { - if (!end && pos != 0) - { - return -1; - } - else if (end && pos != s->end) - { - return -1; - } - return next ? next->test(s, pos) : pos; - } -}; - -struct nrex_node_word_boundary : public nrex_node -{ - bool inverse; - - nrex_node_word_boundary(bool inverse) - : nrex_node() - , inverse(inverse) - { - length = 0; - } - - int test(nrex_search* s, int pos) const - { - bool left = false; - bool right = false; - if (pos != 0) - { - nrex_char c = s->at(pos - 1); - if (c == '_' || NREX_ISALPHANUM(c)) - { - left = true; - } - } - if (pos != s->end) - { - nrex_char c = s->at(pos); - if (c == '_' || NREX_ISALPHANUM(c)) - { - right = true; - } - } - if ((left != right) == inverse) - { - return -1; - } - return next ? next->test(s, pos) : pos; - } -}; - -struct nrex_node_backreference : public nrex_node -{ - int ref; - - nrex_node_backreference(int ref) - : nrex_node(true) - , ref(ref) - { - length = -1; - } - - int test(nrex_search* s, int pos) const - { - nrex_result& r = s->captures[ref]; - for (int i = 0; i < r.length; ++i) - { - if (pos + i >= s->end) - { - return -1; - } - if (s->at(r.start + i) != s->at(pos + i)) - { - return -1; - } - } - return next ? next->test(s, pos + r.length) : pos + r.length; - } -}; - -bool nrex_has_lookbehind(nrex_array<nrex_node_group*>& stack) -{ - for (unsigned int i = 0; i < stack.size(); i++) - { - if (stack[i]->type == nrex_group_look_behind) - { - return true; - } - } - return false; -} - -nrex::nrex() - : _capturing(0) - , _lookahead_depth(0) - , _root(NULL) -{ -} - -nrex::nrex(const nrex_char* pattern, int captures) - : _capturing(0) - , _lookahead_depth(0) - , _root(NULL) -{ - compile(pattern, captures); -} - -nrex::~nrex() -{ - if (_root) - { - NREX_DELETE(_root); - } -} - -bool nrex::valid() const -{ - return (_root != NULL); -} - -void nrex::reset() -{ - _capturing = 0; - _lookahead_depth = 0; - if (_root) - { - NREX_DELETE(_root); - } - _root = NULL; -} - -int nrex::capture_size() const -{ - if (_root) - { - return _capturing + 1; - } - return 0; -} - -bool nrex::compile(const nrex_char* pattern, int captures) -{ - reset(); - nrex_node_group* root = NREX_NEW(nrex_node_group(nrex_group_capture, _capturing)); - nrex_array<nrex_node_group*> stack; - stack.push(root); - unsigned int lookahead_level = 0; - _root = root; - - for (const nrex_char* c = pattern; c[0] != '\0'; ++c) - { - if (c[0] == '(') - { - if (c[1] == '?') - { - if (c[2] == ':') - { - c = &c[2]; - nrex_node_group* group = NREX_NEW(nrex_node_group(nrex_group_non_capture)); - stack.top()->add_child(group); - stack.push(group); - } - else if (c[2] == '!' || c[2] == '=') - { - c = &c[2]; - nrex_node_group* group = NREX_NEW(nrex_node_group(nrex_group_look_ahead, lookahead_level++)); - group->negate = (c[0] == '!'); - stack.top()->add_child(group); - stack.push(group); - if (lookahead_level > _lookahead_depth) - { - _lookahead_depth = lookahead_level; - } - } - else if (c[2] == '<' && (c[3] == '!' || c[3] == '=')) - { - c = &c[3]; - nrex_node_group* group = NREX_NEW(nrex_node_group(nrex_group_look_behind)); - group->negate = (c[0] == '!'); - stack.top()->add_child(group); - stack.push(group); - } - else - { - NREX_COMPILE_ERROR("unrecognised qualifier for group"); - } - } - else if (captures >= 0 && _capturing < captures) - { - nrex_node_group* group = NREX_NEW(nrex_node_group(nrex_group_capture, ++_capturing)); - stack.top()->add_child(group); - stack.push(group); - } - else - { - nrex_node_group* group = NREX_NEW(nrex_node_group(nrex_group_non_capture)); - stack.top()->add_child(group); - stack.push(group); - } - } - else if (c[0] == ')') - { - if (stack.size() > 1) - { - if (stack.top()->type == nrex_group_look_ahead) - { - --lookahead_level; - } - stack.pop(); - } - else - { - NREX_COMPILE_ERROR("unexpected ')'"); - } - } - else if (c[0] == '[') - { - nrex_node_group* group = NREX_NEW(nrex_node_group(nrex_group_bracket)); - stack.top()->add_child(group); - if (c[1] == '^') - { - group->negate = true; - ++c; - } - bool first_child = true; - nrex_char previous_child; - bool previous_child_single = false; - while (true) - { - group->add_childset(); - ++c; - if (c[0] == '\0') - { - NREX_COMPILE_ERROR("unclosed bracket expression '['"); - } - if (c[0] == '[' && c[1] == ':') - { - const nrex_char* d = &c[2]; - nrex_class_type cls = nrex_parse_class(&d); - if (cls != nrex_class_none) - { - c = d; - group->add_child(NREX_NEW(nrex_node_class(cls))); - previous_child_single = false; - } - else - { - group->add_child(NREX_NEW(nrex_node_char('['))); - previous_child = '['; - previous_child_single = true; - } - } - else if (c[0] == ']' && !first_child) - { - break; - } - else if (c[0] == '\\') - { - if (nrex_is_shorthand(c[1])) - { - group->add_child(NREX_NEW(nrex_node_shorthand(c[1]))); - ++c; - previous_child_single = false; - } - else - { - const nrex_char* d = c; - nrex_char unescaped = nrex_unescape(d); - if (c == d) - { - NREX_COMPILE_ERROR("invalid escape token"); - } - group->add_child(NREX_NEW(nrex_node_char(unescaped))); - c = d; - previous_child = unescaped; - previous_child_single = true; - } - } - else if (previous_child_single && c[0] == '-') - { - bool is_range = false; - nrex_char next; - if (c[1] != '\0' && c[1] != ']') - { - if (c[1] == '\\') - { - const nrex_char* d = ++c; - next = nrex_unescape(d); - if (c == d) - { - NREX_COMPILE_ERROR("invalid escape token in range"); - } - } - else - { - next = c[1]; - ++c; - } - is_range = true; - } - if (is_range) - { - if (next < previous_child) - { - NREX_COMPILE_ERROR("text range out of order"); - } - group->pop_back(); - group->add_child(NREX_NEW(nrex_node_range(previous_child, next))); - previous_child_single = false; - } - else - { - group->add_child(NREX_NEW(nrex_node_char(c[0]))); - previous_child = c[0]; - previous_child_single = true; - } - } - else - { - group->add_child(NREX_NEW(nrex_node_char(c[0]))); - previous_child = c[0]; - previous_child_single = true; - } - first_child = false; - } - } - else if (nrex_is_quantifier(c[0])) - { - int min = 0; - int max = -1; - bool valid_quantifier = true; - if (c[0] == '?') - { - min = 0; - max = 1; - } - else if (c[0] == '+') - { - min = 1; - max = -1; - } - else if (c[0] == '*') - { - min = 0; - max = -1; - } - else if (c[0] == '{') - { - bool max_set = false; - const nrex_char* d = c; - while (true) - { - ++d; - if (d[0] == '\0') - { - valid_quantifier = false; - break; - } - else if (d[0] == '}') - { - break; - } - else if (d[0] == ',') - { - max_set = true; - continue; - } - else if (d[0] < '0' || '9' < d[0]) - { - valid_quantifier = false; - break; - } - if (max_set) - { - if (max < 0) - { - max = int(d[0] - '0'); - } - else - { - max = max * 10 + int(d[0] - '0'); - } - } - else - { - min = min * 10 + int(d[0] - '0'); - } - } - if (!max_set) - { - max = min; - } - if (valid_quantifier) - { - c = d; - } - } - if (valid_quantifier) - { - if (stack.top()->back == NULL || !stack.top()->back->quantifiable) - { - NREX_COMPILE_ERROR("element not quantifiable"); - } - nrex_node_quantifier* quant = NREX_NEW(nrex_node_quantifier(min, max)); - if (min == max) - { - if (stack.top()->back->length >= 0) - { - quant->length = max * stack.top()->back->length; - } - } - else - { - if (nrex_has_lookbehind(stack)) - { - NREX_COMPILE_ERROR("variable length quantifiers inside lookbehind not supported"); - } - } - quant->child = stack.top()->swap_back(quant); - quant->child->previous = NULL; - quant->child->next = NULL; - quant->child->parent = quant; - if (c[1] == '?') - { - quant->greedy = false; - ++c; - } - } - else - { - stack.top()->add_child(NREX_NEW(nrex_node_char(c[0]))); - } - } - else if (c[0] == '|') - { - if (nrex_has_lookbehind(stack)) - { - NREX_COMPILE_ERROR("alternations inside lookbehind not supported"); - } - stack.top()->add_childset(); - } - else if (c[0] == '^' || c[0] == '$') - { - stack.top()->add_child(NREX_NEW(nrex_node_anchor((c[0] == '$')))); - } - else if (c[0] == '.') - { - stack.top()->add_child(NREX_NEW(nrex_node_shorthand('.'))); - } - else if (c[0] == '\\') - { - if (nrex_is_shorthand(c[1])) - { - stack.top()->add_child(NREX_NEW(nrex_node_shorthand(c[1]))); - ++c; - } - else if (('1' <= c[1] && c[1] <= '9') || (c[1] == 'g' && c[2] == '{')) - { - int ref = 0; - bool unclosed = false; - if (c[1] == 'g') - { - unclosed = true; - c = &c[2]; - } - while ('0' <= c[1] && c[1] <= '9') - { - ref = ref * 10 + int(c[1] - '0'); - ++c; - } - if (c[1] == '}') - { - unclosed = false; - ++c; - } - if (ref > _capturing || ref <= 0 || unclosed) - { - NREX_COMPILE_ERROR("backreference to non-existent capture"); - } - if (nrex_has_lookbehind(stack)) - { - NREX_COMPILE_ERROR("backreferences inside lookbehind not supported"); - } - stack.top()->add_child(NREX_NEW(nrex_node_backreference(ref))); - } - else if (c[1] == 'b' || c[1] == 'B') - { - stack.top()->add_child(NREX_NEW(nrex_node_word_boundary(c[1] == 'B'))); - ++c; - } - else - { - const nrex_char* d = c; - nrex_char unescaped = nrex_unescape(d); - if (c == d) - { - NREX_COMPILE_ERROR("invalid escape token"); - } - stack.top()->add_child(NREX_NEW(nrex_node_char(unescaped))); - c = d; - } - } - else - { - stack.top()->add_child(NREX_NEW(nrex_node_char(c[0]))); - } - } - if (stack.size() > 1) - { - NREX_COMPILE_ERROR("unclosed group '('"); - } - return true; -} - -bool nrex::match(const nrex_char* str, nrex_result* captures, int offset, int end) const -{ - if (!_root) - { - return false; - } - nrex_search s(str, captures, _lookahead_depth); - if (end >= offset) - { - s.end = end; - } - else - { - s.end = NREX_STRLEN(str); - } - for (int i = offset; i <= s.end; ++i) - { - for (int c = 0; c <= _capturing; ++c) - { - captures[c].start = 0; - captures[c].length = 0; - } - if (_root->test(&s, i) >= 0) - { - return true; - } - } - return false; -} diff --git a/drivers/nrex/nrex.hpp b/drivers/nrex/nrex.hpp deleted file mode 100644 index d30b7d0102..0000000000 --- a/drivers/nrex/nrex.hpp +++ /dev/null @@ -1,176 +0,0 @@ -// NREX: Node RegEx -// Version 0.2 -// -// Copyright (c) 2015-2016, Zher Huei Lee -// All rights reserved. -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would -// be appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, and must not -// be misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source -// distribution. -// - -#ifndef NREX_HPP -#define NREX_HPP - -#include "nrex_config.h" - -#ifdef NREX_UNICODE -typedef wchar_t nrex_char; -#else -typedef char nrex_char; -#endif - -/*! - * \brief Struct to contain the range of a capture result - * - * The range provided is relative to the begining of the searched string. - * - * \see nrex_node::match() - */ -struct nrex_result -{ - public: - int start; /*!< Start of text range */ - int length; /*!< Length of text range */ -}; - -class nrex_node; - -/*! - * \brief Holds the compiled regex pattern - */ -class nrex -{ - private: - unsigned int _capturing; - unsigned int _lookahead_depth; - nrex_node* _root; - public: - - /*! - * \brief Initialises an empty regex container - */ - nrex(); - - /*! - * \brief Initialises and compiles the regex pattern - * - * This calls nrex::compile() with the same arguments. To check whether - * the compilation was successfull, use nrex::valid(). - * - * If the NREX_THROW_ERROR was defined it would automatically throw a - * runtime error nrex_compile_error if it encounters a problem when - * parsing the pattern. - * - * \param pattern The regex pattern - * \param captures The maximum number of capture groups to allow. Any - * extra would be converted to non-capturing groups. - * If negative, no limit would be imposed. Defaults - * to 9. - * - * \see nrex::compile() - */ - nrex(const nrex_char* pattern, int captures = 9); - - ~nrex(); - - /*! - * \brief Removes the compiled regex and frees up the memory - */ - void reset(); - - /*! - * \brief Checks if there is a compiled regex being stored - * \return True if present, False if not present - */ - bool valid() const; - - /*! - * \brief Provides number of captures the compiled regex uses - * - * This is used to provide the array size of the captures needed for - * nrex::match() to work. The size is actually the number of capture - * groups + one for the matching of the entire pattern. This can be - * capped using the extra argument given in nrex::compile() - * (default 10). - * - * \return The number of captures - */ - int capture_size() const; - - /*! - * \brief Compiles the provided regex pattern - * - * This automatically removes the existing compiled regex if already - * present. - * - * If the NREX_THROW_ERROR was defined it would automatically throw a - * runtime error nrex_compile_error if it encounters a problem when - * parsing the pattern. - * - * \param pattern The regex pattern - * \param captures The maximum number of capture groups to allow. Any - * extra would be converted to non-capturing groups. - * If negative, no limit would be imposed. Defaults - * to 9. - * \return True if the pattern was succesfully compiled - */ - bool compile(const nrex_char* pattern, int captures = 9); - - /*! - * \brief Uses the pattern to search through the provided string - * \param str The text to search through. It only needs to be - * null terminated if the end point is not provided. - * This also determines the starting anchor. - * \param captures The array of results to store the capture results. - * The size of that array needs to be the same as the - * size given in nrex::capture_size(). As it matches - * the function fills the array with the results. 0 is - * the result for the entire pattern, 1 and above - * corresponds to the regex capture group if present. - * \param offset The starting point of the search. This does not move - * the starting anchor. Defaults to 0. - * \param end The end point of the search. This also determines - * the ending anchor. If a number less than the offset - * is provided, the search would be done until null - * termination. Defaults to -1. - * \return True if a match was found. False otherwise. - */ - bool match(const nrex_char* str, nrex_result* captures, int offset = 0, int end = -1) const; -}; - -#ifdef NREX_THROW_ERROR - -#include <stdexcept> - -class nrex_compile_error : std::runtime_error -{ - public: - nrex_compile_error(const char* message) - : std::runtime_error(message) - { - } - - ~nrex_compile_error() throw() - { - } -}; - -#endif - -#endif // NREX_HPP diff --git a/drivers/nrex/nrex_config.h b/drivers/nrex/nrex_config.h deleted file mode 100644 index 540f34f8b4..0000000000 --- a/drivers/nrex/nrex_config.h +++ /dev/null @@ -1,12 +0,0 @@ -// Godot-specific configuration -// To use this, replace nrex_config.h - -#include "core/os/memory.h" - -#define NREX_UNICODE -//#define NREX_THROW_ERROR - -#define NREX_NEW(X) memnew(X) -#define NREX_NEW_ARRAY(X, N) memnew_arr(X, N) -#define NREX_DELETE(X) memdelete(X) -#define NREX_DELETE_ARRAY(X) memdelete_arr(X) diff --git a/drivers/nrex/regex.cpp b/drivers/nrex/regex.cpp deleted file mode 100644 index 7bf14d14ad..0000000000 --- a/drivers/nrex/regex.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/*************************************************************************/ -/* regex.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* http://www.godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ -#include "regex.h" -#include "nrex.hpp" -#include "core/os/memory.h" - -void RegEx::_bind_methods() { - - ObjectTypeDB::bind_method(_MD("compile","pattern", "capture"),&RegEx::compile, DEFVAL(9)); - ObjectTypeDB::bind_method(_MD("find","text","start","end"),&RegEx::find, DEFVAL(0), DEFVAL(-1)); - ObjectTypeDB::bind_method(_MD("clear"),&RegEx::clear); - ObjectTypeDB::bind_method(_MD("is_valid"),&RegEx::is_valid); - ObjectTypeDB::bind_method(_MD("get_capture_count"),&RegEx::get_capture_count); - ObjectTypeDB::bind_method(_MD("get_capture","capture"),&RegEx::get_capture); - ObjectTypeDB::bind_method(_MD("get_capture_start","capture"),&RegEx::get_capture_start); - ObjectTypeDB::bind_method(_MD("get_captures"),&RegEx::_bind_get_captures); - -}; - -StringArray RegEx::_bind_get_captures() const { - - StringArray ret; - int count = get_capture_count(); - for (int i=0; i<count; i++) { - - String c = get_capture(i); - ret.push_back(c); - }; - - return ret; - -}; - -void RegEx::clear() { - - text.clear(); - captures.clear(); - exp.reset(); - -}; - -bool RegEx::is_valid() const { - - return exp.valid(); - -}; - -int RegEx::get_capture_count() const { - - ERR_FAIL_COND_V( !exp.valid(), 0 ); - - return exp.capture_size(); -} - -String RegEx::get_capture(int capture) const { - - ERR_FAIL_COND_V( get_capture_count() <= capture, String() ); - - return text.substr(captures[capture].start, captures[capture].length); - -} - -int RegEx::get_capture_start(int capture) const { - - ERR_FAIL_COND_V( get_capture_count() <= capture, -1 ); - - return captures[capture].start; - -} - -Error RegEx::compile(const String& p_pattern, int capture) { - - clear(); - - exp.compile(p_pattern.c_str(), capture); - - ERR_FAIL_COND_V( !exp.valid(), FAILED ); - - captures.resize(exp.capture_size()); - - return OK; - -}; - -int RegEx::find(const String& p_text, int p_start, int p_end) const { - - ERR_FAIL_COND_V( !exp.valid(), -1 ); - ERR_FAIL_COND_V( p_text.length() < p_start, -1 ); - ERR_FAIL_COND_V( p_text.length() < p_end, -1 ); - - bool res = exp.match(p_text.c_str(), &captures[0], p_start, p_end); - - if (res) { - text = p_text; - return captures[0].start; - } - text.clear(); - return -1; - -}; - -RegEx::RegEx(const String& p_pattern) { - - compile(p_pattern); - -}; - -RegEx::RegEx() { - -}; - -RegEx::~RegEx() { - - clear(); - -}; diff --git a/drivers/register_driver_types.cpp b/drivers/register_driver_types.cpp index 7486d4f217..e0f35e81c4 100644 --- a/drivers/register_driver_types.cpp +++ b/drivers/register_driver_types.cpp @@ -40,8 +40,6 @@ #include "platform/windows/export/export.h" #endif -#include "drivers/nrex/regex.h" - static ImageLoaderPNG *image_loader_png=NULL; static ResourceSaverPNG *resource_saver_png=NULL; @@ -53,8 +51,6 @@ void register_core_driver_types() { resource_saver_png = memnew( ResourceSaverPNG ); ResourceSaver::add_resource_format_saver(resource_saver_png); - - ObjectTypeDB::register_type<RegEx>(); } void unregister_core_driver_types() { diff --git a/methods.py b/methods.py index 477fe4f12f..bd5409e3d4 100755 --- a/methods.py +++ b/methods.py @@ -1516,11 +1516,6 @@ def detect_visual_c_compiler_version(tools_env): return vc_chosen_compiler_str -def msvc_is_detected() : - # looks for VisualStudio env variable - # or for Visual C++ Build Tools (which is a standalone MSVC) - return os.getenv("VSINSTALLDIR") or os.getenv("VS100COMNTOOLS") or os.getenv("VS110COMNTOOLS") or os.getenv("VS120COMNTOOLS") or os.getenv("VS140COMNTOOLS"); - def precious_program(env, program, sources, **args): program = env.ProgramOriginal(program, sources, **args) diff --git a/modules/openssl/SCsub b/modules/openssl/SCsub index 3cc6f21bd2..2327cf483c 100644 --- a/modules/openssl/SCsub +++ b/modules/openssl/SCsub @@ -671,7 +671,7 @@ if (env["openssl"] != "system"): # builtin # Workaround for compilation error with GCC/Clang when -Werror is too greedy (GH-4517) import os import methods - if not (os.name=="nt" and methods.msvc_is_detected()): # not Windows and not MSVC + if not (os.name=="nt" and os.getenv("VCINSTALLDIR")): # not Windows and not MSVC env_openssl.Append(CFLAGS = ["-Wno-error=implicit-function-declaration"]) diff --git a/drivers/nrex/SCsub b/modules/regex/SCsub index ee39fd2631..0882406761 100644 --- a/drivers/nrex/SCsub +++ b/modules/regex/SCsub @@ -2,6 +2,6 @@ Import('env') -env.add_source_files(env.drivers_sources, "*.cpp") +env.add_source_files(env.modules_sources, "*.cpp") Export('env') diff --git a/modules/regex/config.py b/modules/regex/config.py new file mode 100644 index 0000000000..667b5d8ba6 --- /dev/null +++ b/modules/regex/config.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +def can_build(platform): + return True + +def configure(env): + pass + diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp new file mode 100644 index 0000000000..a0f4b4934c --- /dev/null +++ b/modules/regex/regex.cpp @@ -0,0 +1,1502 @@ +/*************************************************************************/ +/* regex.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "regex.h" +#include <wctype.h> +#include <wchar.h> + +static int RegEx_hex2int(const CharType c) +{ + if ('0' <= c && c <= '9') + return int(c - '0'); + else if ('a' <= c && c <= 'f') + return int(c - 'a') + 10; + else if ('A' <= c && c <= 'F') + return int(c - 'A') + 10; + return -1; +} + +struct RegExSearch { + + Ref<RegExMatch> match; + const CharType* str; + int end; + int eof; + + // For standard quantifier behaviour, test_parent is used to check the + // rest of the pattern. If the pattern matches, to prevent the parent + // from testing again, the complete flag is used as a shortcut out. + bool complete; + + // With lookahead, the position needs to rewind to its starting position + // when test_parent is used. Due to functional programming, this state + // has to be kept as a parameter. + Vector<int> lookahead_pos; + + CharType at(int p_pos) { + return str[p_pos]; + } + + RegExSearch(Ref<RegExMatch>& p_match, int p_end, int p_lookahead) : match(p_match) { + + str = p_match->string.c_str(); + end = p_end; + eof = p_match->string.length(); + complete = false; + lookahead_pos.resize(p_lookahead); + } + +}; + +struct RegExNode { + + RegExNode* next; + RegExNode* previous; + RegExNode* parent; + bool quantifiable; + int length; + + RegExNode() { + + next = NULL; + previous = NULL; + parent = NULL; + quantifiable = false; + length = -1; + } + + virtual ~RegExNode() { + + if (next) + memdelete(next); + } + + virtual int test(RegExSearch& s, int pos) const { + + return next ? next->test(s, pos) : -1; + } + + virtual int test_parent(RegExSearch& s, int pos) const { + + if (next) + pos = next->test(s, pos); + + if (pos >= 0) { + s.complete = true; + if (parent) + pos = parent->test_parent(s, pos); + } + + if (pos < 0) + s.complete = false; + + return pos; + } + + void increment_length(int amount, bool subtract = false) { + + if (amount >= 0 && length >= 0) { + if (!subtract) + length += amount; + else + length -= amount; + } else { + length = -1; + } + + if (parent) + parent->increment_length(amount, subtract); + + } + +}; + +struct RegExNodeChar : public RegExNode { + + CharType ch; + + RegExNodeChar(CharType p_char) { + + length = 1; + quantifiable = true; + ch = p_char; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (s.end <= pos || 0 > pos || s.at(pos) != ch) + return -1; + + return next ? next->test(s, pos + 1) : pos + 1; + } + + static CharType parse_escape(const CharType*& c) { + + int point = 0; + switch (c[1]) { + case 'x': + for (int i = 2; i <= 3; ++i) { + int res = RegEx_hex2int(c[i]); + if (res == -1) + return '\0'; + point = (point << 4) + res; + } + c = &c[3]; + return CharType(point); + case 'u': + for (int i = 2; i <= 5; ++i) { + int res = RegEx_hex2int(c[i]); + if (res == -1) + return '\0'; + point = (point << 4) + res; + } + c = &c[5]; + return CharType(point); + case '0': ++c; return '\0'; + case 'a': ++c; return '\a'; + case 'e': ++c; return '\e'; + case 'f': ++c; return '\f'; + case 'n': ++c; return '\n'; + case 'r': ++c; return '\r'; + case 't': ++c; return '\t'; + case 'v': ++c; return '\v'; + case 'b': ++c; return '\b'; + default: break; + } + return (++c)[0]; + } +}; + +struct RegExNodeRange : public RegExNode { + + CharType start; + CharType end; + + RegExNodeRange(CharType p_start, CharType p_end) { + + length = 1; + quantifiable = true; + start = p_start; + end = p_end; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (s.end <= pos || 0 > pos) + return -1; + + CharType c = s.at(pos); + if (c < start || end < c) + return -1; + + return next ? next->test(s, pos + 1) : pos + 1; + } +}; + +struct RegExNodeShorthand : public RegExNode { + + CharType repr; + + RegExNodeShorthand(CharType p_repr) { + + length = 1; + quantifiable = true; + repr = p_repr; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (s.end <= pos || 0 > pos) + return -1; + + bool found = false; + bool invert = false; + CharType c = s.at(pos); + switch (repr) { + case '.': + found = true; + break; + case 'W': + invert = true; + case 'w': + found = (c == '_' || iswalnum(c) != 0); + break; + case 'D': + invert = true; + case 'd': + found = ('0' <= c && c <= '9'); + break; + case 'S': + invert = true; + case 's': + found = (iswspace(c) != 0); + break; + default: + break; + } + + if (found == invert) + return -1; + + return next ? next->test(s, pos + 1) : pos + 1; + } +}; + +struct RegExNodeClass : public RegExNode { + + enum Type { + Type_none, + Type_alnum, + Type_alpha, + Type_ascii, + Type_blank, + Type_cntrl, + Type_digit, + Type_graph, + Type_lower, + Type_print, + Type_punct, + Type_space, + Type_upper, + Type_xdigit, + Type_word + }; + + Type type; + + bool test_class(CharType c) const { + + static Vector<CharType> REGEX_NODE_SPACE = String(" \t\r\n\f"); + static Vector<CharType> REGEX_NODE_PUNCT = String("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"); + + switch (type) { + case Type_alnum: + if ('0' <= c && c <= '9') return true; + if ('a' <= c && c <= 'z') return true; + if ('A' <= c && c <= 'Z') return true; + return false; + case Type_alpha: + if ('a' <= c && c <= 'z') return true; + if ('A' <= c && c <= 'Z') return true; + return false; + case Type_ascii: + return (0x00 <= c && c <= 0x7F); + case Type_blank: + return (c == ' ' || c == '\t'); + case Type_cntrl: + return ((0x00 <= c && c <= 0x1F) || c == 0x7F); + case Type_digit: + return ('0' <= c && c <= '9'); + case Type_graph: + return (0x20 < c && c < 0x7F); + case Type_lower: + return ('a' <= c && c <= 'z'); + case Type_print: + return (0x1F < c && c < 0x1F); + case Type_punct: + return (REGEX_NODE_PUNCT.find(c) >= 0); + case Type_space: + return (REGEX_NODE_SPACE.find(c) >= 0); + case Type_upper: + return ('A' <= c && c <= 'Z'); + case Type_xdigit: + if ('0' <= c && c <= '9') return true; + if ('a' <= c && c <= 'f') return true; + if ('A' <= c && c <= 'F') return true; + return false; + case Type_word: + if ('0' <= c && c <= '9') return true; + if ('a' <= c && c <= 'z') return true; + if ('A' <= c && c <= 'Z') return true; + return (c == '_'); + default: + return false; + } + return false; + } + + RegExNodeClass(Type p_type) { + + length = 1; + quantifiable = true; + type = p_type; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (s.end <= pos || 0 > pos) + return -1; + + if (!test_class(s.at(pos))) + return -1; + + return next ? next->test(s, pos + 1) : pos + 1; + } + +#define REGEX_CMP_CLASS(POS, NAME) if (cmp_class(POS, #NAME)) return Type_ ## NAME + + static Type parse_type(const CharType*& p_pos) { + + REGEX_CMP_CLASS(p_pos, alnum); + REGEX_CMP_CLASS(p_pos, alpha); + REGEX_CMP_CLASS(p_pos, ascii); + REGEX_CMP_CLASS(p_pos, blank); + REGEX_CMP_CLASS(p_pos, cntrl); + REGEX_CMP_CLASS(p_pos, digit); + REGEX_CMP_CLASS(p_pos, graph); + REGEX_CMP_CLASS(p_pos, lower); + REGEX_CMP_CLASS(p_pos, print); + REGEX_CMP_CLASS(p_pos, punct); + REGEX_CMP_CLASS(p_pos, space); + REGEX_CMP_CLASS(p_pos, upper); + REGEX_CMP_CLASS(p_pos, xdigit); + REGEX_CMP_CLASS(p_pos, word); + return Type_none; + } + + static bool cmp_class(const CharType*& p_pos, const char* p_text) { + + unsigned int i = 0; + for (i = 0; p_text[i] != '\0'; ++i) + if (p_pos[i] != p_text[i]) + return false; + + if (p_pos[i++] != ':' || p_pos[i] != ']') + return false; + + p_pos = &p_pos[i]; + return true; + } +}; + +struct RegExNodeAnchorStart : public RegExNode { + + RegExNodeAnchorStart() { + + length = 0; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (pos != 0) + return -1; + + return next ? next->test(s, pos) : pos; + } +}; + +struct RegExNodeAnchorEnd : public RegExNode { + + RegExNodeAnchorEnd() { + + length = 0; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (pos != s.eof) + return -1; + + return next ? next->test(s, pos) : pos; + } +}; + +struct RegExNodeWordBoundary : public RegExNode { + + bool inverse; + + RegExNodeWordBoundary(bool p_inverse) { + + length = 0; + inverse = p_inverse; + } + + virtual int test(RegExSearch& s, int pos) const { + + bool left = false; + bool right = false; + + if (pos != 0) { + CharType c = s.at(pos - 1); + if (c == '_' || iswalnum(c)) + left = true; + } + + if (pos != s.eof) { + CharType c = s.at(pos); + if (c == '_' || iswalnum(c)) + right = true; + } + + if ((left == right) != inverse) + return -1; + + return next ? next->test(s, pos) : pos; + } +}; + +struct RegExNodeQuantifier : public RegExNode { + + int min; + int max; + bool greedy; + RegExNode* child; + + RegExNodeQuantifier(int p_min, int p_max) { + + min = p_min; + max = p_max; + greedy = true; + child = NULL; + } + + ~RegExNodeQuantifier() { + + if (child) + memdelete(child); + } + + virtual int test(RegExSearch& s, int pos) const { + + return test_step(s, pos, 0, pos); + } + + virtual int test_parent(RegExSearch& s, int pos) const { + + s.complete = false; + return pos; + } + + int test_step(RegExSearch& s, int pos, int level, int start) const { + + if (pos > s.end) + return -1; + + if (!greedy && level > min) { + int res = next ? next->test(s, pos) : pos; + if (s.complete) + return res; + + if (res >= 0 && parent->test_parent(s, res) >= 0) + return res; + } + + if (max >= 0 && level > max) + return -1; + + int res = pos; + if (level >= 1) { + if (level > min + 1 && pos == start) + return -1; + + res = child->test(s, pos); + if (s.complete) + return res; + } + + if (res >= 0) { + + int res_step = test_step(s, res, level + 1, start); + if (res_step >= 0) + return res_step; + + if (greedy && level >= min) { + if (next) + res = next->test(s, res); + if (s.complete) + return res; + + if (res >= 0 && parent->test_parent(s, res) >= 0) + return res; + } + } + return -1; + } +}; + +struct RegExNodeBackReference : public RegExNode { + + int id; + + RegExNodeBackReference(int p_id) { + + length = -1; + quantifiable = true; + id = p_id; + } + + virtual int test(RegExSearch& s, int pos) const { + + RegExMatch::Group& ref = s.match->captures[id]; + for (int i = 0; i < ref.length; ++i) { + + if (pos + i >= s.end) + return -1; + + if (s.at(ref.start + i) != s.at(pos + i)) + return -1; + } + return next ? next->test(s, pos + ref.length) : pos + ref.length; + } +}; + + +struct RegExNodeGroup : public RegExNode { + + bool inverse; + bool reset_pos; + Vector<RegExNode*> childset; + RegExNode* back; + + RegExNodeGroup() { + + length = 0; + quantifiable = true; + inverse = false; + reset_pos = false; + back = NULL; + } + + virtual ~RegExNodeGroup() { + + for (int i = 0; i < childset.size(); ++i) + memdelete(childset[i]); + } + + virtual int test(RegExSearch& s, int pos) const { + + for (int i = 0; i < childset.size(); ++i) { + + s.complete = false; + + int res = childset[i]->test(s, pos); + + if (s.complete) + return res; + + if (inverse) { + if (res < 0) + res = pos + 1; + else + return -1; + + if (i + 1 < childset.size()) + continue; + } + + if (res >= 0) { + if (reset_pos) + res = pos; + return next ? next->test(s, res) : res; + } + } + return -1; + } + + void add_child(RegExNode* node) { + + node->parent = this; + node->previous = back; + + if (back) + back->next = node; + else + childset.push_back(node); + + increment_length(node->length); + + back = node; + } + + void add_childset() { + + if (childset.size() > 0) + length = -1; + back = NULL; + } + + RegExNode* swap_back(RegExNode* node) { + + RegExNode* old = back; + + if (old) { + if (!old->previous) + childset.remove(childset.size() - 1); + back = old->previous; + increment_length(old->length, true); + } + + add_child(node); + + return old; + } +}; + +struct RegExNodeCapturing : public RegExNodeGroup { + + int id; + + RegExNodeCapturing(int p_id = 0) { + + id = p_id; + } + + virtual int test(RegExSearch& s, int pos) const { + + RegExMatch::Group& ref = s.match->captures[id]; + int old_start = ref.start; + ref.start = pos; + + int res = RegExNodeGroup::test(s, pos); + + if (res >= 0) { + if (!s.complete) + ref.length = res - pos; + } else { + ref.start = old_start; + } + + return res; + } + + virtual int test_parent(RegExSearch& s, int pos) const { + + RegExMatch::Group& ref = s.match->captures[id]; + ref.length = pos - ref.start; + return RegExNode::test_parent(s, pos); + } + + static Variant parse_name(const CharType*& c, bool p_allow_numeric) { + + if (c[1] == '0') { + return -1; + } else if ('1' <= c[1] && c[1] <= '9') { + if (!p_allow_numeric) + return -1; + int res = (++c)[0] - '0'; + while ('0' <= c[1] && c[1] <= '9') + res = res * 10 + int((++c)[0] - '0'); + if ((++c)[0] != '>') + return -1; + return res; + } else if (iswalnum(c[1])) { + String res(++c, 1); + while (iswalnum(c[1])) + res += String(++c, 1); + if ((++c)[0] != '>') + return -1; + return res; + } + return -1; + } +}; + +struct RegExNodeLookAhead : public RegExNodeGroup { + + int id; + + RegExNodeLookAhead(bool p_inverse, int p_id = 0) { + + quantifiable = false; + inverse = p_inverse; + reset_pos = true; + id = p_id; + } + + virtual int test(RegExSearch& s, int pos) const { + + s.lookahead_pos[id] = pos; + return RegExNodeGroup::test(s, pos); + } + + virtual int test_parent(RegExSearch& s, int pos) const { + + return RegExNode::test_parent(s, s.lookahead_pos[id]); + } +}; + +struct RegExNodeLookBehind : public RegExNodeGroup { + + RegExNodeLookBehind(bool p_inverse, int p_id = 0) { + + quantifiable = false; + inverse = p_inverse; + reset_pos = true; + } + + virtual int test(RegExSearch& s, int pos) const { + + if (pos < length) + return -1; + return RegExNodeGroup::test(s, pos - length); + } +}; + +struct RegExNodeBracket : public RegExNode { + + bool inverse; + Vector<RegExNode*> children; + + RegExNodeBracket() { + + length = 1; + quantifiable = true; + inverse = false; + } + + virtual ~RegExNodeBracket() { + + for (int i = 0; i < children.size(); ++i) + memdelete(children[i]); + } + + virtual int test(RegExSearch& s, int pos) const { + + for (int i = 0; i < children.size(); ++i) { + + int res = children[i]->test(s, pos); + + if (inverse) { + if (res < 0) + res = pos + 1; + else + return -1; + + if (i + 1 < children.size()) + continue; + } + + if (res >= 0) + return next ? next->test(s, res) : res; + } + return -1; + } + + void add_child(RegExNode* node) { + + node->parent = this; + children.push_back(node); + } + + void pop_back() { + + memdelete(children[children.size() - 1]); + children.remove(children.size() - 1); + } +}; + +#define REGEX_EXPAND_FAIL(MSG)\ +{\ + ERR_PRINT(MSG);\ + return String();\ +} + +String RegExMatch::expand(const String& p_template) const { + + String res; + for (const CharType* c = p_template.c_str(); *c != '\0'; ++c) { + if (c[0] == '\\') { + if (('1' <= c[1] && c[1] <= '9') || (c[1] == 'g' && c[2] == '{')) { + + int ref = 0; + bool unclosed = false; + + if (c[1] == 'g') { + unclosed = true; + c = &c[2]; + } + + while ('0' <= c[1] && c[1] <= '9') { + ref = ref * 10 + int(c[1] - '0'); + ++c; + } + + if (unclosed) { + if (c[1] != '}') + REGEX_EXPAND_FAIL("unclosed backreference '{'"); + ++c; + } + + res += get_string(ref); + + } else if (c[1] =='g' && c[2] == '<') { + + const CharType* d = &c[2]; + + Variant name = RegExNodeCapturing::parse_name(d, true); + if (name == Variant(-1)) + REGEX_EXPAND_FAIL("unrecognised character for group name"); + + c = d; + + res += get_string(name); + + } else { + + const CharType* d = c; + CharType ch = RegExNodeChar::parse_escape(d); + if (c == d) + REGEX_EXPAND_FAIL("invalid escape token"); + res += String(&ch, 1); + c = d; + } + } else { + res += String(c, 1); + } + } + return res; +} + +int RegExMatch::get_group_count() const { + + int count = 0; + for (int i = 1; i < captures.size(); ++i) + if (captures[i].name.get_type() == Variant::INT) + ++count; + return count; +} + +Array RegExMatch::get_group_array() const { + + Array res; + for (int i = 1; i < captures.size(); ++i) { + const RegExMatch::Group& capture = captures[i]; + if (capture.name.get_type() != Variant::INT) + continue; + + if (capture.start >= 0) + res.push_back(string.substr(capture.start, capture.length)); + else + res.push_back(String()); + } + return res; +} + +Array RegExMatch::get_names() const { + + Array res; + for (int i = 1; i < captures.size(); ++i) + if (captures[i].name.get_type() == Variant::STRING) + res.push_back(captures[i].name); + return res; +} + +Dictionary RegExMatch::get_name_dict() const { + + Dictionary res; + for (int i = 1; i < captures.size(); ++i) { + const RegExMatch::Group& capture = captures[i]; + if (capture.name.get_type() != Variant::STRING) + continue; + + if (capture.start >= 0) + res[capture.name] = string.substr(capture.start, capture.length); + else + res[capture.name] = String(); + } + return res; +} + +String RegExMatch::get_string(const Variant& p_name) const { + + for (int i = 0; i < captures.size(); ++i) { + + const RegExMatch::Group& capture = captures[i]; + + if (capture.name != p_name) + continue; + + if (capture.start == -1) + return String(); + + return string.substr(capture.start, capture.length); + } + return String(); +} + +int RegExMatch::get_start(const Variant& p_name) const { + + for (int i = 0; i < captures.size(); ++i) + if (captures[i].name == p_name) + return captures[i].start; + return -1; +} + +int RegExMatch::get_end(const Variant& p_name) const { + + for (int i = 0; i < captures.size(); ++i) + if (captures[i].name == p_name) + return captures[i].start + captures[i].length; + return -1; +} + +RegExMatch::RegExMatch() { + +} + +static bool RegEx_is_shorthand(CharType ch) { + + switch (ch) { + case 'w': + case 'W': + case 'd': + case 'D': + case 's': + case 'S': + return true; + default: + break; + } + return false; +} + +#define REGEX_COMPILE_FAIL(MSG)\ +{\ + ERR_PRINT(MSG);\ + clear();\ + return FAILED;\ +} + +Error RegEx::compile(const String& p_pattern) { + + ERR_FAIL_COND_V(p_pattern.length() == 0, FAILED); + + if (pattern == p_pattern && root) + return OK; + + clear(); + pattern = p_pattern; + group_names.push_back(0); + RegExNodeGroup* root_group = memnew(RegExNodeCapturing(0)); + root = root_group; + Vector<RegExNodeGroup*> stack; + stack.push_back(root_group); + int lookahead_level = 0; + int numeric_groups = 0; + const int numeric_max = 9; + + for (const CharType* c = p_pattern.c_str(); *c != '\0'; ++c) { + + switch (c[0]) { + case '(': + if (c[1] == '?') { + + RegExNodeGroup* group = NULL; + switch (c[2]) { + case ':': + c = &c[2]; + group = memnew(RegExNodeGroup()); + break; + case '!': + case '=': + group = memnew(RegExNodeLookAhead((c[2] == '!'), lookahead_level++)); + if (lookahead_depth < lookahead_level) + lookahead_depth = lookahead_level; + c = &c[2]; + break; + case '<': + if (c[3] == '!' || c[3] == '=') { + group = memnew(RegExNodeLookBehind((c[3] == '!'), lookahead_level++)); + c = &c[3]; + } + break; + case 'P': + if (c[3] == '<') { + const CharType* d = &c[3]; + Variant name = RegExNodeCapturing::parse_name(d, false); + if (name == Variant(-1)) + REGEX_COMPILE_FAIL("unrecognised character for group name"); + group = memnew(RegExNodeCapturing(group_names.size())); + group_names.push_back(name); + c = d; + } + default: + break; + } + if (!group) + REGEX_COMPILE_FAIL("unrecognised qualifier for group"); + stack[0]->add_child(group); + stack.insert(0, group); + + } else if (numeric_groups < numeric_max) { + + RegExNodeCapturing* group = memnew(RegExNodeCapturing(group_names.size())); + group_names.push_back(++numeric_groups); + stack[0]->add_child(group); + stack.insert(0, group); + + } else { + + RegExNodeGroup* group = memnew(RegExNodeGroup()); + stack[0]->add_child(group); + stack.insert(0, group); + } + break; + case ')': + if (stack.size() == 1) + REGEX_COMPILE_FAIL("unexpected ')'"); + stack.remove(0); + break; + case '\\': + if (('1' <= c[1] && c[1] <= '9') || (c[1] == 'g' && c[2] == '{')) { + + int ref = 0; + bool unclosed = false; + + if (c[1] == 'g') { + unclosed = true; + c = &c[2]; + } + + while ('0' <= c[1] && c[1] <= '9') { + ref = ref * 10 + int(c[1] - '0'); + ++c; + } + + if (unclosed) { + if (c[1] != '}') + REGEX_COMPILE_FAIL("unclosed backreference '{'"); + ++c; + } + + if (ref > numeric_groups || ref <= 0) + REGEX_COMPILE_FAIL("backreference not found"); + + for (int i = 0; i < stack.size(); ++i) + if (dynamic_cast<RegExNodeLookBehind*>(stack[i])) + REGEX_COMPILE_FAIL("backreferences inside lookbehind not supported"); + + for (int i = 0; i < group_names.size(); ++i) { + if (group_names[i].get_type() == Variant::INT && int(group_names[i]) == ref) { + ref = group_names[i]; + break; + } + } + + stack[0]->add_child(memnew(RegExNodeBackReference(ref))); + + } if (c[1] =='g' && c[2] == '<') { + + const CharType* d = &c[2]; + + Variant name = RegExNodeCapturing::parse_name(d, true); + if (name == Variant(-1)) + REGEX_COMPILE_FAIL("unrecognised character for group name"); + + c = d; + + for (int i = 0; i < stack.size(); ++i) + if (dynamic_cast<RegExNodeLookBehind*>(stack[i])) + REGEX_COMPILE_FAIL("backreferences inside lookbehind not supported"); + + int ref = -1; + + for (int i = 0; i < group_names.size(); ++i) { + if (group_names[i].get_type() == Variant::INT && int(group_names[i]) == ref) { + ref = group_names[i]; + break; + } + } + + if (ref == -1) + REGEX_COMPILE_FAIL("backreference not found"); + + stack[0]->add_child(memnew(RegExNodeBackReference(ref))); + + } else if (c[1] == 'b' || c[1] == 'B') { + + stack[0]->add_child(memnew(RegExNodeWordBoundary(*(++c) == 'B'))); + + } else if (RegEx_is_shorthand(c[1])) { + + stack[0]->add_child(memnew(RegExNodeShorthand(*(++c)))); + + } else { + + const CharType* d = c; + CharType ch = RegExNodeChar::parse_escape(d); + if (c == d) + REGEX_COMPILE_FAIL("invalid escape token"); + stack[0]->add_child(memnew(RegExNodeChar(ch))); + c = d; + + } + break; + case '[': + { + RegExNodeBracket* bracket = memnew(RegExNodeBracket()); + stack[0]->add_child(bracket); + if (c[1] == '^') { + bracket->inverse = true; + ++c; + } + bool first_child = true; + CharType previous_child; + bool previous_child_single = false; + while (true) { + ++c; + if (!first_child && c[0] == ']') { + + break; + + } else if (c[0] == '\0') { + + REGEX_COMPILE_FAIL("unclosed bracket expression '['"); + + } else if (c[0] == '\\') { + + if (RegEx_is_shorthand(c[1])) { + bracket->add_child(memnew(RegExNodeShorthand(*(++c)))); + } else { + const CharType* d = c; + CharType ch = RegExNodeChar::parse_escape(d); + if (c == d) + REGEX_COMPILE_FAIL("invalid escape token"); + bracket->add_child(memnew(RegExNodeChar(ch))); + c = d; + previous_child = ch; + previous_child_single = true; + } + + } else if (c[0] == ']' && c[1] == ':') { + + const CharType* d = &c[2]; + RegExNodeClass::Type type = RegExNodeClass::parse_type(d); + if (type != RegExNodeClass::Type_none) { + + c = d; + previous_child_single = false; + + } else { + + bracket->add_child(memnew(RegExNodeChar('['))); + previous_child = '['; + previous_child_single = true; + } + } else if (previous_child_single && c[0] == '-') { + + if (c[1] != '\0' && c[1] != ']') { + + CharType next; + + if (c[1] == '\\') { + const CharType* d = ++c; + next = RegExNodeChar::parse_escape(d); + if (c == d) + REGEX_COMPILE_FAIL("invalid escape token"); + } else { + next = *(++c); + } + + if (next < previous_child) + REGEX_COMPILE_FAIL("text range out of order"); + + bracket->pop_back(); + bracket->add_child(memnew(RegExNodeRange(previous_child, next))); + previous_child_single = false; + } else { + + bracket->add_child(memnew(RegExNodeChar('-'))); + previous_child = '-'; + previous_child_single = true; + } + } else { + + bracket->add_child(memnew(RegExNodeChar(c[0]))); + previous_child = c[0]; + previous_child_single = true; + } + first_child = false; + } + } + break; + case '|': + for (int i = 0; i < stack.size(); ++i) + if (dynamic_cast<RegExNodeLookBehind*>(stack[i])) + REGEX_COMPILE_FAIL("alternations inside lookbehind not supported"); + stack[0]->add_childset(); + break; + case '^': + stack[0]->add_child(memnew(RegExNodeAnchorStart())); + break; + case '$': + stack[0]->add_child(memnew(RegExNodeAnchorEnd())); + break; + case '.': + stack[0]->add_child(memnew(RegExNodeShorthand('.'))); + break; + case '?': + case '*': + case '+': + case '{': + { + int min_val = 0; + int max_val = -1; + bool valid = true; + const CharType* d = c; + bool max_set = true; + switch (c[0]) { + case '?': + min_val = 0; + max_val = 1; + break; + case '*': + min_val = 0; + max_val = -1; + break; + case '+': + min_val = 1; + max_val = -1; + break; + case '{': + max_set = false; + while (valid) { + ++d; + if (d[0] == '}') { + break; + } else if (d[0] == ',') { + max_set = true; + } else if ('0' <= d[0] && d[0] <= '9') { + if (max_set) { + if (max_val < 0) + max_val = int(d[0] - '0'); + else + max_val = max_val * 10 + int(d[0] - '0'); + } else { + min_val = min_val * 10 + int(d[0] - '0'); + } + } else { + valid = false; + } + } + break; + default: + break; + } + + if (!max_set) + max_val = min_val; + + if (valid) { + + c = d; + + if (stack[0]->back == NULL || !stack[0]->back->quantifiable) + REGEX_COMPILE_FAIL("element not quantifiable"); + + if (min_val != max_val) + for (int i = 0; i < stack.size(); ++i) + if (dynamic_cast<RegExNodeLookBehind*>(stack[i])) + REGEX_COMPILE_FAIL("variable length quantifiers inside lookbehind not supported"); + + RegExNodeQuantifier* quant = memnew(RegExNodeQuantifier(min_val, max_val)); + quant->child = stack[0]->swap_back(quant); + quant->child->previous = NULL; + quant->child->parent = quant; + + if (min_val == max_val && quant->child->length >= 0) + quant->length = max_val * quant->child->length; + + if (c[1] == '?') { + quant->greedy = false; + ++c; + } + break; + } + } + default: + stack[0]->add_child(memnew(RegExNodeChar(c[0]))); + break; + } + } + if (stack.size() > 1) + REGEX_COMPILE_FAIL("unclosed group '('"); + return OK; +} + +Ref<RegExMatch> RegEx::search(const String& p_text, int p_start, int p_end) const { + + ERR_FAIL_COND_V(!is_valid(), NULL); + ERR_FAIL_COND_V(p_start < 0, NULL); + ERR_FAIL_COND_V(p_start >= p_text.length(), NULL); + ERR_FAIL_COND_V(p_end > p_text.length(), NULL); + ERR_FAIL_COND_V(p_end != -1 && p_end < p_start, NULL); + + Ref<RegExMatch> res = memnew(RegExMatch()); + + for (int i = 0; i < group_names.size(); ++i) { + RegExMatch::Group group; + group.name = group_names[i]; + res->captures.push_back(group); + } + + res->string = p_text; + + if (p_end == -1) + p_end = p_text.length(); + + RegExSearch s(res, p_end, lookahead_depth); + + for (int i = p_start; i <= s.end; ++i) { + for (int c = 0; c < group_names.size(); ++c) { + res->captures[c].start = -1; + res->captures[c].length = 0; + } + if (root->test(s, i) >= 0) + break; + } + + if (res->captures[0].start >= 0) + return res; + return NULL; +} + +String RegEx::sub(const String& p_text, const String& p_replacement, bool p_all, int p_start, int p_end) const { + + ERR_FAIL_COND_V(!is_valid(), p_text); + ERR_FAIL_COND_V(p_start < 0, p_text); + ERR_FAIL_COND_V(p_start >= p_text.length(), p_text); + ERR_FAIL_COND_V(p_end > p_text.length(), p_text); + ERR_FAIL_COND_V(p_end != -1 && p_end < p_start, p_text); + + String text = p_text; + int start = p_start; + + if (p_end == -1) + p_end = p_text.length(); + + while (start < text.length() && (p_all || start == p_start)) { + + Ref<RegExMatch> m = search(text, start, p_end); + + RegExMatch::Group& s = m->captures[0]; + + if (s.start < 0) + break; + + String res = text.substr(0, s.start) + m->expand(p_replacement); + + start = res.length(); + + if (s.length == 0) + ++start; + + int sub_end = s.start + s.length; + if (sub_end < text.length()) + res += text.substr(sub_end, text.length() - sub_end); + + p_end += res.length() - text.length(); + + text = res; + } + return text; +} + +void RegEx::clear() { + + if (root) + memdelete(root); + + root = NULL; + group_names.clear(); + lookahead_depth = 0; +} + +bool RegEx::is_valid() const { + + return (root != NULL); +} + +String RegEx::get_pattern() const { + + return pattern; +} + +int RegEx::get_group_count() const { + + int count = 0; + for (int i = 1; i < group_names.size(); ++i) + if (group_names[i].get_type() == Variant::INT) + ++count; + return count; +} + +Array RegEx::get_names() const { + + Array res; + for (int i = 1; i < group_names.size(); ++i) + if (group_names[i].get_type() == Variant::STRING) + res.push_back(group_names[i]); + return res; +} + +RegEx::RegEx() { + + root = NULL; + lookahead_depth = 0; +} + +RegEx::RegEx(const String& p_pattern) { + + root = NULL; + compile(p_pattern); +} + +RegEx::~RegEx() { + + if (root) + memdelete(root); +} + +void RegExMatch::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("expand","template"),&RegExMatch::expand); + ObjectTypeDB::bind_method(_MD("get_group_count"),&RegExMatch::get_group_count); + ObjectTypeDB::bind_method(_MD("get_group_array"),&RegExMatch::get_group_array); + ObjectTypeDB::bind_method(_MD("get_names"),&RegExMatch::get_names); + ObjectTypeDB::bind_method(_MD("get_name_dict"),&RegExMatch::get_name_dict); + ObjectTypeDB::bind_method(_MD("get_string","name"),&RegExMatch::get_string, DEFVAL(0)); + ObjectTypeDB::bind_method(_MD("get_start","name"),&RegExMatch::get_start, DEFVAL(0)); + ObjectTypeDB::bind_method(_MD("get_end","name"),&RegExMatch::get_end, DEFVAL(0)); +} + +void RegEx::_bind_methods() { + + ObjectTypeDB::bind_method(_MD("clear"),&RegEx::clear); + ObjectTypeDB::bind_method(_MD("compile","pattern"),&RegEx::compile); + ObjectTypeDB::bind_method(_MD("search","text","start","end"),&RegEx::search, DEFVAL(0), DEFVAL(-1)); + ObjectTypeDB::bind_method(_MD("sub","text","replacement","all","start","end"),&RegEx::sub, DEFVAL(false), DEFVAL(0), DEFVAL(-1)); + ObjectTypeDB::bind_method(_MD("is_valid"),&RegEx::is_valid); + ObjectTypeDB::bind_method(_MD("get_pattern"),&RegEx::get_pattern); + ObjectTypeDB::bind_method(_MD("get_group_count"),&RegEx::get_group_count); + ObjectTypeDB::bind_method(_MD("get_names"),&RegEx::get_names); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "pattern"), _SCS("compile"), _SCS("get_pattern")); +} + diff --git a/modules/regex/regex.h b/modules/regex/regex.h new file mode 100644 index 0000000000..803aa72b3f --- /dev/null +++ b/modules/regex/regex.h @@ -0,0 +1,114 @@ +/*************************************************************************/ +/* regex.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef REGEX_H +#define REGEX_H + +#include "core/vector.h" +#include "core/ustring.h" +#include "core/dictionary.h" +#include "core/reference.h" +#include "core/resource.h" + +class RegExNode; + +class RegExMatch : public Reference { + + OBJ_TYPE(RegExMatch, Reference); + + struct Group { + Variant name; + int start; + int length; + }; + + Vector<Group> captures; + String string; + + friend class RegEx; + friend class RegExSearch; + friend class RegExNodeCapturing; + friend class RegExNodeBackReference; + +protected: + + static void _bind_methods(); + +public: + + String expand(const String& p_template) const; + + int get_group_count() const; + Array get_group_array() const; + + Array get_names() const; + Dictionary get_name_dict() const; + + String get_string(const Variant& p_name) const; + int get_start(const Variant& p_name) const; + int get_end(const Variant& p_name) const; + + RegExMatch(); + +}; + +class RegEx : public Resource { + + OBJ_TYPE(RegEx, Resource); + + RegExNode* root; + Vector<Variant> group_names; + String pattern; + int lookahead_depth; + +protected: + + static void _bind_methods(); + +public: + + void clear(); + Error compile(const String& p_pattern); + + Ref<RegExMatch> search(const String& p_text, int p_start = 0, int p_end = -1) const; + String sub(const String& p_text, const String& p_replacement, bool p_all = false, int p_start = 0, int p_end = -1) const; + + bool is_valid() const; + String get_pattern() const; + int get_group_count() const; + Array get_names() const; + + RegEx(); + RegEx(const String& p_pattern); + ~RegEx(); + +}; + +#endif // REGEX_H + diff --git a/drivers/nrex/regex.h b/modules/regex/register_types.cpp index 74495442c7..050cf3efff 100644 --- a/drivers/nrex/regex.h +++ b/modules/regex/register_types.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* regex.h */ +/* register_types.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -26,40 +26,18 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef REGEX_H -#define REGEX_H -#include "ustring.h" -#include "vector.h" -#include "core/reference.h" -#include "nrex.hpp" +#include "register_types.h" +#include "object_type_db.h" +#include "regex.h" -class RegEx : public Reference { +void register_regex_types() { - OBJ_TYPE(RegEx, Reference); + ObjectTypeDB::register_type<RegExMatch>(); + ObjectTypeDB::register_type<RegEx>(); +} - mutable String text; - mutable Vector<nrex_result> captures; - nrex exp; +void unregister_regex_types() { -protected: +} - static void _bind_methods(); - StringArray _bind_get_captures() const; - -public: - - void clear(); - bool is_valid() const; - int get_capture_count() const; - int get_capture_start(int capture) const; - String get_capture(int capture) const; - Error compile(const String& p_pattern, int capture = 9); - int find(const String& p_text, int p_start = 0, int p_end = -1) const; - - RegEx(); - RegEx(const String& p_pattern); - ~RegEx(); -}; - -#endif // REGEX_H diff --git a/modules/regex/register_types.h b/modules/regex/register_types.h new file mode 100644 index 0000000000..df3b508e14 --- /dev/null +++ b/modules/regex/register_types.h @@ -0,0 +1,31 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2016 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +void register_regex_types(); +void unregister_regex_types(); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index a5b26930be..7ae0100762 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -1,16 +1,11 @@ # -# tested on | Windows native | Linux cross-compilation -# ----------------------------+-------------------+--------------------------- -# MSVS C++ 2010 Express | WORKS | n/a +# tested on | Windows native | Linux cross-compilation +# ----------------------------+-------------------+--------------------------- # Visual C++ Build Tools 2015 | WORKS | n/a -# Mingw-w64 | WORKS | WORKS -# Mingw-w32 | WORKS | WORKS -# MinGW | WORKS | untested -# -##### -# Notes about MSVS C++ : -# -# - MSVC2010-Express compiles to 32bits only. +# MSVS C++ 2010 Express | WORKS | n/a +# Mingw-w64 | WORKS | WORKS +# Mingw-w32 | WORKS | WORKS +# MinGW | WORKS | untested # ##### # Note about Visual C++ Build Tools : @@ -19,6 +14,11 @@ # http://landinghub.visualstudio.com/visual-cpp-build-tools # ##### +# Notes about MSVS C++ : +# +# - MSVC2010-Express compiles to 32bits only. +# +##### # Notes about Mingw-w64 and Mingw-w32 under Windows : # # - both can be installed using the official installer : @@ -109,7 +109,7 @@ def can_build(): if (os.name=="nt"): #building natively on windows! - if ( methods.msvc_is_detected() ): + if ( os.getenv("VCINSTALLDIR") ): return True else: print("\nMSVC not detected, attempting Mingw.") @@ -204,7 +204,7 @@ def configure(env): env.Append(CPPPATH=['#platform/windows']) env['is_mingw']=False - if (os.name=="nt" and methods.msvc_is_detected() ): + if (os.name=="nt" and os.getenv("VCINSTALLDIR") ): #build using visual studio env['ENV']['TMP'] = os.environ['TMP'] env.Append(CPPPATH=['#platform/windows/include']) diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 156f4956bb..cbaaeb03e5 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -206,6 +206,7 @@ void Tween::_bind_methods() { ObjectTypeDB::bind_method(_MD("resume","object","key"),&Tween::resume, DEFVAL("") ); ObjectTypeDB::bind_method(_MD("resume_all"),&Tween::resume_all ); ObjectTypeDB::bind_method(_MD("remove","object","key"),&Tween::remove, DEFVAL("") ); + ObjectTypeDB::bind_method(_MD("_remove","object","key","first_only"),&Tween::_remove ); ObjectTypeDB::bind_method(_MD("remove_all"),&Tween::remove_all ); ObjectTypeDB::bind_method(_MD("seek","time"),&Tween::seek ); ObjectTypeDB::bind_method(_MD("tell"),&Tween::tell ); @@ -620,7 +621,7 @@ void Tween::_tween_process(float p_delta) { object->call(data.key, (const Variant **) arg, data.args, error); } if (!repeat) - call_deferred("remove", object, data.key); + call_deferred("_remove", object, data.key, true); } continue; } @@ -634,7 +635,7 @@ void Tween::_tween_process(float p_delta) { emit_signal("tween_complete",object,data.key); // not repeat mode, remove completed action if (!repeat) - call_deferred("remove", object, data.key); + call_deferred("_remove", object, data.key, true); } } pending_update --; @@ -816,10 +817,15 @@ bool Tween::resume_all() { } bool Tween::remove(Object *p_object, String p_key) { + _remove(p_object, p_key, false); + return true; +} + +void Tween::_remove(Object *p_object, String p_key, bool first_only) { if(pending_update != 0) { - call_deferred("remove", p_object, p_key); - return true; + call_deferred("_remove", p_object, p_key, first_only); + return; } List<List<InterpolateData>::Element *> for_removal; for(List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) { @@ -830,12 +836,14 @@ bool Tween::remove(Object *p_object, String p_key) { continue; if(object == p_object && (data.key == p_key || p_key == "")) { for_removal.push_back(E); + if (first_only) { + break; + } } } for(List<List<InterpolateData>::Element *>::Element *E=for_removal.front();E;E=E->next()) { interpolates.erase(E->get()); } - return true; } bool Tween::remove_all() { diff --git a/scene/animation/tween.h b/scene/animation/tween.h index d0455cdc71..844a012b9f 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -143,6 +143,7 @@ private: void _tween_process(float p_delta); void _set_process(bool p_process,bool p_force=false); + void _remove(Object *p_node, String p_key, bool first_only); protected: diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index feaf516f42..83a4f34282 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -151,6 +151,18 @@ void Container::_notification(int p_what) { queue_sort(); } } break; + case NOTIFICATION_SORT_CHILDREN: { + + Size2 s = get_size(); + + for (int i=0; i<get_child_count();i++) { + Control *c = get_child(i)->cast_to<Control>(); + if (!c || !c->is_visible() || c->is_set_as_toplevel()) + continue; + + fit_child_in_rect(c,Rect2(0, 0, s.width, s.height)); + } + } } } diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 6b43425edc..f942f15ed0 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -186,15 +186,17 @@ void FileDialog::_action_pressed() { hide(); }else if (mode==MODE_OPEN_ANY || mode==MODE_OPEN_DIR) { - String path=dir_access->get_current_dir(); - /*if (tree->get_selected()) { - Dictionary d = tree->get_selected()->get_metadata(0); + + path=path.replace("\\","/"); + + if (TreeItem* item = tree->get_selected()) { + Dictionary d = item->get_metadata(0); if (d["dir"]) { - path=path+"/"+String(d["name"]); + path=path.plus_file(d["name"]); } - }*/ - path=path.replace("\\","/"); + } + emit_signal("dir_selected",path); hide(); } @@ -348,18 +350,18 @@ void FileDialog::update_file_list() { files.sort_custom<NoCaseComparator>(); while(!dirs.empty()) { + String& dir_name = dirs.front()->get(); + TreeItem *ti=tree->create_item(root); + ti->set_text(0,dir_name+"/"); + ti->set_icon(0,folder); - if (dirs.front()->get()!=".") { - TreeItem *ti=tree->create_item(root); - ti->set_text(0,dirs.front()->get()+"/"); - ti->set_icon(0,folder); - Dictionary d; - d["name"]=dirs.front()->get(); - d["dir"]=true; - ti->set_metadata(0,d); - } - dirs.pop_front(); + Dictionary d; + d["name"]=dir_name; + d["dir"]=true; + + ti->set_metadata(0,d); + dirs.pop_front(); } dirs.clear(); diff --git a/tools/editor/editor_file_dialog.cpp b/tools/editor/editor_file_dialog.cpp index b8abd1d32c..90289a275e 100644 --- a/tools/editor/editor_file_dialog.cpp +++ b/tools/editor/editor_file_dialog.cpp @@ -336,15 +336,21 @@ void EditorFileDialog::_action_pressed() { hide(); }else if (mode==MODE_OPEN_ANY || mode==MODE_OPEN_DIR) { - String path=dir_access->get_current_dir(); - /*if (tree->get_selected()) { - Dictionary d = tree->get_selected()->get_metadata(0); - if (d["dir"]) { - path=path+"/"+String(d["name"]); - } - }*/ + path=path.replace("\\","/"); + + for(int i=0;i<item_list->get_item_count();i++) { + if (item_list->is_selected(i)) { + Dictionary d=item_list->get_item_metadata(i); + if (d["dir"]) { + path=path.plus_file(d["name"]); + + break; + } + } + } + _save_to_recent(); emit_signal("dir_selected",path); hide(); @@ -571,25 +577,26 @@ void EditorFileDialog::update_file_list() { files.sort_custom<NoCaseComparator>(); while(!dirs.empty()) { + const String& dir_name=dirs.front()->get(); - if (dirs.front()->get()!=".") { - item_list->add_item(dirs.front()->get()+"/"); - if (display_mode==DISPLAY_THUMBNAILS) { + item_list->add_item(dir_name+"/"); - item_list->set_item_icon(item_list->get_item_count()-1,folder_thumbnail); - } else { + if (display_mode==DISPLAY_THUMBNAILS) { - item_list->set_item_icon(item_list->get_item_count()-1,folder); - } + item_list->set_item_icon(item_list->get_item_count()-1,folder_thumbnail); + } else { - Dictionary d; - d["name"]=dirs.front()->get(); - d["path"]=String(); - d["dir"]=true; - item_list->set_item_metadata( item_list->get_item_count() -1, d); + item_list->set_item_icon(item_list->get_item_count()-1,folder); } - dirs.pop_front(); + Dictionary d; + d["name"]=dir_name; + d["path"]=String(); + d["dir"]=true; + + item_list->set_item_metadata( item_list->get_item_count() -1, d); + + dirs.pop_front(); } dirs.clear(); diff --git a/tools/editor/plugins/canvas_item_editor_plugin.cpp b/tools/editor/plugins/canvas_item_editor_plugin.cpp index b0e002ba44..ac39e0687c 100644 --- a/tools/editor/plugins/canvas_item_editor_plugin.cpp +++ b/tools/editor/plugins/canvas_item_editor_plugin.cpp @@ -32,13 +32,20 @@ #include "os/keyboard.h" #include "scene/main/viewport.h" #include "scene/main/canvas_layer.h" -#include "scene/2d/node_2d.h" +#include "scene/2d/sprite.h" +#include "scene/2d/light_2d.h" +#include "scene/2d/particles_2d.h" +#include "scene/2d/polygon_2d.h" +#include "scene/2d/screen_button.h" #include "globals.h" #include "os/input.h" #include "tools/editor/editor_settings.h" #include "scene/gui/grid_container.h" +#include "scene/gui/patch_9_frame.h" #include "tools/editor/animation_editor.h" #include "tools/editor/plugins/animation_player_editor_plugin.h" +#include "tools/editor/script_editor_debugger.h" +#include "tools/editor/plugins/script_editor_plugin.h" #include "scene/resources/packed_scene.h" @@ -3326,7 +3333,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { vp->add_child(p_editor->get_scene_root()); - viewport = memnew( Control ); + viewport = memnew( CanvasItemEditorViewport(p_editor, this) ); vp_base->add_child(viewport); viewport->set_area_as_parent_rect(); @@ -3642,3 +3649,397 @@ CanvasItemEditorPlugin::~CanvasItemEditorPlugin() } +void CanvasItemEditorViewport::_on_mouse_exit() { + if (selector->is_hidden()){ + _remove_preview(); + } +} + +void CanvasItemEditorViewport::_on_select_type(Object* selected) { + CheckBox* check = selected->cast_to<CheckBox>(); + String type = check->get_text(); + selector_label->set_text(vformat(TTR("Add %s"),type)); + label->set_text(vformat(TTR("Adding %s..."),type)); +} + +void CanvasItemEditorViewport::_on_change_type() { + CheckBox* check=btn_group->get_pressed_button()->cast_to<CheckBox>(); + default_type=check->get_text(); + _perform_drop_data(); + selector->hide(); +} + +void CanvasItemEditorViewport::_create_preview(const Vector<String>& files) const { + label->set_pos(get_global_pos()+Point2(14,14)); + label_desc->set_pos(label->get_pos()+Point2(0,label->get_size().height)); + for (int i=0;i<files.size();i++) { + String path=files[i]; + RES res=ResourceLoader::load(path); + String type=res->get_type(); + if (type=="ImageTexture" || type=="PackedScene") { + if (type=="ImageTexture") { + Ref<ImageTexture> texture=Ref<ImageTexture> ( ResourceCache::get(path)->cast_to<ImageTexture>() ); + Sprite* sprite=memnew(Sprite); + sprite->set_texture(texture); + sprite->set_opacity(0.7f); + preview->add_child(sprite); + label->show(); + label_desc->show(); + } else if (type=="PackedScene") { + Ref<PackedScene> scn=ResourceLoader::load(path); + if (scn.is_valid()){ + Node* instance=scn->instance(); + if (instance){ + preview->add_child(instance); + } + } + } + editor->get_scene_root()->add_child(preview); + } + } +} + +void CanvasItemEditorViewport::_remove_preview() { + if (preview->get_parent()){ + editor->get_scene_root()->remove_child(preview); + for (int i=preview->get_child_count()-1;i>=0;i--){ + Node* node=preview->get_child(i); + memdelete(node); + } + label->hide(); + label_desc->hide(); + } +} + +bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String& p_target_scene_path, Node* p_desired_node) { + if (p_desired_node->get_filename()==p_target_scene_path) { + return true; + } + + int childCount=p_desired_node->get_child_count(); + for (int i=0;i<childCount;i++) { + Node* child=p_desired_node->get_child(i); + if(_cyclical_dependency_exists(p_target_scene_path,child)) { + return true; + } + } + return false; +} + +void CanvasItemEditorViewport::_create_nodes(Node* parent, Node* child, String& path, const Point2& p_point) { + child->set_name(path.get_file().basename()); + Ref<ImageTexture> texture=Ref<ImageTexture> ( ResourceCache::get(path)->cast_to<ImageTexture>() ); + Size2 texture_size = texture->get_size(); + + editor_data->get_undo_redo().add_do_method(parent,"add_child",child); + editor_data->get_undo_redo().add_do_method(child,"set_owner",editor->get_edited_scene()); + editor_data->get_undo_redo().add_do_reference(child); + editor_data->get_undo_redo().add_undo_method(parent,"remove_child",child); + + String new_name=parent->validate_child_name(child); + ScriptEditorDebugger *sed=ScriptEditor::get_singleton()->get_debugger(); + editor_data->get_undo_redo().add_do_method(sed,"live_debug_create_node",editor->get_edited_scene()->get_path_to(parent),child->get_type(),new_name); + editor_data->get_undo_redo().add_undo_method(sed,"live_debug_remove_node",NodePath(String(editor->get_edited_scene()->get_path_to(parent))+"/"+new_name)); + + // handle with different property for texture + String property = "texture"; + List<PropertyInfo> props; + child->get_property_list(&props); + for(const List<PropertyInfo>::Element *E=props.front();E;E=E->next() ) { + if (E->get().name=="config/texture") { // Particles2D + property="config/texture"; + break; + } else if (E->get().name=="texture/texture") { // Polygon2D + property="texture/texture"; + break; + } else if (E->get().name=="normal") { // TouchScreenButton + property="normal"; + break; + } + } + editor_data->get_undo_redo().add_do_property(child,property,texture); + + // make visible for certain node type + if (default_type=="Patch9Frame") { + editor_data->get_undo_redo().add_do_property(child,"rect/size",texture_size); + } else if (default_type=="Polygon2D") { + DVector<Vector2> list; + list.push_back(Vector2(0,0)); + list.push_back(Vector2(texture_size.width,0)); + list.push_back(Vector2(texture_size.width,texture_size.height)); + list.push_back(Vector2(0,texture_size.height)); + editor_data->get_undo_redo().add_do_property(child,"polygon",list); + } + + // locate at preview position + Point2 pos; + if (parent->has_method("get_global_pos")) { + pos=parent->call("get_global_pos"); + } + Matrix32 trans=canvas->get_canvas_transform(); + Point2 target_pos = (p_point-trans.get_origin())/trans.get_scale().x-pos; + if (default_type=="Polygon2D" || default_type=="TouchScreenButton" || default_type=="TextureFrame" || default_type=="Patch9Frame") { + target_pos -= texture_size/2; + } + editor_data->get_undo_redo().add_do_method(child,"set_pos",target_pos); +} + +bool CanvasItemEditorViewport::_create_instance(Node* parent, String& path, const Point2& p_point) { + Ref<PackedScene> sdata=ResourceLoader::load(path); + if (!sdata.is_valid()) { // invalid scene + return false; + } + + Node* instanced_scene=sdata->instance(true); + if (!instanced_scene) { // error on instancing + return false; + } + + if (editor->get_edited_scene()->get_filename()!="") { // cyclical instancing + if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instanced_scene)) { + return false; + } + } + + instanced_scene->set_filename( Globals::get_singleton()->localize_path(path) ); + + editor_data->get_undo_redo().add_do_method(parent,"add_child",instanced_scene); + editor_data->get_undo_redo().add_do_method(instanced_scene,"set_owner",editor->get_edited_scene()); + editor_data->get_undo_redo().add_do_reference(instanced_scene); + editor_data->get_undo_redo().add_undo_method(parent,"remove_child",instanced_scene); + + String new_name=parent->validate_child_name(instanced_scene); + ScriptEditorDebugger *sed=ScriptEditor::get_singleton()->get_debugger(); + editor_data->get_undo_redo().add_do_method(sed,"live_debug_instance_node",editor->get_edited_scene()->get_path_to(parent),path,new_name); + editor_data->get_undo_redo().add_undo_method(sed,"live_debug_remove_node",NodePath(String(editor->get_edited_scene()->get_path_to(parent))+"/"+new_name)); + + Point2 pos; + Node2D* parent_node2d=parent->cast_to<Node2D>(); + if (parent_node2d) { + pos=parent_node2d->get_global_pos(); + } else { + Control* parent_control=parent->cast_to<Control>(); + if (parent_control) { + pos=parent_control->get_global_pos(); + } + } + Matrix32 trans=canvas->get_canvas_transform(); + editor_data->get_undo_redo().add_do_method(instanced_scene,"set_pos",(p_point-trans.get_origin())/trans.get_scale().x-pos); + + return true; +} + +void CanvasItemEditorViewport::_perform_drop_data(){ + _remove_preview(); + + Vector<String> error_files; + + editor_data->get_undo_redo().create_action(TTR("Create Node")); + + for (int i=0;i<selected_files.size();i++) { + String path=selected_files[i]; + RES res=ResourceLoader::load(path); + if (res.is_null()) { + continue; + } + String type=res->get_type(); + if (type=="ImageTexture") { + Node* child; + if (default_type=="Light2D") child=memnew(Light2D); + else if (default_type=="Particles2D") child=memnew(Particles2D); + else if (default_type=="Polygon2D") child=memnew(Polygon2D); + else if (default_type=="TouchScreenButton") child=memnew(TouchScreenButton); + else if (default_type=="TextureFrame") child=memnew(TextureFrame); + else if (default_type=="Patch9Frame") child=memnew(Patch9Frame); + else child=memnew(Sprite); // default + + _create_nodes(target_node, child, path, drop_pos); + } else if (type=="PackedScene") { + bool success=_create_instance(target_node, path, drop_pos); + if (!success) { + error_files.push_back(path); + } + } + } + + editor_data->get_undo_redo().commit_action(); + + if (error_files.size()>0) { + String files_str; + for (int i=0;i<error_files.size();i++) { + files_str += error_files[i].get_file().basename() + ","; + } + files_str=files_str.substr(0,files_str.length()-1); + accept->get_ok()->set_text(TTR("Ugh")); + accept->set_text(vformat(TTR("Error instancing scene from %s"),files_str.c_str())); + accept->popup_centered_minsize(); + } +} + +bool CanvasItemEditorViewport::can_drop_data(const Point2& p_point,const Variant& p_data) const { + Dictionary d=p_data; + if (d.has("type")) { + if (String(d["type"])=="files") { + Vector<String> files=d["files"]; + bool can_instance=false; + for (int i=0;i<files.size();i++) { // check if dragged files contain resource or scene can be created at least one + RES res=ResourceLoader::load(files[i]); + if (res.is_null()) { + continue; + } + String type=res->get_type(); + if (type=="PackedScene") { + Ref<PackedScene> sdata=ResourceLoader::load(files[i]); + Node* instanced_scene=sdata->instance(true); + if (!instanced_scene) { + continue; + } + memdelete(instanced_scene); + } + can_instance=true; + break; + } + if (can_instance) { + if (!preview->get_parent()){ // create preview only once + _create_preview(files); + } + Matrix32 trans=canvas->get_canvas_transform(); + preview->set_pos((p_point-trans.get_origin())/trans.get_scale().x); + label->set_text(vformat(TTR("Adding %s..."),default_type)); + } + return can_instance; + } + } + label->hide(); + return false; +} + +void CanvasItemEditorViewport::drop_data(const Point2& p_point,const Variant& p_data) { + bool is_shift=Input::get_singleton()->is_key_pressed(KEY_SHIFT); + bool is_alt=Input::get_singleton()->is_key_pressed(KEY_ALT); + + selected_files.clear(); + Dictionary d=p_data; + if (d.has("type") && String(d["type"])=="files"){ + selected_files=d["files"]; + } + + List<Node*> list=editor->get_editor_selection()->get_selected_node_list(); + if (list.size()==0) { + accept->get_ok()->set_text(TTR("OK :(")); + accept->set_text(TTR("No parent to instance a child at.")); + accept->popup_centered_minsize(); + _remove_preview(); + return; + } + if (list.size()!=1) { + accept->get_ok()->set_text(TTR("I see..")); + accept->set_text(TTR("This operation requires a single selected node.")); + accept->popup_centered_minsize(); + _remove_preview(); + return; + } + + target_node=list[0]; + if (is_shift && target_node!=editor->get_edited_scene()) { + target_node=target_node->get_parent(); + } + drop_pos=p_point; + + if (is_alt) { + List<BaseButton*> btn_list; + btn_group->get_button_list(&btn_list); + for (int i=0;i<btn_list.size();i++) { + CheckBox* check=btn_list[i]->cast_to<CheckBox>(); + check->set_pressed(check->get_text()==default_type); + } + selector_label->set_text(vformat(TTR("Add %s"),default_type)); + selector->popup_centered_minsize(); + } else { + _perform_drop_data(); + } +} + +void CanvasItemEditorViewport::_notification(int p_what) { + if (p_what==NOTIFICATION_ENTER_TREE) { + connect("mouse_exit",this,"_on_mouse_exit"); + } else if (p_what==NOTIFICATION_EXIT_TREE) { + disconnect("mouse_exit",this,"_on_mouse_exit"); + } +} + +void CanvasItemEditorViewport::_bind_methods() { + ObjectTypeDB::bind_method(_MD("_on_select_type"),&CanvasItemEditorViewport::_on_select_type); + ObjectTypeDB::bind_method(_MD("_on_change_type"),&CanvasItemEditorViewport::_on_change_type); + ObjectTypeDB::bind_method(_MD("_on_mouse_exit"),&CanvasItemEditorViewport::_on_mouse_exit); +} + +CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasItemEditor* p_canvas) { + default_type="Sprite"; + // Node2D + types.push_back("Sprite"); + types.push_back("Light2D"); + types.push_back("Particles2D"); + types.push_back("Polygon2D"); + types.push_back("TouchScreenButton"); + // Control + types.push_back("TextureFrame"); + types.push_back("Patch9Frame"); + + target_node=NULL; + editor=p_node; + editor_data=editor->get_scene_tree_dock()->get_editor_data(); + canvas=p_canvas; + preview=memnew( Node2D ); + accept=memnew( AcceptDialog ); + editor->get_gui_base()->add_child(accept); + + selector=memnew( WindowDialog ); + selector->set_title(TTR("Change default type")); + + VBoxContainer* vbc=memnew(VBoxContainer); + vbc->add_constant_override("separation",10*EDSCALE); + vbc->set_custom_minimum_size(Size2(200,260)*EDSCALE); + + selector_label=memnew(Label); + selector_label->set_align(Label::ALIGN_CENTER); + selector_label->set_valign(Label::VALIGN_BOTTOM); + selector_label->set_custom_minimum_size(Size2(0,30)*EDSCALE); + vbc->add_child(selector_label); + + btn_group=memnew( ButtonGroup ); + btn_group->set_h_size_flags(0); + btn_group->connect("button_selected", this, "_on_select_type"); + + for (int i=0;i<types.size();i++) { + CheckBox* check=memnew(CheckBox); + check->set_text(types[i]); + btn_group->add_child(check); + } + vbc->add_child(btn_group); + + Button* ok=memnew(Button); + ok->set_text(TTR("OK")); + ok->set_h_size_flags(0); + vbc->add_child(ok); + ok->connect("pressed", this, "_on_change_type"); + + selector->add_child(vbc); + editor->get_gui_base()->add_child(selector); + + label=memnew(Label); + label->add_color_override("font_color", Color(1,1,0,1)); + label->add_color_override("font_color_shadow", Color(0,0,0,1)); + label->add_constant_override("shadow_as_outline", 1*EDSCALE); + label->hide(); + editor->get_gui_base()->add_child(label); + + label_desc=memnew(Label); + label_desc->set_text(TTR("Drag & drop + Shift : Add node as sibling\nDrag & drop + Alt : Change node type")); + label_desc->add_color_override("font_color", Color(0.6,0.6,0.6,1)); + label_desc->add_color_override("font_color_shadow", Color(0.2,0.2,0.2,1)); + label_desc->add_constant_override("shadow_as_outline", 1*EDSCALE); + label_desc->add_constant_override("line_spacing", 0); + label_desc->hide(); + editor->get_gui_base()->add_child(label_desc); +} diff --git a/tools/editor/plugins/canvas_item_editor_plugin.h b/tools/editor/plugins/canvas_item_editor_plugin.h index 9f4bc46eb4..bbec078e02 100644 --- a/tools/editor/plugins/canvas_item_editor_plugin.h +++ b/tools/editor/plugins/canvas_item_editor_plugin.h @@ -31,17 +31,18 @@ #include "tools/editor/editor_plugin.h" #include "tools/editor/editor_node.h" +#include "scene/gui/button_group.h" +#include "scene/gui/check_box.h" +#include "scene/gui/label.h" #include "scene/gui/spin_box.h" #include "scene/gui/panel_container.h" #include "scene/gui/box_container.h" #include "scene/2d/canvas_item.h" - /** @author Juan Linietsky <reduzio@gmail.com> */ - - +class CanvasItemEditorViewport; class CanvasItemEditorSelectedItem : public Object { @@ -453,4 +454,49 @@ public: }; +class CanvasItemEditorViewport : public VBoxContainer { + OBJ_TYPE( CanvasItemEditorViewport, VBoxContainer ); + + String default_type; + Vector<String> types; + + Vector<String> selected_files; + Node* target_node; + Point2 drop_pos; + + EditorNode* editor; + EditorData* editor_data; + CanvasItemEditor* canvas; + Node2D* preview; + AcceptDialog* accept; + WindowDialog* selector; + Label* selector_label; + Label* label; + Label* label_desc; + ButtonGroup* btn_group; + + void _on_mouse_exit(); + void _on_select_type(Object* selected); + void _on_change_type(); + + void _create_preview(const Vector<String>& files) const; + void _remove_preview(); + + bool _cyclical_dependency_exists(const String& p_target_scene_path, Node* p_desired_node); + void _create_nodes(Node* parent, Node* child, String& path, const Point2& p_point); + bool _create_instance(Node* parent, String& path, const Point2& p_point); + void _perform_drop_data(); + + static void _bind_methods(); + +protected: + void _notification(int p_what); + +public: + virtual bool can_drop_data(const Point2& p_point,const Variant& p_data) const; + virtual void drop_data(const Point2& p_point,const Variant& p_data); + + CanvasItemEditorViewport(EditorNode *p_node, CanvasItemEditor* p_canvas); +}; + #endif diff --git a/tools/editor/scene_tree_dock.cpp b/tools/editor/scene_tree_dock.cpp index 56f10ff7f8..16f06c7ac9 100644 --- a/tools/editor/scene_tree_dock.cpp +++ b/tools/editor/scene_tree_dock.cpp @@ -81,7 +81,10 @@ void SceneTreeDock::_unhandled_key_input(InputEvent p_event) { _tool_selected(TOOL_DUPLICATE); } else if (ED_IS_SHORTCUT("scene_tree/add_script", p_event)) { - _tool_selected(TOOL_SCRIPT); + _tool_selected(TOOL_CREATE_SCRIPT); + } + else if (ED_IS_SHORTCUT("scene_tree/load_script", p_event)) { + _tool_selected(TOOL_LOAD_SCRIPT); } else if (ED_IS_SHORTCUT("scene_tree/move_up", p_event)) { _tool_selected(TOOL_MOVE_UP); @@ -266,6 +269,24 @@ void SceneTreeDock::_replace_with_branch_scene(const String& p_file,Node* base) scene_tree->set_selected(instanced_scene); } + +void SceneTreeDock::_file_selected(String p_file) { + RES p_script = ResourceLoader::load(p_file, "Script"); + if (p_script.is_null()) { + accept->get_ok()->set_text(TTR("Ugh")); + accept->set_text(vformat(TTR("Error loading script from %s"), p_file)); + accept->popup_centered_minsize(); + return; + } + + Node *selected = scene_tree->get_selected(); + if (!selected) + return; + selected->set_script(p_script.get_ref_ptr()); + editor->push_item(p_script.operator->()); + file_dialog->hide(); +} + bool SceneTreeDock::_cyclical_dependency_exists(const String& p_target_scene_path, Node* p_desired_node) { int childCount = p_desired_node->get_child_count(); @@ -358,7 +379,22 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { //groups_editor->set_current(current); //groups_editor->popup_centered_ratio(); } break; - case TOOL_SCRIPT: { + case TOOL_LOAD_SCRIPT: { + Node *selected = scene_tree->get_selected(); + if (!selected) + break; + + file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE); + + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + file_dialog->clear_filters(); + for (List<String>::Element *E = extensions.front(); E; E = E->next()) + file_dialog->add_filter("*." + E->get() + " ; " + E->get().to_upper()); + + file_dialog->popup_centered_ratio(); + } break; + case TOOL_CREATE_SCRIPT: { Node *selected = scene_tree->get_selected(); if (!selected) @@ -672,6 +708,7 @@ void SceneTreeDock::_notification(int p_what) { button_add->set_icon(get_icon("Add","EditorIcons")); button_instance->set_icon(get_icon("Instance","EditorIcons")); button_create_script->set_icon(get_icon("Script","EditorIcons")); + button_load_script->set_icon(get_icon("Script", "EditorIcons")); filter_icon->set_texture(get_icon("Zoom","EditorIcons")); @@ -1303,8 +1340,10 @@ void SceneTreeDock::_selection_changed() { if (selection_size==1 && EditorNode::get_singleton()->get_editor_selection()->get_selection().front()->key()->get_script().is_null()) { button_create_script->show(); + button_load_script->show(); } else { button_create_script->hide(); + button_load_script->hide(); } //tool_buttons[TOOL_MULTI_EDIT]->set_disabled(EditorNode::get_singleton()->get_editor_selection()->get_selection().size()<2); @@ -1721,6 +1760,15 @@ void SceneTreeDock::_files_dropped(Vector<String> p_files,NodePath p_to,int p_ty _perform_instance_scenes(p_files,node,to_pos); } +void SceneTreeDock::_script_dropped(String p_file, NodePath p_to) { + Ref<Script> scr = ResourceLoader::load(p_file); + ERR_FAIL_COND(!scr.is_valid()); + Node *n = get_node(p_to); + if (n) { + n->set_script(scr.get_ref_ptr()); + } +} + void SceneTreeDock::_nodes_dragged(Array p_nodes,NodePath p_to,int p_type) { Vector<Node*> nodes; @@ -1775,7 +1823,8 @@ void SceneTreeDock::_tree_rmb(const Vector2& p_menu_pos) { //menu->add_icon_item(get_icon("Groups","EditorIcons"),TTR("Edit Groups"),TOOL_GROUP); //menu->add_icon_item(get_icon("Connect","EditorIcons"),TTR("Edit Connections"),TOOL_CONNECT); menu->add_separator(); - menu->add_icon_shortcut(get_icon("Script","EditorIcons"),ED_GET_SHORTCUT("scene_tree/add_script"), TOOL_SCRIPT); + menu->add_icon_shortcut(get_icon("Script", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/add_script"), TOOL_CREATE_SCRIPT); + menu->add_icon_shortcut(get_icon("Script", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/load_script"), TOOL_LOAD_SCRIPT); menu->add_separator(); } @@ -1834,7 +1883,7 @@ void SceneTreeDock::_focus_node() { void SceneTreeDock::open_script_dialog(Node* p_for_node) { scene_tree->set_selected(p_for_node,false); - _tool_selected(TOOL_SCRIPT); + _tool_selected(TOOL_CREATE_SCRIPT); } void SceneTreeDock::_bind_methods() { @@ -1859,9 +1908,11 @@ void SceneTreeDock::_bind_methods() { ObjectTypeDB::bind_method(_MD("_new_scene_from"),&SceneTreeDock::_new_scene_from); ObjectTypeDB::bind_method(_MD("_nodes_dragged"),&SceneTreeDock::_nodes_dragged); ObjectTypeDB::bind_method(_MD("_files_dropped"),&SceneTreeDock::_files_dropped); + ObjectTypeDB::bind_method(_MD("_script_dropped"),&SceneTreeDock::_script_dropped); ObjectTypeDB::bind_method(_MD("_tree_rmb"),&SceneTreeDock::_tree_rmb); ObjectTypeDB::bind_method(_MD("_filter_changed"),&SceneTreeDock::_filter_changed); ObjectTypeDB::bind_method(_MD("_focus_node"),&SceneTreeDock::_focus_node); + ObjectTypeDB::bind_method(_MD("_file_selected"), &SceneTreeDock::_file_selected); ObjectTypeDB::bind_method(_MD("instance"),&SceneTreeDock::instance); @@ -1886,6 +1937,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor,Node *p_scene_root,EditorSelec ED_SHORTCUT("scene_tree/instance_scene",TTR("Instance Child Scene")); ED_SHORTCUT("scene_tree/change_node_type", TTR("Change Type")); ED_SHORTCUT("scene_tree/add_script", TTR("Add Script")); + ED_SHORTCUT("scene_tree/load_script", TTR("Load Script")); ED_SHORTCUT("scene_tree/move_up", TTR("Move Up"), KEY_MASK_CMD | KEY_UP); ED_SHORTCUT("scene_tree/move_down", TTR("Move Down"), KEY_MASK_CMD | KEY_DOWN); ED_SHORTCUT("scene_tree/duplicate", TTR("Duplicate"),KEY_MASK_CMD | KEY_D); @@ -1922,12 +1974,19 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor,Node *p_scene_root,EditorSelec tb = memnew( ToolButton ); - tb->connect("pressed",this,"_tool_selected",make_binds(TOOL_SCRIPT, false)); + tb->connect("pressed",this,"_tool_selected",make_binds(TOOL_CREATE_SCRIPT, false)); tb->set_tooltip(TTR("Create a new script for the selected node.")); tb->set_shortcut(ED_GET_SHORTCUT("scene_tree/add_script")); filter_hbc->add_child(tb); button_create_script=tb; + tb = memnew(ToolButton); + tb->connect("pressed", this, "_tool_selected", make_binds(TOOL_LOAD_SCRIPT, false)); + tb->set_tooltip(TTR("Load a script for the selected node.")); + tb->set_shortcut(ED_GET_SHORTCUT("scene_tree/load_script")); + filter_hbc->add_child(tb); + button_load_script = tb; + scene_tree = memnew( SceneTreeEditor(false,true,true )); vbc->add_child(scene_tree); @@ -1941,6 +2000,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor,Node *p_scene_root,EditorSelec scene_tree->connect("open_script",this,"_script_open_request"); scene_tree->connect("nodes_rearranged",this,"_nodes_dragged"); scene_tree->connect("files_dropped",this,"_files_dropped"); + scene_tree->connect("script_dropped",this,"_script_dropped"); scene_tree->connect("nodes_dragged",this,"_nodes_drag_begin"); scene_tree->get_scene_tree()->connect("item_double_clicked", this, "_focus_node"); @@ -1955,6 +2015,11 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor,Node *p_scene_root,EditorSelec add_child(create_dialog); create_dialog->connect("create",this,"_create"); + file_dialog = memnew(EditorFileDialog); + add_child(file_dialog); + file_dialog->hide(); + file_dialog->connect("file_selected", this, "_file_selected"); + //groups_editor = memnew( GroupsEditor ); //add_child(groups_editor); //groups_editor->set_undo_redo(&editor_data->get_undo_redo()); diff --git a/tools/editor/scene_tree_dock.h b/tools/editor/scene_tree_dock.h index 8933a03883..36d4a6d208 100644 --- a/tools/editor/scene_tree_dock.h +++ b/tools/editor/scene_tree_dock.h @@ -58,7 +58,8 @@ class SceneTreeDock : public VBoxContainer { TOOL_REPLACE, TOOL_CONNECT, TOOL_GROUP, - TOOL_SCRIPT, + TOOL_CREATE_SCRIPT, + TOOL_LOAD_SCRIPT, TOOL_MOVE_UP, TOOL_MOVE_DOWN, TOOL_DUPLICATE, @@ -75,10 +76,12 @@ class SceneTreeDock : public VBoxContainer { int current_option; CreateDialog *create_dialog; + EditorFileDialog *file_dialog; ToolButton *button_add; ToolButton *button_instance; ToolButton *button_create_script; + ToolButton *button_load_script; SceneTreeEditor *scene_tree; @@ -147,6 +150,7 @@ class SceneTreeDock : public VBoxContainer { void _nodes_dragged(Array p_nodes,NodePath p_to,int p_type); void _files_dropped(Vector<String> p_files,NodePath p_to,int p_type); + void _script_dropped(String p_file, NodePath p_to); void _tree_rmb(const Vector2& p_menu_pos); @@ -155,6 +159,7 @@ class SceneTreeDock : public VBoxContainer { void _perform_instance_scenes(const Vector<String>& p_files,Node* parent,int p_pos); void _replace_with_branch_scene(const String& p_file,Node* base); + void _file_selected(String p_file); protected: void _notification(int p_what); @@ -174,7 +179,7 @@ public: void fill_path_renames(Node* p_node, Node *p_new_parent, List<Pair<NodePath,NodePath> > *p_renames); void perform_node_renames(Node* p_base,List<Pair<NodePath,NodePath> > *p_renames, Map<Ref<Animation>, Set<int> > *r_rem_anims=NULL); SceneTreeEditor *get_tree_editor() { return scene_tree; } - + EditorData *get_editor_data() { return editor_data; } void open_script_dialog(Node* p_for_node); SceneTreeDock(EditorNode *p_editor,Node *p_scene_root,EditorSelection *p_editor_selection,EditorData &p_editor_data); diff --git a/tools/editor/scene_tree_editor.cpp b/tools/editor/scene_tree_editor.cpp index 53bfe8cc57..f5628d0c8f 100644 --- a/tools/editor/scene_tree_editor.cpp +++ b/tools/editor/scene_tree_editor.cpp @@ -971,6 +971,10 @@ Variant SceneTreeEditor::get_drag_data_fw(const Point2& p_point,Control* p_from) return drag_data; } +bool SceneTreeEditor::_is_script_type(const StringName &p_type) const { + return (script_types->find(p_type)); +} + bool SceneTreeEditor::can_drop_data_fw(const Point2& p_point,const Variant& p_data,Control* p_from) const { if (!can_rename) @@ -998,9 +1002,13 @@ bool SceneTreeEditor::can_drop_data_fw(const Point2& p_point,const Variant& p_da if (files.size()==0) return false; //weird + if (_is_script_type(EditorFileSystem::get_singleton()->get_file_type(files[0]))) { + tree->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM); + return true; + } for(int i=0;i<files.size();i++) { - String file = files[0]; + String file = files[i]; String ftype = EditorFileSystem::get_singleton()->get_file_type(file); if (ftype!="PackedScene") return false; @@ -1044,7 +1052,15 @@ void SceneTreeEditor::drop_data_fw(const Point2& p_point,const Variant& p_data,C if (String(d["type"])=="files") { - emit_signal("files_dropped",d["files"],np,section); + Vector<String> files = d["files"]; + + + String ftype = EditorFileSystem::get_singleton()->get_file_type(files[0]); + if (_is_script_type(ftype)) { + emit_signal("script_dropped", files[0],np); + } else { + emit_signal("files_dropped",files,np,section); + } } } @@ -1113,6 +1129,7 @@ void SceneTreeEditor::_bind_methods() { ADD_SIGNAL( MethodInfo("nodes_dragged") ); ADD_SIGNAL( MethodInfo("nodes_rearranged",PropertyInfo(Variant::ARRAY,"paths"),PropertyInfo(Variant::NODE_PATH,"to_path"),PropertyInfo(Variant::INT,"type") ) ); ADD_SIGNAL( MethodInfo("files_dropped",PropertyInfo(Variant::STRING_ARRAY,"files"),PropertyInfo(Variant::NODE_PATH,"to_path"),PropertyInfo(Variant::INT,"type") ) ); + ADD_SIGNAL( MethodInfo("script_dropped",PropertyInfo(Variant::STRING,"file"),PropertyInfo(Variant::NODE_PATH,"to_path"))); ADD_SIGNAL( MethodInfo("rmb_pressed",PropertyInfo(Variant::VECTOR2,"pos")) ) ; ADD_SIGNAL( MethodInfo("open") ); @@ -1209,12 +1226,16 @@ SceneTreeEditor::SceneTreeEditor(bool p_label,bool p_can_rename, bool p_can_open update_timer->set_wait_time(0.5); add_child(update_timer); + script_types = memnew(List<StringName>); + ObjectTypeDB::get_inheriters_from("Script", script_types); + } SceneTreeEditor::~SceneTreeEditor() { + memdelete(script_types); } diff --git a/tools/editor/scene_tree_editor.h b/tools/editor/scene_tree_editor.h index 79b7a64468..12d85ecdeb 100644 --- a/tools/editor/scene_tree_editor.h +++ b/tools/editor/scene_tree_editor.h @@ -137,6 +137,9 @@ class SceneTreeEditor : public Control { Timer* update_timer; + List<StringName> *script_types; + bool _is_script_type(const StringName &p_type) const; + public: void set_filter(const String& p_filter); |