From 6064a12fc8bcd7cd703ad420649987485ad49e2d Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 21 Oct 2013 22:15:10 +0200 Subject: [PATCH 01/59] Started adding string.find(), works without pattern --- .gitignore | 2 ++ src/lualib.js | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 413e796..ad24838 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ node_modules closurecompiler tests/*.js tests/*.js.tmp + +/GitIgnore diff --git a/src/lualib.js b/src/lualib.js index f25f352..f35fac8 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1064,9 +1064,26 @@ lua_libs["string"] = { "dump": function (func) { not_supported(); }, - "find": function () { - // TODO - not_supported(); + "find": function (s, pattern, index, plain) { + if (index == null) + index = 0; + if (index < 0) + index = s.length + index; + + /*if (plain != true) { + var match = s:match(pattern, index) + if (match != nil ) + pattern = match; + else + return [null]; + }*/ + + var start = s.indexOf(pattern, index); + + if (start == -1) + return [null]; + else + return [start+1, start+pattern.length]; }, "format": function (formatstring) { // TODO: Finish implementation From 3d30cd78510e02f88a2f9a8bc198d46aee1f691c Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 22 Oct 2013 09:58:02 +0200 Subject: [PATCH 02/59] Added string.gsub(), works without pattern --- src/lualib.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index f35fac8..ceb271d 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1094,8 +1094,27 @@ lua_libs["string"] = { not_supported(); }, "gsub": function (s, pattern, repl, n) { - // TODO - not_supported(); + // TODO : add pattern (regex) matching + var newS = s; + var oldS = s; + n = Number(n); // NaN if n == undefined + + var replCount = 0; + while (true) { + newS = oldS.replace(pattern, repl); + + if (newS != oldS) { + oldS = newS; + replCount++; + } + else + break; + + if (!isNaN(n) && replCount >= n) + break; + } + + return [newS, replCount]; }, "len": function (s) { return [check_string(s).length]; From f7c0f42e91abfa9651e2373a1907b72b8c2d7481 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 22 Oct 2013 15:01:00 +0200 Subject: [PATCH 03/59] Added luapattern_to_regex() and luareplacement_to_regex() functions --- src/lualib.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/lualib.js b/src/lualib.js index ceb271d..0af4154 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1043,6 +1043,38 @@ lua_libs["package"] = { } }; +function luapattern_to_regex( pattern ) { + var replacements = { + // character classes, metacharacters + + "%a": "[a-zA-Z]", // all letters what about wharacters with accent or special letter like ç + "%l": "[a-z]", + "%u": "[A-Z]", + "%d": "\\d", // all digit + "%p": "[,\?;\.:/!]", // all punctuation + "%s": "[ \t]", // all space characters are \r, \n, etc.. space characters ? + "%w": "\\w", + // "%g": "\\w", // all printable characters except space. + // "%c": "\\w", // Control character ? + + // pattern items, quantifiers + "\\]-": "]*", + "-\\)": "*)", + "%([0-9]){1}": "{$1}", + "%f\\[([^\\]]+)\\]": "[^$1]{1}[$1]{1}", // frontier pattern + } + + for (var luaExp in replacements) { + pattern = pattern.replace(new RegExp(luaExp, "g"), replacements[luaExp]); + } + + return pattern; +} + +function luareplacement_to_regex( pattern ) { + return pattern.replace(/%([0-9]+)/g, "$$$1"); +} + // string lua_libs["string"] = { "byte": function (s, i, j) { From f47b0dd125284724d30e5c6756fe0b2e34c073cf Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 22 Oct 2013 15:02:08 +0200 Subject: [PATCH 04/59] Updated string.gsub() with patterns --- src/lualib.js | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 0af4154..9f68f28 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1126,21 +1126,35 @@ lua_libs["string"] = { not_supported(); }, "gsub": function (s, pattern, repl, n) { - // TODO : add pattern (regex) matching var newS = s; var oldS = s; + pattern = luapattern_to_regex(pattern); + repl = luareplacement_to_regex(repl); n = Number(n); // NaN if n == undefined var replCount = 0; - while (true) { - newS = oldS.replace(pattern, repl); - - if (newS != oldS) { - oldS = newS; - replCount++; - } - else + var offset = 0; + var regex = new RegExp(pattern); + + var i = s.search(regex); + while (i > 0) { + var searchS = newS.substr(offset); // searchS the string in whih we will search and replace the pattern + // doing the replacement in a portion of the input string is necessary to match Lua's gsub() behavior + // which search for the pattern, replace then move forward and never looks back + + var matches = searchS.match(regex); + if (matches == null) break; + var patternS = matches[0]; + var patternLength = patternS.length; + var patternStartIndexInSearch = searchS.indexOf(patternS); + + newS = newS.replace(searchS, searchS.replace(regex, repl)); + + var diff = newS.length - oldS.length; + offset += patternStartIndexInSearch + patternLength + diff; // patternLength + diff is the length of the replacement + replCount++; + oldS = newS; if (!isNaN(n) && replCount >= n) break; From 8f00b4f5f696b5c5a92154e8e7d550287d916437 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 22 Oct 2013 15:42:02 +0200 Subject: [PATCH 05/59] Added string.match() with patterns, is working --- src/lualib.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 9f68f28..29b3788 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1168,9 +1168,14 @@ lua_libs["string"] = { "lower": function (s) { return [check_string(s).toLowerCase()]; }, - "match": function (s) { - // TODO - not_supported(); + "match": function (s, pattern, index) { + if (index == undefined) + index = 1; + else if (index < 0) + index = s.length + index; + index = index-1; + pattern = luapattern_to_regex(pattern); + return s.substr(index).match(new RegExp(pattern, "g")); }, "rep": function (s, n) { s = check_string(s); From 797937c9ff773678f66222f740ef7f4840af96eb Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 22 Oct 2013 19:14:19 +0200 Subject: [PATCH 06/59] Added missing replacements to luapattern_to_regex() --- src/lualib.js | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 29b3788..313e545 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1045,23 +1045,41 @@ lua_libs["package"] = { function luapattern_to_regex( pattern ) { var replacements = { - // character classes, metacharacters - - "%a": "[a-zA-Z]", // all letters what about wharacters with accent or special letter like ç - "%l": "[a-z]", - "%u": "[A-Z]", - "%d": "\\d", // all digit - "%p": "[,\?;\.:/!]", // all punctuation - "%s": "[ \t]", // all space characters are \r, \n, etc.. space characters ? - "%w": "\\w", - // "%g": "\\w", // all printable characters except space. - // "%c": "\\w", // Control character ? - // pattern items, quantifiers + "(%[a-zA-Z]{1})-": "$1*", // put this before the character classes "\\]-": "]*", "-\\)": "*)", + "- ": "* ", + "-$": "*", + // probably other cases of hyphens that should be converted to * "%([0-9]){1}": "{$1}", "%f\\[([^\\]]+)\\]": "[^$1]{1}[$1]{1}", // frontier pattern + + // character classes, metacharacters + "%a": "[a-zA-Z\u00C0-\u017F]", // all letters with accented characters À to ſ (shouldn't the down limit be much lower ?) + "%A": "[^a-zA-Z\u00C0-\u017F]", + + "%l": "[a-z\u00E0-\u00FF]", // à to ÿ + "%L": "[^a-z\u00E0-\u00FF]", + + "%u": "[A-Z\u00C0-\u00DF]", // À to ß + "%U": "[^A-Z\u00C0-\u00DF]", + // below character 00FF upper case and lowercase characters are mixed + + "%c": "[\u0000-\u001F]", // Control characters + "%C": "[^\u0000-\u001F]", + + "%p": "[,\?;\.:/!]", // all punctuation + "%P": "[^,\?;\.:/!]", + + "%d": "\\d", // all digit + "%D": "\\D", + "%s": "\\s", // all space characters Any difference between 'space' and 'whitespace' ? + "%S": "\\S", + "%w": "\\w", // all word (alphanum) characters + "%W": "\\W", + + // "%g": "", // all printable characters except space. 0021 (!) to ? } for (var luaExp in replacements) { From dcdd1d5f0f53115c9a336242e231ad8c85851996 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 22 Oct 2013 19:14:38 +0200 Subject: [PATCH 07/59] Fixed string.match() --- src/lualib.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index 313e545..aead1d2 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1192,8 +1192,14 @@ lua_libs["string"] = { else if (index < 0) index = s.length + index; index = index-1; + pattern = luapattern_to_regex(pattern); - return s.substr(index).match(new RegExp(pattern, "g")); + var matches = s.substr(index).match(pattern); + + if (matches == null) + return [null]; + else + return [matches[0]]; }, "rep": function (s, n) { s = check_string(s); From 55ec5bd885a9eaea00172a5f55f55aefcb9d2b15 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 22 Oct 2013 19:37:07 +0200 Subject: [PATCH 08/59] Added patterns to string.find(), works OK --- src/lualib.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index aead1d2..0c5cc0b 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1115,25 +1115,30 @@ lua_libs["string"] = { not_supported(); }, "find": function (s, pattern, index, plain) { - if (index == null) - index = 0; - if (index < 0) + if (index == undefined) + index = 1; + else if (index < 0) index = s.length + index; + + index = index-1; + s = s.substr(index); - /*if (plain != true) { - var match = s:match(pattern, index) - if (match != nil ) - pattern = match; + if (plain !== true) { + pattern = luapattern_to_regex(pattern); + var matches = s.match(pattern); + if (matches != null) + pattern = matches[0]; else return [null]; - }*/ - - var start = s.indexOf(pattern, index); + } - if (start == -1) - return [null]; + start = s.indexOf(pattern); + if (start != -1) { + start += index; + return [start, start+pattern.length-1]; + } else - return [start+1, start+pattern.length]; + return [null]; }, "format": function (formatstring) { // TODO: Finish implementation From bb5cbe50a7d64ef9bba9a0b4138d20cf035069da Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 22 Oct 2013 19:52:03 +0200 Subject: [PATCH 09/59] Added use of check_string() in string.find(), gsub() and match() --- src/lualib.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 0c5cc0b..e540d4e 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1115,6 +1115,7 @@ lua_libs["string"] = { not_supported(); }, "find": function (s, pattern, index, plain) { + s = check_string(s); if (index == undefined) index = 1; else if (index < 0) @@ -1149,8 +1150,8 @@ lua_libs["string"] = { not_supported(); }, "gsub": function (s, pattern, repl, n) { - var newS = s; - var oldS = s; + var newS = check_string(s); + var oldS = newS; pattern = luapattern_to_regex(pattern); repl = luareplacement_to_regex(repl); n = Number(n); // NaN if n == undefined @@ -1161,9 +1162,12 @@ lua_libs["string"] = { var i = s.search(regex); while (i > 0) { - var searchS = newS.substr(offset); // searchS the string in whih we will search and replace the pattern + var searchS = newS.substr(offset); + // searchS the portion of the string in which we will search and replace the pattern + // offset is the position in newS of the first character of the portion that hasn't been modified yet + // doing the replacement in a portion of the input string is necessary to match Lua's gsub() behavior - // which search for the pattern, replace then move forward and never looks back + // which search for the pattern, replace then move forward and never look back var matches = searchS.match(regex); if (matches == null) @@ -1192,6 +1196,7 @@ lua_libs["string"] = { return [check_string(s).toLowerCase()]; }, "match": function (s, pattern, index) { + s = check_string(s); if (index == undefined) index = 1; else if (index < 0) From 22e89c2dd7b74f6b438e8092862411a200d06bae Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 23 Oct 2013 00:01:45 +0200 Subject: [PATCH 10/59] Started adding string.gmatch() and lua_gmatch_next() --- src/lualib.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index e540d4e..cde68ec 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1093,6 +1093,17 @@ function luareplacement_to_regex( pattern ) { return pattern.replace(/%([0-9]+)/g, "$$$1"); } +function lua_gmatch_next(data) { + var match = data.s.match(pattern); + if (match == null) + return ""; + match = match[0]; + var matchStartPos = data.s.search(match); + data.s = data.s.substr(matchStartPos+match.length); + // if (results.length > 1) results = results.slice(1); // remove first result entry which is the whole match + return match; +} + // string lua_libs["string"] = { "byte": function (s, i, j) { @@ -1146,8 +1157,8 @@ lua_libs["string"] = { return ["[" + slice(arguments, 1).join(", ") + "]" + arguments[0]]; }, "gmatch": function (s, pattern) { - // TODO - not_supported(); + s = check_string(s); + return [lua_gmatch_next, {s:s, pattern:luapattern_to_regex(pattern)}] }, "gsub": function (s, pattern, repl, n) { var newS = check_string(s); From 05b3196559729026343d874f09128cdbd441ac9a Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 23 Oct 2013 11:12:27 +0200 Subject: [PATCH 11/59] Fixed string.gmatch() and lua_gmatch_next(), works OK --- src/lualib.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index cde68ec..f2afb7c 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1094,14 +1094,13 @@ function luareplacement_to_regex( pattern ) { } function lua_gmatch_next(data) { - var match = data.s.match(pattern); + var match = data.s.match(data.pattern); if (match == null) - return ""; + return [null]; match = match[0]; var matchStartPos = data.s.search(match); data.s = data.s.substr(matchStartPos+match.length); - // if (results.length > 1) results = results.slice(1); // remove first result entry which is the whole match - return match; + return [match]; } // string @@ -1158,6 +1157,7 @@ lua_libs["string"] = { }, "gmatch": function (s, pattern) { s = check_string(s); + // an object is used to keep the modifs to the string accross calls to lua_gmatch_next() return [lua_gmatch_next, {s:s, pattern:luapattern_to_regex(pattern)}] }, "gsub": function (s, pattern, repl, n) { From e4bc6853179d2b3ef2f3d00f0c685dc36506a1c4 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sat, 26 Oct 2013 00:09:47 +0200 Subject: [PATCH 12/59] Began updating string.format(), will use sprintf() bu need fork --- src/lualib.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index f2afb7c..3228d49 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1103,6 +1103,11 @@ function lua_gmatch_next(data) { return [match]; } +// SPRINTF +// from https://github.com/alexei/sprintf.js +/*! sprintf.js | Copyright (c) 2007-2013 Alexandru Marasteanu | 3 clause BSD license */ +(function(e){function r(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}function i(e,t){for(var n=[];t>0;n[--t]=e);return n.join("")}var t=function(){return t.cache.hasOwnProperty(arguments[0])||(t.cache[arguments[0]]=t.parse(arguments[0])),t.format.call(null,t.cache[arguments[0]],arguments)};t.format=function(e,n){var s=1,o=e.length,u="",a,f=[],l,c,h,p,d,v;for(l=0;l>>=0;break;case"x":a=a.toString(16);break;case"X":a=a.toString(16).toUpperCase()}a=/[def]/.test(h[8])&&h[3]&&a>=0?"+"+a:a,d=h[4]?h[4]=="0"?"0":h[4].charAt(1):" ",v=h[6]-String(a).length,p=h[6]?i(d,v):"",f.push(h[5]?a+p:p+a)}}return f.join("")},t.cache={},t.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push("%");else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw"[sprintf] huh?";if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw"[sprintf] huh?";s.push(u[1]);while((o=o.substring(u[0].length))!=="")if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw"[sprintf] huh?";s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw"[sprintf] mixing positional and named placeholders is not (yet) supported";r.push(n)}t=t.substring(n[0].length)}return r};var n=function(e,n,r){return r=n.slice(0),r.splice(0,0,e),t.apply(null,r)};e.sprintf=t,e.vsprintf=n})(typeof exports!="undefined"?exports:window); + // string lua_libs["string"] = { "byte": function (s, i, j) { @@ -1151,9 +1156,8 @@ lua_libs["string"] = { else return [null]; }, - "format": function (formatstring) { - // TODO: Finish implementation - return ["[" + slice(arguments, 1).join(", ") + "]" + arguments[0]]; + "format": function () { + return [sprintf.apply(this, arguments)]; }, "gmatch": function (s, pattern) { s = check_string(s); From c16087f5166417bb2bd666e3621feb868ff106a8 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sun, 27 Oct 2013 12:25:55 +0100 Subject: [PATCH 13/59] Fixed wrong index in string.find() --- src/lualib.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 3228d49..d608949 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1064,7 +1064,7 @@ function luapattern_to_regex( pattern ) { "%u": "[A-Z\u00C0-\u00DF]", // À to ß "%U": "[^A-Z\u00C0-\u00DF]", - // below character 00FF upper case and lowercase characters are mixed + // below character 00FF, upper case and lowercase characters are mixed "%c": "[\u0000-\u001F]", // Control characters "%C": "[^\u0000-\u001F]", @@ -1079,7 +1079,7 @@ function luapattern_to_regex( pattern ) { "%w": "\\w", // all word (alphanum) characters "%W": "\\W", - // "%g": "", // all printable characters except space. 0021 (!) to ? + // "%g": "", // all printable characters except space. ! to ? } for (var luaExp in replacements) { @@ -1103,11 +1103,6 @@ function lua_gmatch_next(data) { return [match]; } -// SPRINTF -// from https://github.com/alexei/sprintf.js -/*! sprintf.js | Copyright (c) 2007-2013 Alexandru Marasteanu | 3 clause BSD license */ -(function(e){function r(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}function i(e,t){for(var n=[];t>0;n[--t]=e);return n.join("")}var t=function(){return t.cache.hasOwnProperty(arguments[0])||(t.cache[arguments[0]]=t.parse(arguments[0])),t.format.call(null,t.cache[arguments[0]],arguments)};t.format=function(e,n){var s=1,o=e.length,u="",a,f=[],l,c,h,p,d,v;for(l=0;l>>=0;break;case"x":a=a.toString(16);break;case"X":a=a.toString(16).toUpperCase()}a=/[def]/.test(h[8])&&h[3]&&a>=0?"+"+a:a,d=h[4]?h[4]=="0"?"0":h[4].charAt(1):" ",v=h[6]-String(a).length,p=h[6]?i(d,v):"",f.push(h[5]?a+p:p+a)}}return f.join("")},t.cache={},t.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push("%");else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw"[sprintf] huh?";if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw"[sprintf] huh?";s.push(u[1]);while((o=o.substring(u[0].length))!=="")if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw"[sprintf] huh?";s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw"[sprintf] mixing positional and named placeholders is not (yet) supported";r.push(n)}t=t.substring(n[0].length)}return r};var n=function(e,n,r){return r=n.slice(0),r.splice(0,0,e),t.apply(null,r)};e.sprintf=t,e.vsprintf=n})(typeof exports!="undefined"?exports:window); - // string lua_libs["string"] = { "byte": function (s, i, j) { @@ -1135,8 +1130,7 @@ lua_libs["string"] = { index = 1; else if (index < 0) index = s.length + index; - - index = index-1; + index = index-1; // -1 because Lua's arrays index starts at 1 instead of 0 s = s.substr(index); if (plain !== true) { @@ -1150,7 +1144,7 @@ lua_libs["string"] = { start = s.indexOf(pattern); if (start != -1) { - start += index; + start += index+1; // +1 because Lua's arrays index starts at 1 instead of 0 return [start, start+pattern.length-1]; } else From 0889cd17a902af9e6fcbe8bdbcd71bbb6b77b984 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Thu, 31 Oct 2013 14:45:59 +0100 Subject: [PATCH 14/59] Added sprintf.js-lua.js inside string.format() --- src/lualib.js | 140 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index d608949..90bb82f 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1151,8 +1151,144 @@ lua_libs["string"] = { return [null]; }, "format": function () { - return [sprintf.apply(this, arguments)]; - }, + /*! sprintf.js | Copyright (c) 2007-2013 Alexandru Marasteanu | 3 clause BSD license */ + /*! sprintf.js-lua.js | sprintf.js, forked to match Lua's string.format() behavior (and for use in lua.js) by Florent POUJOL */ + var sprintf = function() { + if (!sprintf.cache.hasOwnProperty(arguments[0])) { + sprintf.cache[arguments[0]] = sprintf.parse(arguments[0]); + } + return sprintf.format.call(null, sprintf.cache[arguments[0]], arguments); + }; + + sprintf.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]); + if (node_type === 'string') { + output.push(parse_tree[i]); + } + else if (node_type === 'array') { + match = parse_tree[i]; // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor]; + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw(sprintf('[sprintf.format] property "%s" does not exist', match[2][k])); + } + arg = arg[match[2][k]]; + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]]; + } + else { // positional argument (implicit) + arg = argv[cursor++]; + } + + if (/[^sq]/.test(match[8]) && (get_type(arg) != 'number')) { + throw(sprintf('[sprintf.format] expecting number but found %s', get_type(arg))); + } + switch (match[8]) { + // case 'b': arg = arg.toString(2); break; + case 'c': arg = String.fromCharCode(arg); break; + case 'i': + case 'd': arg = parseInt(arg, 10); break; // int + case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(6); break; + case 'E': arg = match[7] ? arg.toExponential(match[7]).toUpperCase() : arg.toExponential(6).toUpperCase(); break; + case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg).toFixed(6); break; // float + // case 'g': // from C++ doc : use the sortest representation e or f > in Lua, has only 5 digit after the coma instead of 6 for floats + // case 'G': // from C++ doc : use the sortest representation E or F (F does not exist in Lua) + case 'o': arg = arg.toString(8); break; // octal + case 'q': arg = '"'+((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)+'"'; break; + case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; + case 'u': arg = arg >>> 0; break; // unsigned integer + case 'x': arg = arg.toString(16); break; // hexadecimal + case 'X': arg = arg.toString(16).toUpperCase(); break; + // TODO hexa : some wrong values returned : -100 become -64 instead of ffffff9c (same for octal) + } + if (/[eE]/.test(match[8])) { + //make the exponent (exp[2]) always at least 3 digit (ie : 3.14E+003) + var exp = /^(.+\+)(\d+)$/.exec(arg); + if (exp != null) { + if (exp[2].length == 1) arg = exp[1]+"00"+exp[2]; + else if (exp[2].length == 2) arg = exp[1]+"0"+exp[2]; + } + } + arg = (/[dieEf]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); + pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; + pad_length = match[6] - String(arg).length; + pad = match[6] ? str_repeat(pad_character, pad_length) : ''; + output.push(match[5] ? arg + pad : pad + arg); + } + } + return output.join(''); + }; + + sprintf.cache = {}; + + sprintf.parse = function(fmt) { // fmt = format string + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; + while (_fmt) { + // \x25 = % + if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { // no % found + parse_tree.push(match[0]); + } + else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { // 2 consecutive % found + parse_tree.push('%'); + } + else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([cdeEfgGiouxXqs])/.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1; + var field_list = [], replacement_field = match[2], field_match = []; + if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else { + throw('[sprintf.parse] No field_match found in replacement_field 1.'); + } + } + } + else { + throw('[sprintf.parse] No field_match found in replacement_field 2.'); + } + match[2] = field_list; + } + else { + arg_names |= 2; + } + if (arg_names === 3) { + throw('[sprintf.parse] mixing positional and named placeholders is not (yet) supported'); + } + parse_tree.push(match); + } + else { + throw('[sprintf.parse] Format string "'+fmt+'" not recognized.'); + } + _fmt = _fmt.substring(match[0].length); + } + return parse_tree; + }; + + /** + * helpers + */ + function get_type(variable) { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); + } + + function str_repeat(input, multiplier) { + for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} + return output.join(''); + } + + return [sprintf.apply(this, arguments)]; + }, // format "gmatch": function (s, pattern) { s = check_string(s); // an object is used to keep the modifs to the string accross calls to lua_gmatch_next() From 7c62d3b48d168f03ca9e5ba86f49b4079975fe3f Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Thu, 31 Oct 2013 21:02:49 +0100 Subject: [PATCH 15/59] Fixed some comments/indentation --- src/lualib.js | 139 +++++++++++++++++++++++++------------------------- 1 file changed, 70 insertions(+), 69 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 90bb82f..41430d4 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1051,7 +1051,7 @@ function luapattern_to_regex( pattern ) { "-\\)": "*)", "- ": "* ", "-$": "*", - // probably other cases of hyphens that should be converted to * + // TODO : probably other cases of hyphens that should be converted to * "%([0-9]){1}": "{$1}", "%f\\[([^\\]]+)\\]": "[^$1]{1}[$1]{1}", // frontier pattern @@ -1059,11 +1059,11 @@ function luapattern_to_regex( pattern ) { "%a": "[a-zA-Z\u00C0-\u017F]", // all letters with accented characters À to ſ (shouldn't the down limit be much lower ?) "%A": "[^a-zA-Z\u00C0-\u017F]", - "%l": "[a-z\u00E0-\u00FF]", // à to ÿ - "%L": "[^a-z\u00E0-\u00FF]", - "%u": "[A-Z\u00C0-\u00DF]", // À to ß "%U": "[^A-Z\u00C0-\u00DF]", + + "%l": "[a-z\u00E0-\u00FF]", // à to ÿ + "%L": "[^a-z\u00E0-\u00FF]", // below character 00FF, upper case and lowercase characters are mixed "%c": "[\u0000-\u001F]", // Control characters @@ -1079,7 +1079,7 @@ function luapattern_to_regex( pattern ) { "%w": "\\w", // all word (alphanum) characters "%W": "\\W", - // "%g": "", // all printable characters except space. ! to ? + // "%g": "", // TODO : all printable characters except space. (all characters but the control characters (%C)) } for (var luaExp in replacements) { @@ -1151,7 +1151,6 @@ lua_libs["string"] = { return [null]; }, "format": function () { - /*! sprintf.js | Copyright (c) 2007-2013 Alexandru Marasteanu | 3 clause BSD license */ /*! sprintf.js-lua.js | sprintf.js, forked to match Lua's string.format() behavior (and for use in lua.js) by Florent POUJOL */ var sprintf = function() { if (!sprintf.cache.hasOwnProperty(arguments[0])) { @@ -1168,63 +1167,63 @@ lua_libs["string"] = { output.push(parse_tree[i]); } else if (node_type === 'array') { - match = parse_tree[i]; // convenience purposes only - if (match[2]) { // keyword argument - arg = argv[cursor]; - for (k = 0; k < match[2].length; k++) { - if (!arg.hasOwnProperty(match[2][k])) { - throw(sprintf('[sprintf.format] property "%s" does not exist', match[2][k])); - } - arg = arg[match[2][k]]; - } - } - else if (match[1]) { // positional argument (explicit) - arg = argv[match[1]]; - } - else { // positional argument (implicit) - arg = argv[cursor++]; + match = parse_tree[i]; // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor]; + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw(sprintf('[string.format()] property "%s" does not exist', match[2][k])); } + arg = arg[match[2][k]]; + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]]; + } + else { // positional argument (implicit) + arg = argv[cursor++]; + } - if (/[^sq]/.test(match[8]) && (get_type(arg) != 'number')) { - throw(sprintf('[sprintf.format] expecting number but found %s', get_type(arg))); - } - switch (match[8]) { - // case 'b': arg = arg.toString(2); break; - case 'c': arg = String.fromCharCode(arg); break; - case 'i': - case 'd': arg = parseInt(arg, 10); break; // int - case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(6); break; - case 'E': arg = match[7] ? arg.toExponential(match[7]).toUpperCase() : arg.toExponential(6).toUpperCase(); break; - case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg).toFixed(6); break; // float - // case 'g': // from C++ doc : use the sortest representation e or f > in Lua, has only 5 digit after the coma instead of 6 for floats - // case 'G': // from C++ doc : use the sortest representation E or F (F does not exist in Lua) - case 'o': arg = arg.toString(8); break; // octal - case 'q': arg = '"'+((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)+'"'; break; - case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; - case 'u': arg = arg >>> 0; break; // unsigned integer - case 'x': arg = arg.toString(16); break; // hexadecimal - case 'X': arg = arg.toString(16).toUpperCase(); break; - // TODO hexa : some wrong values returned : -100 become -64 instead of ffffff9c (same for octal) - } - if (/[eE]/.test(match[8])) { - //make the exponent (exp[2]) always at least 3 digit (ie : 3.14E+003) - var exp = /^(.+\+)(\d+)$/.exec(arg); - if (exp != null) { - if (exp[2].length == 1) arg = exp[1]+"00"+exp[2]; - else if (exp[2].length == 2) arg = exp[1]+"0"+exp[2]; - } - } - arg = (/[dieEf]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); - pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; - pad_length = match[6] - String(arg).length; - pad = match[6] ? str_repeat(pad_character, pad_length) : ''; - output.push(match[5] ? arg + pad : pad + arg); - } + if (/[^sq]/.test(match[8]) && (get_type(arg) != 'number')) { + throw(sprintf('[string.format()] expecting number but found %s', get_type(arg))); + } + switch (match[8]) { + // case 'b': arg = arg.toString(2); break; + case 'c': arg = String.fromCharCode(arg); break; + case 'i': + case 'd': arg = parseInt(arg, 10); break; // int + case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(6); break; + case 'E': arg = match[7] ? arg.toExponential(match[7]).toUpperCase() : arg.toExponential(6).toUpperCase(); break; + case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg).toFixed(6); break; // float + // case 'g': // from C++ doc : use the sortest representation e or f > in Lua, has only 5 digit after the coma instead of 6 for floats + // case 'G': // from C++ doc : use the sortest representation E or F (F does not exist in Lua) + case 'o': arg = arg.toString(8); break; // octal + case 'q': arg = '"'+((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)+'"'; break; + case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; + case 'u': arg = arg >>> 0; break; // unsigned integer + case 'x': arg = arg.toString(16); break; // hexadecimal + case 'X': arg = arg.toString(16).toUpperCase(); break; + // TODO hexa : some wrong values returned : -100 become -64 instead of ffffff9c (same for octal) + } + if (/[eE]/.test(match[8])) { + //make the exponent (exp[2]) always at least 3 digit (ie : 3.14E+003) + var exp = /^(.+\+)(\d+)$/.exec(arg); + if (exp != null) { + if (exp[2].length == 1) arg = exp[1]+"00"+exp[2]; + else if (exp[2].length == 2) arg = exp[1]+"0"+exp[2]; } - return output.join(''); - }; + } + arg = (/[dieEf]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); + pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; + pad_length = match[6] - String(arg).length; + pad = match[6] ? str_repeat(pad_character, pad_length) : ''; + output.push(match[5] ? arg + pad : pad + arg); + } + } + return output.join(''); + }; - sprintf.cache = {}; + sprintf.cache = {}; sprintf.parse = function(fmt) { // fmt = format string var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; @@ -1250,12 +1249,12 @@ lua_libs["string"] = { field_list.push(field_match[1]); } else { - throw('[sprintf.parse] No field_match found in replacement_field 1.'); + throw('[string.format()] No field_match found in replacement_field 1.'); } } } else { - throw('[sprintf.parse] No field_match found in replacement_field 2.'); + throw('[string.format()] No field_match found in replacement_field 2.'); } match[2] = field_list; } @@ -1263,12 +1262,12 @@ lua_libs["string"] = { arg_names |= 2; } if (arg_names === 3) { - throw('[sprintf.parse] mixing positional and named placeholders is not (yet) supported'); + throw('[string.format()] mixing positional and named placeholders is not (yet) supported'); } parse_tree.push(match); } else { - throw('[sprintf.parse] Format string "'+fmt+'" not recognized.'); + throw('[string.format()] Format string "'+fmt+'" not recognized.'); } _fmt = _fmt.substring(match[0].length); } @@ -1278,17 +1277,19 @@ lua_libs["string"] = { /** * helpers */ - function get_type(variable) { + function get_type(variable) { return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); } function str_repeat(input, multiplier) { - for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} - return output.join(''); - } + for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} + return output.join(''); + } - return [sprintf.apply(this, arguments)]; - }, // format + if (arguments.length > 0) + arguments[0] = check_string(s); + return [sprintf.apply(this, arguments)]; + }, // string.format() "gmatch": function (s, pattern) { s = check_string(s); // an object is used to keep the modifs to the string accross calls to lua_gmatch_next() From d4e0ecbd15bc0ab2ce274235c14cadc6eba8d938 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Fri, 1 Nov 2013 16:14:31 +0100 Subject: [PATCH 16/59] Fixed two bugs in string.format() and string.find() --- src/lualib.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 41430d4..108b970 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1142,7 +1142,7 @@ lua_libs["string"] = { return [null]; } - start = s.indexOf(pattern); + var start = s.indexOf(pattern); if (start != -1) { start += index+1; // +1 because Lua's arrays index starts at 1 instead of 0 return [start, start+pattern.length-1]; @@ -1151,7 +1151,8 @@ lua_libs["string"] = { return [null]; }, "format": function () { - /*! sprintf.js-lua.js | sprintf.js, forked to match Lua's string.format() behavior (and for use in lua.js) by Florent POUJOL */ + /*! sprintf.js | Copyright (c) 2007-2013 Alexandru Marasteanu | 3 clause BSD license */ + // forked to match Lua's string.format() behavior (and for use in lua.js) by Florent POUJOL var sprintf = function() { if (!sprintf.cache.hasOwnProperty(arguments[0])) { sprintf.cache[arguments[0]] = sprintf.parse(arguments[0]); @@ -1287,7 +1288,7 @@ lua_libs["string"] = { } if (arguments.length > 0) - arguments[0] = check_string(s); + arguments[0] = check_string(arguments[0]); return [sprintf.apply(this, arguments)]; }, // string.format() "gmatch": function (s, pattern) { From f8cd83845af4e3e42b17f470d6a67009e5c69e4c Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Fri, 1 Nov 2013 16:26:42 +0100 Subject: [PATCH 17/59] Moved lua_gmatch_next() inside string.gmatch() + moved luareplacement_to_regex() in string.gsub() --- src/lualib.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 108b970..65b491e 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1089,20 +1089,6 @@ function luapattern_to_regex( pattern ) { return pattern; } -function luareplacement_to_regex( pattern ) { - return pattern.replace(/%([0-9]+)/g, "$$$1"); -} - -function lua_gmatch_next(data) { - var match = data.s.match(data.pattern); - if (match == null) - return [null]; - match = match[0]; - var matchStartPos = data.s.search(match); - data.s = data.s.substr(matchStartPos+match.length); - return [match]; -} - // string lua_libs["string"] = { "byte": function (s, i, j) { @@ -1293,6 +1279,16 @@ lua_libs["string"] = { }, // string.format() "gmatch": function (s, pattern) { s = check_string(s); + var lua_gmatch_next = function(data) { + var match = data.s.match(data.pattern); + if (match == null) + return [null]; + match = match[0]; + var matchStartPos = data.s.search(match); + data.s = data.s.substr(matchStartPos+match.length); + return [match]; + } + // an object is used to keep the modifs to the string accross calls to lua_gmatch_next() return [lua_gmatch_next, {s:s, pattern:luapattern_to_regex(pattern)}] }, @@ -1300,7 +1296,7 @@ lua_libs["string"] = { var newS = check_string(s); var oldS = newS; pattern = luapattern_to_regex(pattern); - repl = luareplacement_to_regex(repl); + repl = repl.replace(/%([0-9]+)/g, "$$$1"); n = Number(n); // NaN if n == undefined var replCount = 0; From 595e9cc484610acbdb8efdfa38671d847ccbc158 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Fri, 1 Nov 2013 17:16:42 +0100 Subject: [PATCH 18/59] Fixed typo in os.clock() --- src/lualib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index 65b491e..86da5c5 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -959,7 +959,7 @@ lua_libs["os"] = { "clock": function () { // This function is supposed to return the time the script has been executing // not the time since it started, but I don't know of a way to do this. - return [(((new Date()).getTime()) / 1000) - _lua_clock_script]; + return [(((new Date()).getTime()) / 1000) - _lua_clock_start]; }, "date": function (format, time) { // TODO From dd145214e463ab0bc5c6b36108d861c43b9331b0 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Fri, 1 Nov 2013 19:00:16 +0100 Subject: [PATCH 19/59] Fixed exception message in sprintf.format --- src/lualib.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 86da5c5..c1c4ca3 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1159,7 +1159,7 @@ lua_libs["string"] = { arg = argv[cursor]; for (k = 0; k < match[2].length; k++) { if (!arg.hasOwnProperty(match[2][k])) { - throw(sprintf('[string.format()] property "%s" does not exist', match[2][k])); + throw('[string.format()] property "'+match[2][k]+'" does not exist'); } arg = arg[match[2][k]]; } @@ -1172,7 +1172,7 @@ lua_libs["string"] = { } if (/[^sq]/.test(match[8]) && (get_type(arg) != 'number')) { - throw(sprintf('[string.format()] expecting number but found %s', get_type(arg))); + throw('[string.format()] expecting number but found '+get_type(arg)); } switch (match[8]) { // case 'b': arg = arg.toString(2); break; From 72b2ff2a17a9163559ab2c4c162f0afa314cc086 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 4 Nov 2013 21:49:58 +0100 Subject: [PATCH 20/59] Fixed a bug preventing arrays to be looped upon by pairs(). --- src/lualib.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lualib.js b/src/lualib.js index c1c4ca3..56a989d 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -629,6 +629,8 @@ var lua_core = { } if (table.arraymode) { var j = table.uints.length; + if (j == null) // sometimes table.uints has no length property. Is set in lua_len(). + j = lua_len(table); while (j-- > 0) { if (table.uints[j] != null) { props.push(j + 1); From 7b9fb9153a65037b3a32d24fda578a9271bb9fa4 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 5 Nov 2013 16:04:35 +0100 Subject: [PATCH 21/59] Fixed yesterday's fix (allow some arrays to be looped by pairs()) --- src/lualib.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 56a989d..6db72b1 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -629,8 +629,12 @@ var lua_core = { } if (table.arraymode) { var j = table.uints.length; - if (j == null) // sometimes table.uints has no length property. Is set in lua_len(). - j = lua_len(table); + if (j == null) { // sometimes table.uints has no length property. Is set in lua_len(). + j = 0; + for (i in table.uints) + j++; + table.uints.length = j; + } while (j-- > 0) { if (table.uints[j] != null) { props.push(j + 1); From e4ab72981942083f42724e275477bc99a838362a Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sat, 9 Nov 2013 12:40:24 +0100 Subject: [PATCH 22/59] Fixed wrong behavior of string.gsub() + added support for functions as replacement --- src/lualib.js | 59 ++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 6db72b1..6cf590d 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1298,45 +1298,46 @@ lua_libs["string"] = { // an object is used to keep the modifs to the string accross calls to lua_gmatch_next() return [lua_gmatch_next, {s:s, pattern:luapattern_to_regex(pattern)}] }, - "gsub": function (s, pattern, repl, n) { - var newS = check_string(s); - var oldS = newS; - pattern = luapattern_to_regex(pattern); - repl = repl.replace(/%([0-9]+)/g, "$$$1"); - n = Number(n); // NaN if n == undefined + "gsub": function (s, pattern, replacement, n) { + s = check_string(s); - var replCount = 0; - var offset = 0; + pattern = luapattern_to_regex(pattern); var regex = new RegExp(pattern); - var i = s.search(regex); - while (i > 0) { - var searchS = newS.substr(offset); - // searchS the portion of the string in which we will search and replace the pattern - // offset is the position in newS of the first character of the portion that hasn't been modified yet + var replacementCount = 0, replacementType = typeof replacement; + if (replacementType == "string") // replacement can be a function + replacement = replacement.replace(/%([0-9]+)/g, "$$$1"); + + n = Number(n); // NaN if n == undefined - // doing the replacement in a portion of the input string is necessary to match Lua's gsub() behavior - // which search for the pattern, replace then move forward and never look back + var matches = s.match( new RegExp(pattern , 'g') ); + var newS = ""; - var matches = searchS.match(regex); - if (matches == null) - break; - var patternS = matches[0]; - var patternLength = patternS.length; - var patternStartIndexInSearch = searchS.indexOf(patternS); + for (var i in matches) { + var match = matches[i]; + var matchEndIndex = s.search( match ) + match.length; + var matchChunk = s.substr( 0, matchEndIndex ); + var newMatchChunk = ""; - newS = newS.replace(searchS, searchS.replace(regex, repl)); - - var diff = newS.length - oldS.length; - offset += patternStartIndexInSearch + patternLength + diff; // patternLength + diff is the length of the replacement - replCount++; - oldS = newS; + if (replacementType == "string") { + newMatchChunk = matchChunk.replace( regex, replacement ); + } + else if (replacementType == "function") { + newMatchChunk = matchChunk.replace( match, replacement( match ) ); + } - if (!isNaN(n) && replCount >= n) + newS += newMatchChunk; + s = s.substr( matchEndIndex ); + + replacementCount++; + if (!isNaN(n) && ++replacementCount >= n) + break; + if (pattern[0] == "^" || pattern[pattern.length] == "$") break; } - return [newS, replCount]; + newS += s; + return [newS, replacementCount]; }, "len": function (s) { return [check_string(s).length]; From 9c0302484d5bcb9170bb6233c9d466beb877b7ac Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sat, 9 Nov 2013 18:45:53 +0100 Subject: [PATCH 23/59] Added support for escaped special characters in luapattern_to_js() function --- src/lualib.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index 6cf590d..b956840 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1056,7 +1056,7 @@ function luapattern_to_regex( pattern ) { "\\]-": "]*", "-\\)": "*)", "- ": "* ", - "-$": "*", + "-$": "*$", // TODO : probably other cases of hyphens that should be converted to * "%([0-9]){1}": "{$1}", "%f\\[([^\\]]+)\\]": "[^$1]{1}[$1]{1}", // frontier pattern @@ -1086,6 +1086,20 @@ function luapattern_to_regex( pattern ) { "%W": "\\W", // "%g": "", // TODO : all printable characters except space. (all characters but the control characters (%C)) + + // escape special characters + "%\\.": "\\.", + "%\\^": "\\^", + "%\\$": "\\$", + "%\\(": "\\(", + "%\\)": "\\)", + "%\\[": "\\[", + "%\\]": "\\]", + "%\\*": "\\*", + "%\\+": "\\+", + "%\\-": "\\-", + "%\\?": "\\?", + "%%": "%", } for (var luaExp in replacements) { From 74c1809483e4d076395dadea0446c2c5615ae593 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sat, 9 Nov 2013 18:47:57 +0100 Subject: [PATCH 24/59] lua_gmatch_next() now correctly returns the capture (if any in the pattern) instead of the whole match --- src/lualib.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index b956840..7f329eb 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1298,19 +1298,25 @@ lua_libs["string"] = { return [sprintf.apply(this, arguments)]; }, // string.format() "gmatch": function (s, pattern) { - s = check_string(s); var lua_gmatch_next = function(data) { + console.log(data.pattern); + return; var match = data.s.match(data.pattern); + if (match == null) return [null]; - match = match[0]; - var matchStartPos = data.s.search(match); - data.s = data.s.substr(matchStartPos+match.length); + + if (match[1] != null) // there was a capture, match[0] is the whole matched expression + match = match[1]; // match[0] is the whole match, not the first capture + else + match = match[0]; + + data.s = data.s.substr( data.s.search(match) + match.length ); return [match]; } // an object is used to keep the modifs to the string accross calls to lua_gmatch_next() - return [lua_gmatch_next, {s:s, pattern:luapattern_to_regex(pattern)}] + return [lua_gmatch_next, {s:check_string(s), pattern:luapattern_to_regex(pattern)}] }, "gsub": function (s, pattern, replacement, n) { s = check_string(s); From 19b309e460c90ec25039fed83dd4ef8cb14ac26b Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sat, 9 Nov 2013 18:48:49 +0100 Subject: [PATCH 25/59] Fixed string.gsub() when the pattern must match at the end of th string (only partial support, several cases will not work) --- src/lualib.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lualib.js b/src/lualib.js index 7f329eb..8fd7a85 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1330,6 +1330,14 @@ lua_libs["string"] = { n = Number(n); // NaN if n == undefined + var reverse = false; + if (pattern[pattern.length-1] == "$") { + reverse = true; + s = s.split("").reverse().join(""); + pattern = "^" + pattern.substr(0, pattern.length-1); + regex = new RegExp(pattern); + } + var matches = s.match( new RegExp(pattern , 'g') ); var newS = ""; @@ -1357,6 +1365,10 @@ lua_libs["string"] = { } newS += s; + if (reverse) { + newS = newS.split("").reverse().join(""); // reverse + } + return [newS, replacementCount]; }, "len": function (s) { From 164909634bc195bd308415a3cdbbaae90f7d7db7 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 11 Nov 2013 18:01:56 +0100 Subject: [PATCH 26/59] Made tonumber() return null instead of NaN, and made it return null wherever there is other characters than digits, dots or spaces in the input string (like Lua's tonumber() does) --- src/lualib.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 8fd7a85..a3f883d 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -723,15 +723,22 @@ var lua_core = { } return [table] }, - "tonumber": function (e, base) { + tonumber: function (e, base) { if (typeof e == "number") { - return [e]; + return [e] } + e = check_string(e); + if (e.search("[^0-9\. ]") != -1) + return [null]; + var num; if (base === 10 || base == null) { - return [parseFloat(e)]; + num = parseFloat(e); } else { - return [parseInt(e, base)]; + num = parseInt(e, base); } + if (isNaN(num)) + num = null; + return [num]; }, "tostring": function (e) { if (e == null) { @@ -1299,10 +1306,7 @@ lua_libs["string"] = { }, // string.format() "gmatch": function (s, pattern) { var lua_gmatch_next = function(data) { - console.log(data.pattern); - return; var match = data.s.match(data.pattern); - if (match == null) return [null]; From 65c944397d14feaf97c9ab331a8442c1df7cb158 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 11 Nov 2013 18:03:47 +0100 Subject: [PATCH 27/59] Fixed string.gsub() when the pattern must match at the end of the string (again) --- src/lualib.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index a3f883d..63d1c99 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1334,20 +1334,12 @@ lua_libs["string"] = { n = Number(n); // NaN if n == undefined - var reverse = false; - if (pattern[pattern.length-1] == "$") { - reverse = true; - s = s.split("").reverse().join(""); - pattern = "^" + pattern.substr(0, pattern.length-1); - regex = new RegExp(pattern); - } - var matches = s.match( new RegExp(pattern , 'g') ); var newS = ""; for (var i in matches) { var match = matches[i]; - var matchEndIndex = s.search( match ) + match.length; + var matchEndIndex = s.search( regex ) + match.length; var matchChunk = s.substr( 0, matchEndIndex ); var newMatchChunk = ""; @@ -1362,16 +1354,11 @@ lua_libs["string"] = { s = s.substr( matchEndIndex ); replacementCount++; - if (!isNaN(n) && ++replacementCount >= n) - break; - if (pattern[0] == "^" || pattern[pattern.length] == "$") + if (!isNaN(n) && replacementCount >= n) break; } newS += s; - if (reverse) { - newS = newS.split("").reverse().join(""); // reverse - } return [newS, replacementCount]; }, From 9abe406473e265c2615790a642d2c32d040ef770 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 11 Nov 2013 18:05:02 +0100 Subject: [PATCH 28/59] Made string.match() return the correct match when there was a capture in the pattern (like I did on november 9 with lua_gmatch_next() ) --- src/lualib.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 63d1c99..414317b 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1379,10 +1379,14 @@ lua_libs["string"] = { pattern = luapattern_to_regex(pattern); var matches = s.substr(index).match(pattern); - if (matches == null) - return [null]; - else - return [matches[0]]; + var match = null; + if (matches != null) { + if (matches[1] != null) // there was a capture, match[0] is the whole matched expression + match = matches[1]; + else + match = matches[0]; + } + return [match]; }, "rep": function (s, n) { s = check_string(s); From 250f6a570a61ea8b01eedc736d4320a0957e15e6 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 12 Nov 2013 17:56:49 +0100 Subject: [PATCH 29/59] Prevented an error in tonumber() if e argument was not of type string --- src/lualib.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 414317b..a1d9e26 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -724,11 +724,11 @@ var lua_core = { return [table] }, tonumber: function (e, base) { - if (typeof e == "number") { - return [e] - } - e = check_string(e); - if (e.search("[^0-9\. ]") != -1) + var type = typeof e; + if (type == "number") { + return [e] + } + if (type != "string" || e.search("[^0-9\. -]") != -1) return [null]; var num; if (base === 10 || base == null) { From 18fe311d24d5ded30d2337ae51063e40b773dcaf Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 18 Nov 2013 11:31:49 +0100 Subject: [PATCH 30/59] Made ensure_arraymode() also work when table.uints is an object instead of an array (no matter the arraymode property) --- src/lualib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index a1d9e26..164a7a5 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -29,7 +29,7 @@ function not_supported() { } function ensure_arraymode(table) { - if (!table.arraymode) { + if (!table.arraymode || Object.prototype.toString.call( table.uints ) !== '[object Array]') { var newuints = []; for (var i in table.uints) { if (table.uints[i] != null) { From 5576e804ef6b6eae244f8ebade6b9bd6657649f2 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sat, 30 Nov 2013 15:17:14 +0100 Subject: [PATCH 31/59] Fixed style --- .gitignore | 2 - src/lualib.js | 280 +++++++++++++++++++++++++++++++------------------- 2 files changed, 172 insertions(+), 110 deletions(-) diff --git a/.gitignore b/.gitignore index ad24838..413e796 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,3 @@ node_modules closurecompiler tests/*.js tests/*.js.tmp - -/GitIgnore diff --git a/src/lualib.js b/src/lualib.js index 164a7a5..e773979 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -29,7 +29,7 @@ function not_supported() { } function ensure_arraymode(table) { - if (!table.arraymode || Object.prototype.toString.call( table.uints ) !== '[object Array]') { + if (!table.arraymode) { var newuints = []; for (var i in table.uints) { if (table.uints[i] != null) { @@ -629,12 +629,6 @@ var lua_core = { } if (table.arraymode) { var j = table.uints.length; - if (j == null) { // sometimes table.uints has no length property. Is set in lua_len(). - j = 0; - for (i in table.uints) - j++; - table.uints.length = j; - } while (j-- > 0) { if (table.uints[j] != null) { props.push(j + 1); @@ -723,21 +717,23 @@ var lua_core = { } return [table] }, - tonumber: function (e, base) { + "tonumber": function (e, base) { var type = typeof e; if (type == "number") { - return [e] + return [e]; } - if (type != "string" || e.search("[^0-9\. -]") != -1) + if (type != "string" || e.search("[^0-9\. -]") != -1) { return [null]; + } var num; if (base === 10 || base == null) { num = parseFloat(e); } else { num = parseInt(e, base); } - if (isNaN(num)) + if (isNaN(num)) { num = null; + } return [num]; }, "tostring": function (e) { @@ -1056,7 +1052,7 @@ lua_libs["package"] = { } }; -function luapattern_to_regex( pattern ) { +function lua_pattern_to_regex(pattern) { var replacements = { // pattern items, quantifiers "(%[a-zA-Z]{1})-": "$1*", // put this before the character classes @@ -1107,7 +1103,7 @@ function luapattern_to_regex( pattern ) { "%\\-": "\\-", "%\\?": "\\?", "%%": "%", - } + }; for (var luaExp in replacements) { pattern = pattern.replace(new RegExp(luaExp, "g"), replacements[luaExp]); @@ -1139,33 +1135,58 @@ lua_libs["string"] = { }, "find": function (s, pattern, index, plain) { s = check_string(s); - if (index == undefined) + if (index === undefined) { index = 1; - else if (index < 0) + } else if (index < 0) { index = s.length + index; - index = index-1; // -1 because Lua's arrays index starts at 1 instead of 0 + } + index--; // -1 because Lua's arrays index starts at 1 instead of 0 s = s.substr(index); if (plain !== true) { - pattern = luapattern_to_regex(pattern); + pattern = lua_pattern_to_regex(pattern); var matches = s.match(pattern); - if (matches != null) + if (matches !== null) { pattern = matches[0]; - else + } else { return [null]; + } } var start = s.indexOf(pattern); if (start != -1) { - start += index+1; // +1 because Lua's arrays index starts at 1 instead of 0 - return [start, start+pattern.length-1]; - } - else + start += index + 1; // +1 because Lua's arrays index starts at 1 instead of 0 + return [start, start + pattern.length - 1]; + } else { return [null]; + } }, "format": function () { - /*! sprintf.js | Copyright (c) 2007-2013 Alexandru Marasteanu | 3 clause BSD license */ - // forked to match Lua's string.format() behavior (and for use in lua.js) by Florent POUJOL + // sprintf.js, forked to match Lua's string.format() behavior (and for use in lua.js) by Florent POUJOL + /* Copyright (c) 2007-2013, Alexandru Marasteanu + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of this software nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ var sprintf = function() { if (!sprintf.cache.hasOwnProperty(arguments[0])) { sprintf.cache[arguments[0]] = sprintf.parse(arguments[0]); @@ -1174,57 +1195,96 @@ lua_libs["string"] = { }; sprintf.format = function(parse_tree, argv) { - var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; + var cursor = 1; + var tree_length = parse_tree.length; + var node_type = ''; + var arg; + var output = []; + var i; + var k; + var match; + var pad; + var pad_character; + var pad_length; for (i = 0; i < tree_length; i++) { node_type = get_type(parse_tree[i]); if (node_type === 'string') { output.push(parse_tree[i]); - } - else if (node_type === 'array') { + } else if (node_type === 'array') { match = parse_tree[i]; // convenience purposes only if (match[2]) { // keyword argument arg = argv[cursor]; for (k = 0; k < match[2].length; k++) { if (!arg.hasOwnProperty(match[2][k])) { - throw('[string.format()] property "'+match[2][k]+'" does not exist'); + throw new Error('[string.format()] property "'+match[2][k]+'" does not exist'); } arg = arg[match[2][k]]; } - } - else if (match[1]) { // positional argument (explicit) + } else if (match[1]) { // positional argument (explicit) arg = argv[match[1]]; - } - else { // positional argument (implicit) + } else { // positional argument (implicit) arg = argv[cursor++]; } if (/[^sq]/.test(match[8]) && (get_type(arg) != 'number')) { - throw('[string.format()] expecting number but found '+get_type(arg)); + throw new Error('[string.format()] expecting number but found '+get_type(arg)); } switch (match[8]) { - // case 'b': arg = arg.toString(2); break; - case 'c': arg = String.fromCharCode(arg); break; + case 'c': + arg = String.fromCharCode(arg); + break; case 'i': - case 'd': arg = parseInt(arg, 10); break; // int - case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(6); break; - case 'E': arg = match[7] ? arg.toExponential(match[7]).toUpperCase() : arg.toExponential(6).toUpperCase(); break; - case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg).toFixed(6); break; // float - // case 'g': // from C++ doc : use the sortest representation e or f > in Lua, has only 5 digit after the coma instead of 6 for floats - // case 'G': // from C++ doc : use the sortest representation E or F (F does not exist in Lua) - case 'o': arg = arg.toString(8); break; // octal - case 'q': arg = '"'+((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)+'"'; break; - case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; - case 'u': arg = arg >>> 0; break; // unsigned integer - case 'x': arg = arg.toString(16); break; // hexadecimal - case 'X': arg = arg.toString(16).toUpperCase(); break; - // TODO hexa : some wrong values returned : -100 become -64 instead of ffffff9c (same for octal) + case 'd': // int + arg = parseInt(arg, 10); + break; + case 'e': + arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(6); + break; + case 'E': + arg = match[7] ? arg.toExponential(match[7]).toUpperCase() : arg.toExponential(6).toUpperCase(); + break; + case 'f': // float + case 'F': + arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg).toFixed(6); + break; + case 'g': + not_supported(); // from C++ doc : use the sortest representation e or f > in Lua, has only 5 digit after the coma instead of 6 for floats + break; + case 'G': + not_supported(); // from C++ doc : use the sortest representation E or F (F does not exist in Lua) + break; + case 'o': // octal + arg = arg.toString(8); + break; + case 'u': // unsigned integer + arg = arg >>> 0; + break; + case 'x': // hexadecimal + arg = arg.toString(16); + break; + case 'X': + arg = arg.toString(16).toUpperCase(); + break; + // TODO hexadecimal : some wrong values are returned : -100 becomes -64 instead of ffffff9c (same issue with octal) + case 'q': + arg = '"'+((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)+'"'; + break; + case 's': + arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); + break; + default: + not_supported(); + break; } if (/[eE]/.test(match[8])) { //make the exponent (exp[2]) always at least 3 digit (ie : 3.14E+003) var exp = /^(.+\+)(\d+)$/.exec(arg); if (exp != null) { - if (exp[2].length == 1) arg = exp[1]+"00"+exp[2]; - else if (exp[2].length == 2) arg = exp[1]+"0"+exp[2]; + if (exp[2].length == 1) { + arg = exp[1]+"00"+exp[2]; + } else if (exp[2].length == 2) { + arg = exp[1]+"0"+exp[2]; + } } } arg = (/[dieEf]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); @@ -1240,126 +1300,128 @@ lua_libs["string"] = { sprintf.cache = {}; sprintf.parse = function(fmt) { // fmt = format string - var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; + var _fmt = fmt; + var match = []; + var parse_tree = []; + var arg_names = 0; while (_fmt) { // \x25 = % if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { // no % found parse_tree.push(match[0]); - } - else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { // 2 consecutive % found + } else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { // 2 consecutive % found parse_tree.push('%'); - } - else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([cdeEfgGiouxXqs])/.exec(_fmt)) !== null) { + } else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([cdeEfgGiouxXqs])/.exec(_fmt)) !== null) { if (match[2]) { arg_names |= 1; - var field_list = [], replacement_field = match[2], field_match = []; + var field_list = []; + var replacement_field = match[2]; + var field_match = []; if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { field_list.push(field_match[1]); while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { field_list.push(field_match[1]); - } - else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { + } else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { field_list.push(field_match[1]); - } - else { - throw('[string.format()] No field_match found in replacement_field 1.'); + } else { + throw new Error('[string.format()] No field_match found in replacement_field 1.'); } } - } - else { - throw('[string.format()] No field_match found in replacement_field 2.'); + } else { + throw new Error('[string.format()] No field_match found in replacement_field 2.'); } match[2] = field_list; - } - else { + } else { arg_names |= 2; } if (arg_names === 3) { - throw('[string.format()] mixing positional and named placeholders is not (yet) supported'); + throw new Error('[string.format()] mixing positional and named placeholders is not (yet) supported'); } parse_tree.push(match); - } - else { - throw('[string.format()] Format string "'+fmt+'" not recognized.'); + } else { + throw new Error('[string.format()] Format string "'+fmt+'" not recognized.'); } _fmt = _fmt.substring(match[0].length); } return parse_tree; }; - /** - * helpers - */ function get_type(variable) { - return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); + var type = typeof variable; + if (type == "object" && Object.prototype.toString.call(variable) == "[object Array]") { + type = "array"; + } + return type; } - + function str_repeat(input, multiplier) { for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} - return output.join(''); + return output.join(''); } - if (arguments.length > 0) + if (arguments.length > 0) { arguments[0] = check_string(arguments[0]); + } return [sprintf.apply(this, arguments)]; - }, // string.format() + }, "gmatch": function (s, pattern) { var lua_gmatch_next = function(data) { var match = data.s.match(data.pattern); - if (match == null) + if (match === null) { return [null]; + } - if (match[1] != null) // there was a capture, match[0] is the whole matched expression - match = match[1]; // match[0] is the whole match, not the first capture - else + if (match[1] !== null) { // if there was a capture, match[0] is the whole matched expression, match[1] the first capture + match = match[1]; + } else { match = match[0]; + } - data.s = data.s.substr( data.s.search(match) + match.length ); + data.s = data.s.substr(data.s.search(match) + match.length); return [match]; - } + }; // an object is used to keep the modifs to the string accross calls to lua_gmatch_next() - return [lua_gmatch_next, {s:check_string(s), pattern:luapattern_to_regex(pattern)}] + return [lua_gmatch_next, {"s":check_string(s), "pattern":lua_pattern_to_regex(pattern)}]; }, "gsub": function (s, pattern, replacement, n) { s = check_string(s); - pattern = luapattern_to_regex(pattern); + pattern = lua_pattern_to_regex(pattern); var regex = new RegExp(pattern); - var replacementCount = 0, replacementType = typeof replacement; - if (replacementType == "string") // replacement can be a function + var replacementCount = 0; + var replacementType = typeof replacement; + if (replacementType == "string") { // replacement can be a function replacement = replacement.replace(/%([0-9]+)/g, "$$$1"); - + } n = Number(n); // NaN if n == undefined - var matches = s.match( new RegExp(pattern , 'g') ); + var matches = s.match(new RegExp(pattern , 'g')); var newS = ""; for (var i in matches) { var match = matches[i]; - var matchEndIndex = s.search( regex ) + match.length; - var matchChunk = s.substr( 0, matchEndIndex ); + var matchEndIndex = s.search(regex) + match.length; + var matchChunk = s.substr(0, matchEndIndex); var newMatchChunk = ""; if (replacementType == "string") { - newMatchChunk = matchChunk.replace( regex, replacement ); - } - else if (replacementType == "function") { - newMatchChunk = matchChunk.replace( match, replacement( match ) ); + newMatchChunk = matchChunk.replace(regex, replacement); + } else if (replacementType == "function") { + newMatchChunk = matchChunk.replace(match, replacement(match)); } newS += newMatchChunk; - s = s.substr( matchEndIndex ); + s = s.substr(matchEndIndex); replacementCount++; - if (!isNaN(n) && replacementCount >= n) + if (!isNaN(n) && replacementCount >= n) { break; + } } newS += s; - return [newS, replacementCount]; }, "len": function (s) { @@ -1370,22 +1432,24 @@ lua_libs["string"] = { }, "match": function (s, pattern, index) { s = check_string(s); - if (index == undefined) + if (index === undefined) { index = 1; - else if (index < 0) + } else if (index < 0) { index = s.length + index; - index = index-1; + } + index--; - pattern = luapattern_to_regex(pattern); + pattern = lua_pattern_to_regex(pattern); var matches = s.substr(index).match(pattern); var match = null; - if (matches != null) { - if (matches[1] != null) // there was a capture, match[0] is the whole matched expression + if (matches !== null) { + if (matches[1] !== null) { // there was a capture, match[0] is the whole matched expression match = matches[1]; - else + } else { match = matches[0]; - } + } + } return [match]; }, "rep": function (s, n) { From 84128ccb41bed2c9d37c09e3c6d2af64724e70bc Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 2 Dec 2013 09:22:37 +0100 Subject: [PATCH 32/59] Fixed typo in type() --- src/lualib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index e773979..12d14fd 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -774,7 +774,7 @@ var lua_core = { case "undefined": return ["nil"]; default: - throw new Error("Unepected value of type " + typeof v); + throw new Error("Unexpected value of type " + typeof v); } }, "unpack": function (list, i, j) { From 94225eb683500897b2b9b75dad03e96736fbd12d Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 2 Dec 2013 09:32:02 +0100 Subject: [PATCH 33/59] Moved tonumber() code in separate function lua_tonumber() --- src/lualib.js | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 12d14fd..c07200b 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -555,6 +555,25 @@ function lua_concat(op1, op2) { } } } +function lua_tonumber(e, base) { + var type = typeof e; + if (type == "number") { + return e; + } + if (type != "string" || e.search("[^0-9\. -]") != -1) { + return null; + } + var num; + if (base === 10 || base === null) { + num = parseFloat(e); + } else { + num = parseInt(e, base); + } + if (isNaN(num)) { + num = null; + } + return num; +} // core lua functions function _ipairs_next(table, index) { @@ -718,23 +737,7 @@ var lua_core = { return [table] }, "tonumber": function (e, base) { - var type = typeof e; - if (type == "number") { - return [e]; - } - if (type != "string" || e.search("[^0-9\. -]") != -1) { - return [null]; - } - var num; - if (base === 10 || base == null) { - num = parseFloat(e); - } else { - num = parseInt(e, base); - } - if (isNaN(num)) { - num = null; - } - return [num]; + return [lua_tonumber(e, base)]; }, "tostring": function (e) { if (e == null) { From 8d2a4f1615d501db95a9c8be044f24895ca749fd Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 3 Dec 2013 19:18:57 +0100 Subject: [PATCH 34/59] Fixed base argument equality check in lua_tonumber() --- src/lualib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index c07200b..c46fda8 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -564,7 +564,7 @@ function lua_tonumber(e, base) { return null; } var num; - if (base === 10 || base === null) { + if (base === 10 || base == null) { num = parseFloat(e); } else { num = parseInt(e, base); From b6ccb1da25ef073edf3eccb93b1f2120e5f89ccf Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 3 Dec 2013 19:20:03 +0100 Subject: [PATCH 35/59] Fixed equality checks in string.gmatch() and string.match() --- src/lualib.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index c46fda8..27f2105 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1370,11 +1370,11 @@ lua_libs["string"] = { "gmatch": function (s, pattern) { var lua_gmatch_next = function(data) { var match = data.s.match(data.pattern); - if (match === null) { + if (match == null) { return [null]; } - if (match[1] !== null) { // if there was a capture, match[0] is the whole matched expression, match[1] the first capture + if (match[1] != null) { // if there was a capture, match[0] is the whole matched expression, match[1] the first capture match = match[1]; } else { match = match[0]; @@ -1446,8 +1446,8 @@ lua_libs["string"] = { var matches = s.substr(index).match(pattern); var match = null; - if (matches !== null) { - if (matches[1] !== null) { // there was a capture, match[0] is the whole matched expression + if (matches != null) { + if (matches[1] != null) { // there was a capture, match[0] is the whole matched expression match = matches[1]; } else { match = matches[0]; From 99eed03c5ba25e46c55a4e02567d13cbeaff718c Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 4 Dec 2013 16:59:20 +0100 Subject: [PATCH 36/59] Added support for g and G format in string.format() --- src/lualib.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 27f2105..6b276c8 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1247,14 +1247,12 @@ lua_libs["string"] = { arg = match[7] ? arg.toExponential(match[7]).toUpperCase() : arg.toExponential(6).toUpperCase(); break; case 'f': // float - case 'F': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg).toFixed(6); - break; - case 'g': - not_supported(); // from C++ doc : use the sortest representation e or f > in Lua, has only 5 digit after the coma instead of 6 for floats break; - case 'G': - not_supported(); // from C++ doc : use the sortest representation E or F (F does not exist in Lua) + case 'g': + case 'G': + arg = match[7] ? parseFloat(arg).toFixed(match[7] - 1) : parseFloat(arg).toFixed(5); + // in practice, g or G always return a float with 1 less digits after the coma than asked for (by default it's 5 instead of 6) break; case 'o': // octal arg = arg.toString(8); From 90e5c340f03b1faa63dd62eebc2baa49b9fb2904 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 4 Dec 2013 17:00:14 +0100 Subject: [PATCH 37/59] Fixed wrong behavior of o, x and X formats with negatives values in string.format() --- src/lualib.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index 6b276c8..7bb6cf2 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1255,18 +1255,26 @@ lua_libs["string"] = { // in practice, g or G always return a float with 1 less digits after the coma than asked for (by default it's 5 instead of 6) break; case 'o': // octal + if (arg < 0) { + arg = 0xFFFFFFFF + arg + 1; + } arg = arg.toString(8); break; case 'u': // unsigned integer arg = arg >>> 0; break; case 'x': // hexadecimal + if (arg < 0) { + arg = 0xFFFFFFFF + arg + 1; + } arg = arg.toString(16); break; case 'X': + if (arg < 0) { + arg = 0xFFFFFFFF + arg + 1; + } arg = arg.toString(16).toUpperCase(); break; - // TODO hexadecimal : some wrong values are returned : -100 becomes -64 instead of ffffff9c (same issue with octal) case 'q': arg = '"'+((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)+'"'; break; From 5759c6f95292f30384175a3a8e5cd0fe78338abc Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 4 Dec 2013 18:04:28 +0100 Subject: [PATCH 38/59] Improved %p pattern in lua_pattern_to_regex(), made not supported pattern to call not_supported() --- src/lualib.js | 62 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 7bb6cf2..4b18b12 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1056,43 +1056,55 @@ lua_libs["package"] = { }; function lua_pattern_to_regex(pattern) { + var notsupportedPatterns = { + // - quantifier + "(%[a-zA-Z]{1})-", + "\\]-", + "-\\)", + "- ", + "-$", + + "%(g|G)", // all printable characters except space. + + // character classes between square brackets [%l] + "\[[^%]*(%[aAcCdDgGlLpPsSuUwW][\]]*\]", + }; + + for (var i in notsupportedPatterns) { + if (pattern.search(new RegExp(notsupportedPatterns[i], "g")) != -1) { + not_supported(); + } + } + var replacements = { - // pattern items, quantifiers - "(%[a-zA-Z]{1})-": "$1*", // put this before the character classes - "\\]-": "]*", - "-\\)": "*)", - "- ": "* ", - "-$": "*$", - // TODO : probably other cases of hyphens that should be converted to * - "%([0-9]){1}": "{$1}", + "%([0-9]){1}": "{$1}",^// quantifier "%f\\[([^\\]]+)\\]": "[^$1]{1}[$1]{1}", // frontier pattern - // character classes, metacharacters + // character classes "%a": "[a-zA-Z\u00C0-\u017F]", // all letters with accented characters À to ſ (shouldn't the down limit be much lower ?) "%A": "[^a-zA-Z\u00C0-\u017F]", - - "%u": "[A-Z\u00C0-\u00DF]", // À to ß - "%U": "[^A-Z\u00C0-\u00DF]", - - "%l": "[a-z\u00E0-\u00FF]", // à to ÿ - "%L": "[^a-z\u00E0-\u00FF]", - // below character 00FF, upper case and lowercase characters are mixed "%c": "[\u0000-\u001F]", // Control characters "%C": "[^\u0000-\u001F]", - - "%p": "[,\?;\.:/!]", // all punctuation - "%P": "[^,\?;\.:/!]", - + "%d": "\\d", // all digit - "%D": "\\D", - "%s": "\\s", // all space characters Any difference between 'space' and 'whitespace' ? + "%D": "\\D", + + "%l": "[a-z\u00E0-\u00FF]", // lowercase letters + à to ÿ (below character 00FF, upper case and lowercase characters are mixed) + "%L": "[^a-z\u00E0-\u00FF]", + + "%p": "[,\?;\.:/\\!\(\)\[\]\{\}\"'#|%$`^@~&+*<>-]", // all punctuation + "%P": "[^,\?;\.:/\\!\(\)\[\]\{\}\"'#|%$`^@~&+*<>-]", + + "%s": "\\s", // all space characters "%S": "\\S", - "%w": "\\w", // all word (alphanum) characters - "%W": "\\W", - // "%g": "", // TODO : all printable characters except space. (all characters but the control characters (%C)) + "%u": "[A-Z\u00C0-\u00DF]", // uppercase letter + À to ß + "%U": "[^A-Z\u00C0-\u00DF]", + "%w": "\\w", // all alphanum characters + "%W": "\\W", + // escape special characters "%\\.": "\\.", "%\\^": "\\^", From a8be65dd8620aa0e2d0fec5f1de8214b8c57b505 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 9 Dec 2013 11:57:33 +0100 Subject: [PATCH 39/59] Improved regexes to detect use of the hyphen quantifier in lua_pattern_to_regex() --- src/lualib.js | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 4b18b12..ef0cea7 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1056,22 +1056,19 @@ lua_libs["package"] = { }; function lua_pattern_to_regex(pattern) { - var notsupportedPatterns = { - // - quantifier - "(%[a-zA-Z]{1})-", - "\\]-", - "-\\)", - "- ", - "-$", - - "%(g|G)", // all printable characters except space. + var notsupportedPatterns = [ + // hyphen quantifier + /%[aAcCdDgGlLpPsSuUwW]-/g, // after a chracter class + /(]|\))-/g, // after a set or parenthesis + /[^%]-(\)|%)/g, // not escaped, before a parenthesis or a character class + + /%(g|G)/g, // all printable characters except space. - // character classes between square brackets [%l] - "\[[^%]*(%[aAcCdDgGlLpPsSuUwW][\]]*\]", - }; + /\[[^%]*%[aAcCdDgGlLpPsSuUwW][\]]*\]/g, // character classes between square brackets [%l] + ]; for (var i in notsupportedPatterns) { - if (pattern.search(new RegExp(notsupportedPatterns[i], "g")) != -1) { + if (pattern.search(notsupportedPatterns[i]) != -1) { not_supported(); } } From 40a581dd4ac7a404ddde385a4cc70bb82a4416ba Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 9 Dec 2013 14:39:20 +0100 Subject: [PATCH 40/59] Added get_balanced_match() function + use in string.find() --- src/lualib.js | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index ef0cea7..14a69c1 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1124,6 +1124,40 @@ function lua_pattern_to_regex(pattern) { return pattern; } +function get_balanced_match(s, pattern) { + var match = pattern.search(/%b.{1}.{1}/); // lua_pattern_to_regex() will leave balanced pattern untouched in the returned regex + if (match !== -1) { + var startChar = pattern[2]; + var endChar = pattern[3]; + var level = 0; + var startIndex = -1; + var endIndex = -1; + + for (var i in s) { + i = parseInt(i); + var _char = s[i]; + if (_char === startChar) { + if (level === 0) { + startIndex = i; + level = 0; // in case one or more endChar were encountered first + } + level++; + } else if (_char === endChar) { + level--; + if (level === 0) { + endIndex = i; + break; + } + } + } + + if (startIndex >= 0 && endIndex >= 0) { + return s.substring(startIndex, endIndex + 1); + } + } + return null; +} + // string lua_libs["string"] = { "byte": function (s, i, j) { @@ -1157,14 +1191,19 @@ lua_libs["string"] = { if (plain !== true) { pattern = lua_pattern_to_regex(pattern); - var matches = s.match(pattern); - if (matches !== null) { - pattern = matches[0]; + var match = get_balanced_match(s, pattern); + if (match != null) { + pattern = match; } else { - return [null]; + var matches = s.match(pattern); + if (matches !== null) { + pattern = matches[0]; + } else { + return [null]; + } } } - + var start = s.indexOf(pattern); if (start != -1) { start += index + 1; // +1 because Lua's arrays index starts at 1 instead of 0 From 69bc575d700c7e854059c92cddba79268ffb6522 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 9 Dec 2013 14:44:33 +0100 Subject: [PATCH 41/59] Made patterns not composed only of one balanced pattern unsupported --- src/lualib.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index 14a69c1..0999335 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1065,6 +1065,8 @@ function lua_pattern_to_regex(pattern) { /%(g|G)/g, // all printable characters except space. /\[[^%]*%[aAcCdDgGlLpPsSuUwW][\]]*\]/g, // character classes between square brackets [%l] + + /(^[^%]+%b|%b.{2}.+$)/g, // a balanced pattern with something before or after it ]; for (var i in notsupportedPatterns) { @@ -1074,7 +1076,7 @@ function lua_pattern_to_regex(pattern) { } var replacements = { - "%([0-9]){1}": "{$1}",^// quantifier + "%([0-9]){1}": "{$1}", // quantifier "%f\\[([^\\]]+)\\]": "[^$1]{1}[$1]{1}", // frontier pattern // character classes From 8dd9bcabb9ee0b69d7f25e8284fd6ac55f78594e Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Mon, 9 Dec 2013 16:34:52 +0100 Subject: [PATCH 42/59] Added support for balanced match in string.gsub() --- src/lualib.js | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 0999335..6771c34 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1454,13 +1454,10 @@ lua_libs["string"] = { if (replacementType == "string") { // replacement can be a function replacement = replacement.replace(/%([0-9]+)/g, "$$$1"); } - n = Number(n); // NaN if n == undefined + n = parseInt(n); // NaN if n == undefined - var matches = s.match(new RegExp(pattern , 'g')); var newS = ""; - - for (var i in matches) { - var match = matches[i]; + var processMatch = function( match ) { var matchEndIndex = s.search(regex) + match.length; var matchChunk = s.substr(0, matchEndIndex); var newMatchChunk = ""; @@ -1468,17 +1465,48 @@ lua_libs["string"] = { if (replacementType == "string") { newMatchChunk = matchChunk.replace(regex, replacement); } else if (replacementType == "function") { - newMatchChunk = matchChunk.replace(match, replacement(match)); + newMatchChunk = matchChunk.replace(regex, replacement(match)); } - newS += newMatchChunk; s = s.substr(matchEndIndex); replacementCount++; if (!isNaN(n) && replacementCount >= n) { - break; + return true; // break } - } + }; + + var match = get_balanced_match(s, pattern); + if (match !== null) { + var startChar = pattern[2]; + var endChar = pattern[3]; + // escape start and end char if they are special regex chars + var specialChars = ["[","]","(",")","{","}"]; // in this context it's not necessarily usefull to add others characters + if (specialChars.indexOf(startChar) !== -1) { + startChar = "\\"+startChar; + } + if (specialChars.indexOf(endChar) !== -1) { + endChar = "\\"+endChar; + } + + do { + match = get_balanced_match(s, pattern); + if (match === null) { + break; + } + regex = match.replace(new RegExp(startChar, "g"), startChar); + regex = regex.replace(new RegExp(endChar, "g"), endChar); + regex = new RegExp(regex); + } while (processMatch(match) != true); + } else { + var matches = s.match(new RegExp(pattern , 'g')); + + for (var i in matches) { + if (processMatch(matches[i])) { + break; + } + } + } newS += s; return [newS, replacementCount]; From 4b3889ec59ae90cc0d816f0a62ae18328722e725 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 10 Dec 2013 13:29:53 +0100 Subject: [PATCH 43/59] Added support for balanced match in string.match() --- src/lualib.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 6771c34..097a4de 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1525,16 +1525,18 @@ lua_libs["string"] = { index = s.length + index; } index--; + s = s.substr(index); pattern = lua_pattern_to_regex(pattern); - var matches = s.substr(index).match(pattern); - - var match = null; - if (matches != null) { - if (matches[1] != null) { // there was a capture, match[0] is the whole matched expression - match = matches[1]; - } else { - match = matches[0]; + var match = get_balanced_match(s, pattern); + if (match === null) { + var matches = s.match(pattern); + if (matches != null) { + if (matches[1] != null) { // there was a capture, match[0] is the whole matched expression + match = matches[1]; + } else { + match = matches[0]; + } } } return [match]; From 7ccdc685c37082df07972db65b08f4a3a07a6cd1 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 10 Dec 2013 15:16:08 +0100 Subject: [PATCH 44/59] Improved string.find() : now returns the correct indexes as well as the captured chunks (if any) --- src/lualib.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 097a4de..f26a8cb 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1207,12 +1207,15 @@ lua_libs["string"] = { } var start = s.indexOf(pattern); + var returnValues = null; if (start != -1) { - start += index + 1; // +1 because Lua's arrays index starts at 1 instead of 0 - return [start, start + pattern.length - 1]; - } else { - return [null]; + returnValues = [start + index + 1, start + index + pattern.length]; + if (matches != null && matches[1] != null) { // string.find() returns the capture(s) (if any) after the indexes + returnValues = returnValues.concat(matches.slice(1)); + } } + + return returnValues; }, "format": function () { // sprintf.js, forked to match Lua's string.format() behavior (and for use in lua.js) by Florent POUJOL From 1cae6c7df71404f84d8be660510f31566c2e8bcd Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 10 Dec 2013 15:41:44 +0100 Subject: [PATCH 45/59] Fixed get_balanced_match() when there is more startChar than endChar or one or more endChar before the first startChar --- src/lualib.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index f26a8cb..d91d5ee 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1131,28 +1131,33 @@ function get_balanced_match(s, pattern) { if (match !== -1) { var startChar = pattern[2]; var endChar = pattern[3]; - var level = 0; + var level = -1; var startIndex = -1; + var startIndexes = []; var endIndex = -1; for (var i in s) { i = parseInt(i); var _char = s[i]; if (_char === startChar) { - if (level === 0) { + startIndexes.push(i); + if (level < 0) { startIndex = i; level = 0; // in case one or more endChar were encountered first } level++; } else if (_char === endChar) { level--; + endIndex = i; if (level === 0) { - endIndex = i; break; } } } + if (level > 0) { // there was more startChar than endChar + startIndex = startIndexes[level]; + } if (startIndex >= 0 && endIndex >= 0) { return s.substring(startIndex, endIndex + 1); } From 24ee541c4b928c75a8e0243fa80fd8d47173593b Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 10 Dec 2013 16:06:23 +0100 Subject: [PATCH 46/59] Added support for balanced match in string.gmatch() --- src/lualib.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index d91d5ee..a646f44 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1433,24 +1433,23 @@ lua_libs["string"] = { }, "gmatch": function (s, pattern) { var lua_gmatch_next = function(data) { - var match = data.s.match(data.pattern); - if (match == null) { - return [null]; - } - - if (match[1] != null) { // if there was a capture, match[0] is the whole matched expression, match[1] the first capture - match = match[1]; - } else { - match = match[0]; + var match = get_balanced_match(data.s, data.pattern); + if (match === null) { + var matches = data.s.match(data.pattern); + if (matches === null) { + return [null]; + } else { + if (matches[1] != null) { // if there was a capture, match[0] is the whole matched expression, match[1] the first capture + match = matches[1]; + } else { + match = matches[0]; + } + } } - + data.s = data.s.substr(data.s.search(match) + match.length); return [match]; }; - - // an object is used to keep the modifs to the string accross calls to lua_gmatch_next() - return [lua_gmatch_next, {"s":check_string(s), "pattern":lua_pattern_to_regex(pattern)}]; - }, "gsub": function (s, pattern, replacement, n) { s = check_string(s); From 7d8b1d476f3d24cac93872a4931728e0adac5fe8 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Tue, 10 Dec 2013 16:22:37 +0100 Subject: [PATCH 47/59] Made capture index in patterns not supported --- src/lualib.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index a646f44..dd05480 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1067,6 +1067,8 @@ function lua_pattern_to_regex(pattern) { /\[[^%]*%[aAcCdDgGlLpPsSuUwW][\]]*\]/g, // character classes between square brackets [%l] /(^[^%]+%b|%b.{2}.+$)/g, // a balanced pattern with something before or after it + + /%[0-9]{1}/g, // capture index ]; for (var i in notsupportedPatterns) { @@ -1076,7 +1078,6 @@ function lua_pattern_to_regex(pattern) { } var replacements = { - "%([0-9]){1}": "{$1}", // quantifier "%f\\[([^\\]]+)\\]": "[^$1]{1}[$1]{1}", // frontier pattern // character classes From 81e96440e03ccad471226b6d757c450d8a70d655 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 11 Dec 2013 09:42:21 +0100 Subject: [PATCH 48/59] Improved string.find() : added use of lua_tonumber() on index argument, fixed plain argument condition --- src/lualib.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index dd05480..bfc5cfc 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1189,7 +1189,8 @@ lua_libs["string"] = { }, "find": function (s, pattern, index, plain) { s = check_string(s); - if (index === undefined) { + index = lua_tonumber(index); + if (index == null) { index = 1; } else if (index < 0) { index = s.length + index; @@ -1197,10 +1198,10 @@ lua_libs["string"] = { index--; // -1 because Lua's arrays index starts at 1 instead of 0 s = s.substr(index); - if (plain !== true) { + if (plain == null || plain === false) { pattern = lua_pattern_to_regex(pattern); var match = get_balanced_match(s, pattern); - if (match != null) { + if (match !== null) { pattern = match; } else { var matches = s.match(pattern); @@ -1210,6 +1211,7 @@ lua_libs["string"] = { return [null]; } } + // pattern is now the matched string } var start = s.indexOf(pattern); From a98bde54908237fa3a2a4d8a7345f7211f08f09b Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 11 Dec 2013 09:58:09 +0100 Subject: [PATCH 49/59] Fixed missing bit in string.gmatch() --- src/lualib.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index bfc5cfc..f32caf9 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1226,7 +1226,7 @@ lua_libs["string"] = { return returnValues; }, "format": function () { - // sprintf.js, forked to match Lua's string.format() behavior (and for use in lua.js) by Florent POUJOL + // sprintf.js, forked to match Lua's string.format() behavior and for use in lua.js /* Copyright (c) 2007-2013, Alexandru Marasteanu All rights reserved. @@ -1257,6 +1257,7 @@ lua_libs["string"] = { } return sprintf.format.call(null, sprintf.cache[arguments[0]], arguments); }; + sprintf.cache = {}; sprintf.format = function(parse_tree, argv) { var cursor = 1; @@ -1344,7 +1345,6 @@ lua_libs["string"] = { break; default: not_supported(); - break; } if (/[eE]/.test(match[8])) { //make the exponent (exp[2]) always at least 3 digit (ie : 3.14E+003) @@ -1367,8 +1367,6 @@ lua_libs["string"] = { return output.join(''); }; - sprintf.cache = {}; - sprintf.parse = function(fmt) { // fmt = format string var _fmt = fmt; var match = []; @@ -1453,6 +1451,10 @@ lua_libs["string"] = { data.s = data.s.substr(data.s.search(match) + match.length); return [match]; }; + + // an object is used to keep the modifs to the string accross calls to lua_gmatch_next() + return [lua_gmatch_next, {"s":check_string(s), "pattern":lua_pattern_to_regex(pattern)}]; + }, "gsub": function (s, pattern, replacement, n) { s = check_string(s); From 6f684d4994ce13c8d47b95101498f501d4c5a3a9 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 11 Dec 2013 10:08:32 +0100 Subject: [PATCH 50/59] Improved string.gsub() : added use of lua_tonumber() fo n argument + changed returned value of processMath() --- src/lualib.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index f32caf9..a4a1fad 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1457,6 +1457,7 @@ lua_libs["string"] = { }, "gsub": function (s, pattern, replacement, n) { s = check_string(s); + n = lua_tonumber(n); pattern = lua_pattern_to_regex(pattern); var regex = new RegExp(pattern); @@ -1466,7 +1467,6 @@ lua_libs["string"] = { if (replacementType == "string") { // replacement can be a function replacement = replacement.replace(/%([0-9]+)/g, "$$$1"); } - n = parseInt(n); // NaN if n == undefined var newS = ""; var processMatch = function( match ) { @@ -1483,9 +1483,10 @@ lua_libs["string"] = { s = s.substr(matchEndIndex); replacementCount++; - if (!isNaN(n) && replacementCount >= n) { - return true; // break + if (n !== null && replacementCount >= n) { + return false; // break } + return true; }; var match = get_balanced_match(s, pattern); @@ -1509,12 +1510,12 @@ lua_libs["string"] = { regex = match.replace(new RegExp(startChar, "g"), startChar); regex = regex.replace(new RegExp(endChar, "g"), endChar); regex = new RegExp(regex); - } while (processMatch(match) != true); + } while (processMatch(match)); } else { var matches = s.match(new RegExp(pattern , 'g')); for (var i in matches) { - if (processMatch(matches[i])) { + if (!processMatch(matches[i])) { break; } } From f27c12b73927581160d9d949d59f4551f68ab6c1 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 11 Dec 2013 10:24:25 +0100 Subject: [PATCH 51/59] Added use of lua_tonumber() in string.byte(), match(), rep() and sub() --- src/lualib.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index a4a1fad..e93a58b 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1169,6 +1169,8 @@ function get_balanced_match(s, pattern) { // string lua_libs["string"] = { "byte": function (s, i, j) { + i = lua_tonumber(i); + j = lua_tonumber(j); if (i == null) { i = 0; } @@ -1469,7 +1471,7 @@ lua_libs["string"] = { } var newS = ""; - var processMatch = function( match ) { + var processMatch = function(match) { var matchEndIndex = s.search(regex) + match.length; var matchChunk = s.substr(0, matchEndIndex); var newMatchChunk = ""; @@ -1532,7 +1534,8 @@ lua_libs["string"] = { }, "match": function (s, pattern, index) { s = check_string(s); - if (index === undefined) { + index = lua_tonumber(index); + if (index === null) { index = 1; } else if (index < 0) { index = s.length + index; @@ -1544,8 +1547,8 @@ lua_libs["string"] = { var match = get_balanced_match(s, pattern); if (match === null) { var matches = s.match(pattern); - if (matches != null) { - if (matches[1] != null) { // there was a capture, match[0] is the whole matched expression + if (matches !== null) { + if (matches[1] != null) { match = matches[1]; } else { match = matches[0]; @@ -1556,7 +1559,8 @@ lua_libs["string"] = { }, "rep": function (s, n) { s = check_string(s); - if (typeof n == "number") { + n = lua_tonumber(n); + if (n !== null) { var result = []; while (n-- > 0) { result.push(s); @@ -1570,6 +1574,8 @@ lua_libs["string"] = { return [check_string(s).split("").reverse().join("")]; }, "sub": function (s, i, j) { + i = lua_tonumber(i); + j = lua_tonumber(j); // thanks to ghoulsblade for pointing out the bugs in string.sub i = i < 0 ? (i + s.length + 1) : (i >= 0 ? i : 0) if (j == null) { From 809fccc9805c05f73d12e1945306e6aaa74e836f Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 11 Dec 2013 12:08:47 +0100 Subject: [PATCH 52/59] Removed "character classes between square brackets" from the unsupported patterns in lua_pattern_to_regex() --- src/lualib.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index e93a58b..f2a1ba7 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1064,8 +1064,6 @@ function lua_pattern_to_regex(pattern) { /%(g|G)/g, // all printable characters except space. - /\[[^%]*%[aAcCdDgGlLpPsSuUwW][\]]*\]/g, // character classes between square brackets [%l] - /(^[^%]+%b|%b.{2}.+$)/g, // a balanced pattern with something before or after it /%[0-9]{1}/g, // capture index From 94757a780c260add7ef95dd342f6140e5e74ac19 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 11 Dec 2013 12:41:34 +0100 Subject: [PATCH 53/59] Fixed returned value when string.find() don't find anything --- src/lualib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index f2a1ba7..3a70a4d 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1215,7 +1215,7 @@ lua_libs["string"] = { } var start = s.indexOf(pattern); - var returnValues = null; + var returnValues = [null]; if (start != -1) { returnValues = [start + index + 1, start + index + pattern.length]; if (matches != null && matches[1] != null) { // string.find() returns the capture(s) (if any) after the indexes From 6a75a5d69e244c0b81b7a236cb9966b390f4d174 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Wed, 11 Dec 2013 16:54:19 +0100 Subject: [PATCH 54/59] Fixed a wrong indentation --- src/lualib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index 3a70a4d..d646242 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -424,7 +424,7 @@ function lua_rawget(table, key) { return table.objs[i][1]; } } - break; + break; default: throw new Error("Unsupported key for table: " + (typeof key)); } From 921c62bb6b30feab7f724fc090c4cfb1463673e8 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Fri, 27 Dec 2013 10:34:14 +0100 Subject: [PATCH 55/59] Fixed string.byte() --- src/lualib.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index d646242..3a0e3aa 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1167,17 +1167,20 @@ function get_balanced_match(s, pattern) { // string lua_libs["string"] = { "byte": function (s, i, j) { + s = check_string(s); i = lua_tonumber(i); j = lua_tonumber(j); if (i == null) { - i = 0; + i = 1; } if (j == null) { j = i; } + i--; + j--; var result = []; - while (i < j && i < s.length) { - result.push(s.charCodeAt(i)); + while (i > 0 && i <= j && i < s.length) { + result.push(s.charCodeAt(i++)); } return result; }, From 48557e929fab8d5c49369ca3b26c9fe9581e11eb Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Fri, 27 Dec 2013 10:39:58 +0100 Subject: [PATCH 56/59] Fixed while() condition in string.byte() --- src/lualib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index 3a0e3aa..2ebb560 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1179,7 +1179,7 @@ lua_libs["string"] = { i--; j--; var result = []; - while (i > 0 && i <= j && i < s.length) { + while (i >= 0 && i <= j && i < s.length) { result.push(s.charCodeAt(i++)); } return result; From e8278ef46242939f3d076cd5b2c0f49cb7cc54f2 Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sat, 28 Dec 2013 15:24:37 +0100 Subject: [PATCH 57/59] Added tests for string functions --- tests/string.lua | 135 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 tests/string.lua diff --git a/tests/string.lua b/tests/string.lua new file mode 100644 index 0000000..a91f4fc --- /dev/null +++ b/tests/string.lua @@ -0,0 +1,135 @@ + +local s, r, r2, r3, r4, r5 + +-- string.byte( s[, start, end]) +assert(string.byte("ABCDE") == 65) +assert(string.byte("ABCDE", 1) == 65) +assert(string.byte("ABCDE",0) == nil) -- we're not using C +assert(string.byte("ABCDE",100) == nil) -- index out of range, no value returned +local r, r2 = string.byte("ABCDE",3,4) +assert(r == 67 and r2 == 68) +local b, c, d, e = string.byte("ABCDE",2,10) +assert(b == 66 and c == 67 and d == 68 and e == 69) + + +-- string.gsub(s, pattern, replacement[, limit]) +s = "Hello banana" +assert(string.gsub(s, "banana", "Lua user") == "Hello Lua user") +assert(string.gsub(s, "a", "A", 2) == "Hello bAnAna") +assert(string.gsub(s, "a(n)", "a(%1)") == "Hello ba(n)a(n)a") + +r, r2 = string.gsub(s, "(a)(n)", "%2%1") +assert(r == "Hello bnanaa" and r2 == 2) + +r, r2 = string.gsub(s, "(a)(n)", "%2%1", 1) +assert(r == "Hello bnaana" and r2 == 1) + +r = {} +r2, r3 = string.gsub(s, "(%w+)", function(a) table.insert(r, a) end) +assert(r2 == s and r3 == #r and r[1] == "Hello" and r[2] == "banana") + +r2, r3 = string.gsub(s, "(%w+)", function(w) return string.len(w) end, 1) -- replace with length +assert(r2 == "5 banana" and r3 == 1) + +r, r2 = string.gsub(s, "(a)", string.upper, 10) -- make all "a"s found uppercase +assert(r == "Hello bAnAnA" and r2 == 3) + +--r, r2 = string.gsub(s, "(a)(n)", function(a,b) return b..a end) -- reverse any "an"s +--assert(r == "Hello bnanaa" and r2 == 2) +-- function with two parameters are not supported +-- actually the capturing parenthesis are not supported with gsub, the whole matched expression is passed to the function + + +-- string.find(s, pattern[, index, plain]) +r, r2 = string.find("Hello Lua user", "Lua") +assert(r == 7 and r2 == 9) + +r, r2 = string.find("Hello Lua user", "banana") +assert(r == nil and r2 == nil) + +r, r2 = string.find("Hello Lua user", "Lua", 1) -- start at first character +assert(r == 7 and r2 == 9) + +r, r2 = string.find("Hello Lua user", "Lua", 8) -- "Lua" not found again after character 8 +assert(r == nil and r2 == nil) + +r, r2 = string.find("Hello Lua user", "e", -5) -- first "e" 5 characters from the end +assert(r == 13 and r2 == 13) + +r, r2 = string.find("Hello Lua user", "%su") -- find a space character followed by "u" +assert(r == 10 and r2 == 11) + +r, r2 = string.find("Hello Lua user", "%su", 1, true) -- turn on plain searches, now not found +assert(r == nil and r2 == nil) + + +r, r2, r3 = string.find("11 12 13", "(1%d)") +assert(r == 1 and r2 == 2 and r3 == "11") + +r, r2, r3 = string.find("11 12 13", "(1%d)", 3) +assert(r == 4 and r2 == 5 and r3 == "12") + +r, r2, r3, r4, r5 = string.find("11 12 13", "(1%d) (1%d) (1%d)") +assert(r == 1 and r2 == 8 and r3 == "11" and r4 == "12" and r5 == "13") + +r, r2, r3 = string.find("123456789", "2(34)5") -- turn on plain searches, now not found +assert(r == 2 and r2 == 5 and r3 == "34") + +r = string.find("123456789", "45678", 7) -- turn on plain searches, now not found +assert(r == nil) + +r, r2 = string.find("one.two.three.for", ".") -- turn on plain searches, now not found +assert(r == 1 and r2 == 1) + +r, r2 = string.find("one.two.three.for", ".", 5) -- turn on plain searches, now not found +assert(r == 5 and r2 == 5) + +r, r2 = string.find("one.two.three.for", ".", 5, true) -- turn on plain searches, now not found +assert(r == 8 and r2 == 8) + + +-- string.match(s, pattern[, index]) +assert(string.match("I have 2 questions for you.", "%d+ %a+") == "2 questions") +assert(string.match("I have 2 questions for you.", "%D+", 3) == "have ") +assert(string.match("I have 2 questions for you.", "foobar") == nil) +assert(string.match("I have 2 questions for you.", "2 (%w+)") == "questions") +assert(string.match("I have 2 questions for you.", "%bso") == "stio") -- %dxy +assert(string.match("I have 2 questins for you.", "%bso") == "stins for yo") -- %dxy + + +-- string.gmatch(pattern) +r = {} +for word in string.gmatch("Hello Lua user", "%a+") do + table.insert(r, word) +end +r2 = { "Hello", "Lua", "user"} +for i, word in ipairs(r) do + assert(word == r2[i]) +end + +r = {} +for word in string.gmatch('axbycxdye', 'x([^x]+)') do + table.insert(r, word) +end +r2 = {"byc", "dye"} +for i, word in ipairs(r) do + assert(word == r2[i]) +end + +r = {} +for word in string.gmatch('a(b)c(d)e', '%b()') do + table.insert(r, word) +end +r2 = {"(b)", "(d)"} +for i, word in ipairs(r) do + assert(word == r2[i]) +end + + +-- string.format(s, e1[, e2, ...]) +assert(string.format("%s %q", "Hello", "Lua user!") == 'Hello "Lua user!"') -- string and quoted string +assert(string.format("%c%c%c", 76,117,97) == "Lua") -- char +assert(string.format("%e, %E", math.pi,math.pi) == "3.141593e+000, 3.141593E+000") -- exponent +assert(string.format("%f, %g", math.pi,math.pi) == "3.141593, 3.14159") -- float and compact float +assert(string.format("%d, %i, %u", -100,-100,-100) == "-100, -100, 4294967196") -- signed, signed, unsigned integer +assert(string.format("%o, %x, %X", -100,-100,-100) == "37777777634, ffffff9c, FFFFFF9C") -- octal, hex, hex From b3e10f24111d2aa8008a637efa47e993f565818a Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sat, 28 Dec 2013 15:28:52 +0100 Subject: [PATCH 58/59] Fixed string.gsub() when the replacement is a function that returns a value --- src/lualib.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lualib.js b/src/lualib.js index 2ebb560..00f9527 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1480,7 +1480,12 @@ lua_libs["string"] = { if (replacementType == "string") { newMatchChunk = matchChunk.replace(regex, replacement); } else if (replacementType == "function") { - newMatchChunk = matchChunk.replace(regex, replacement(match)); + var result = replacement(match)[0]; // the function always returns an array + if (result == null) { + newMatchChunk = matchChunk; + } else { + newMatchChunk = matchChunk.replace(regex, result); + } } newS += newMatchChunk; s = s.substr(matchEndIndex); From 30133793a89d13a520f9185227430583899aabad Mon Sep 17 00:00:00 2001 From: Florent POUJOL Date: Sun, 29 Dec 2013 18:00:49 +0100 Subject: [PATCH 59/59] string.gsub() now passes the correct argument(s) to the replacement function + updated tests --- src/lualib.js | 12 +++++++++++- tests/string.lua | 9 +++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/lualib.js b/src/lualib.js index 00f9527..afa8765 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -1480,7 +1480,17 @@ lua_libs["string"] = { if (replacementType == "string") { newMatchChunk = matchChunk.replace(regex, replacement); } else if (replacementType == "function") { - var result = replacement(match)[0]; // the function always returns an array + var result = null; + // match is the whole expression matched by the pattern, now get captures + var matches = match.match(pattern); // not global to get the captures ! + + if (matches[1] != null) { + matches = matches.slice(1); + result = replacement.apply(null, matches)[0]; + } else { + result = replacement(match)[0]; // the function always returns an array + } + if (result == null) { newMatchChunk = matchChunk; } else { diff --git a/tests/string.lua b/tests/string.lua index a91f4fc..ab5acb6 100644 --- a/tests/string.lua +++ b/tests/string.lua @@ -34,10 +34,11 @@ assert(r2 == "5 banana" and r3 == 1) r, r2 = string.gsub(s, "(a)", string.upper, 10) -- make all "a"s found uppercase assert(r == "Hello bAnAnA" and r2 == 3) ---r, r2 = string.gsub(s, "(a)(n)", function(a,b) return b..a end) -- reverse any "an"s ---assert(r == "Hello bnanaa" and r2 == 2) --- function with two parameters are not supported --- actually the capturing parenthesis are not supported with gsub, the whole matched expression is passed to the function +r, r2 = string.gsub(s, "(a)(n)", function(a,b) return b..a end) -- reverse any "an"s +assert(r == "Hello bnanaa" and r2 == 2) + +r, r2 = string.gsub(s, "a(n)", function(a) assert(a == "n"); return "ab" end) -- replace "n" by "b" +assert(r == "Hello bababa" and r2 == 2) -- string.find(s, pattern[, index, plain])