From 1c36d2cc9c25d74a806ee74c0ee28da32d5e84ba Mon Sep 17 00:00:00 2001
From: Marius <mariausol@gmail.com>
Date: Tue, 11 Dec 2012 00:40:14 +0200
Subject: beta 2012.12.10 23:20

---
 scripts/context/lua/mtxrun.lua         | 571 ++++++++++++++++-----------------
 scripts/context/stubs/mswin/mtxrun.lua | 571 ++++++++++++++++-----------------
 scripts/context/stubs/unix/mtxrun      | 571 ++++++++++++++++-----------------
 3 files changed, 831 insertions(+), 882 deletions(-)

(limited to 'scripts')

diff --git a/scripts/context/lua/mtxrun.lua b/scripts/context/lua/mtxrun.lua
index be3acb7da..01c601eb5 100644
--- a/scripts/context/lua/mtxrun.lua
+++ b/scripts/context/lua/mtxrun.lua
@@ -1625,7 +1625,8 @@ function lpeg.replacer(one,two,makefunction)
         elseif no == 1 then
             local o = one[1]
             one, two = P(o[1]), o[2]
-            pattern = Cs(((1-one)^1 + one/two)^0)
+         -- pattern = Cs(((1-one)^1 + one/two)^0)
+            pattern = Cs((one/two + 1)^0)
         else
             for i=1,no do
                 local o = one[i]
@@ -1636,7 +1637,28 @@ function lpeg.replacer(one,two,makefunction)
     else
         one = P(one)
         two = two or ""
-        pattern = Cs(((1-one)^1 + one/two)^0)
+     -- pattern = Cs(((1-one)^1 + one/two)^0)
+        pattern = Cs((one/two +1)^0)
+    end
+    if makefunction then
+        return function(str)
+            return lpegmatch(pattern,str)
+        end
+    else
+        return pattern
+    end
+end
+
+function lpeg.finder(lst,makefunction)
+    local pattern
+    if type(lst) == "table" then
+        local p = P(false)
+        for i=1,#lst do
+            p = p + P(lst[i])
+        end
+        pattern = (p + 1)^0
+    else
+        pattern = (P(lst) + 1)^0
     end
     if makefunction then
         return function(str)
@@ -3058,66 +3080,145 @@ file       = file or { }
 local file = file
 
 local insert, concat = table.insert, table.concat
-local find, gmatch, match, gsub, sub, char, lower = string.find, string.gmatch, string.match, string.gsub, string.sub, string.char, string.lower
+local match = string.match
 local lpegmatch = lpeg.match
 local getcurrentdir, attributes = lfs.currentdir, lfs.attributes
+local checkedsplit = string.checkedsplit
+
+-- local patterns = file.patterns or { }
+-- file.patterns  = patterns
 
-local P, R, S, C, Cs, Cp, Cc = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc
+local P, R, S, C, Cs, Cp, Cc, Ct = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc, lpeg.Ct
 
-local function dirname(name,default)
-    return match(name,"^(.+)[/\\].-$") or (default or "")
+local colon     = P(":")
+local period    = P(".")
+local periods   = P("..")
+local fwslash   = P("/")
+local bwslash   = P("\\")
+local slashes   = S("\\/")
+local noperiod  = 1-period
+local noslashes = 1-slashes
+local name      = noperiod^1
+local suffix    = period/"" * (1-period-slashes)^1 * -1
+
+local pattern = C((noslashes^0 * slashes^1)^1)
+
+local function pathpart(name,default)
+    return lpegmatch(pattern,name) or default or ""
 end
 
+local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1
+
 local function basename(name)
-    return match(name,"^.+[/\\](.-)$") or name
+    return lpegmatch(pattern,name) or name
 end
 
--- local function nameonly(name)
---     return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
--- end
+local pattern = (noslashes^0 * slashes^1)^0 * Cs((1-suffix)^1) * suffix^0
 
 local function nameonly(name)
-    return (gsub(match(name,"^.+[/\\](.-)$") or name,"%.[%a%d]+$",""))
+    return lpegmatch(pattern,name) or name
 end
 
-local function suffixonly(name,default)
-    return match(name,"^.+%.([^/\\]-)$") or default or ""
-end
+local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1
 
-local function splitname(name)
-    local n, s = match(name,"^(.+)%.([^/\\]-)$")
-    return n or name, s or ""
+local function suffixonly(name)
+    return lpegmatch(pattern,name) or ""
 end
 
+file.pathpart   = pathpart
 file.basename   = basename
-
-file.pathpart   = dirname
-file.dirname    = dirname
-
 file.nameonly   = nameonly
-
 file.suffixonly = suffixonly
-file.extname    = suffixonly -- obsolete
 file.suffix     = suffixonly
 
-function file.removesuffix(filename)
-    return (gsub(filename,"%.[%a%d]+$",""))
+file.dirname    = pathpart   -- obsolete
+file.extname    = suffixonly -- obsolete
+
+-- actually these are schemes
+
+local drive  = C(R("az","AZ")) * colon
+local path   = C(((1-slashes)^0 * slashes)^0)
+local suffix = period * C(P(1-period)^0 * P(-1))
+local base   = C((1-suffix)^0)
+local rest   = C(P(1)^0)
+
+drive  = drive  + Cc("")
+path   = path   + Cc("")
+base   = base   + Cc("")
+suffix = suffix + Cc("")
+
+local pattern_a =   drive * path  *   base * suffix
+local pattern_b =           path  *   base * suffix
+local pattern_c = C(drive * path) * C(base * suffix) -- trick: two extra captures
+local pattern_d =           path  *   rest
+
+function file.splitname(str,splitdrive)
+    if splitdrive then
+        return lpegmatch(pattern_a,str) -- returns drive, path, base, suffix
+    else
+        return lpegmatch(pattern_b,str) -- returns path, base, suffix
+    end
+end
+
+function file.splitbase(str)
+    return lpegmatch(pattern_d,str) -- returns path, base+suffix
+end
+
+function file.nametotable(str,splitdrive) -- returns table
+    local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str)
+    if splitdrive then
+        return {
+            path    = path,
+            drive   = drive,
+            subpath = subpath,
+            name    = name,
+            base    = base,
+            suffix  = suffix,
+        }
+    else
+        return {
+            path    = path,
+            name    = name,
+            base    = base,
+            suffix  = suffix,
+        }
+    end
+end
+
+local pattern = Cs(((period * noperiod^1 * -1)/"" + 1)^1)
+
+function file.removesuffix(name)
+    return lpegmatch(pattern,name)
 end
 
+-- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
+--
+-- function file.addsuffix(name, suffix)
+--     local p = lpegmatch(pattern,name)
+--     if p then
+--         return name
+--     else
+--         return name .. "." .. suffix
+--     end
+-- end
+
+local suffix  = period/"" * (1-period-slashes)^1 * -1
+local pattern = Cs((noslashes^0 * slashes^1)^0 * ((1-suffix)^1)) * Cs(suffix)
+
 function file.addsuffix(filename, suffix, criterium)
     if not suffix or suffix == "" then
         return filename
     elseif criterium == true then
         return filename .. "." .. suffix
     elseif not criterium then
-        local n, s = splitname(filename)
+        local n, s = lpegmatch(pattern,filename)
         if not s or s == "" then
             return filename .. "." .. suffix
         else
             return filename
         end
     else
-        local n, s = splitname(filename)
+        local n, s = lpegmatch(pattern,filename)
         if s and s ~= "" then
             local t = type(criterium)
             if t == "table" then
@@ -3134,88 +3235,49 @@ function file.addsuffix(filename, suffix, criterium)
                 end
             end
         end
-        return n .. "." .. suffix
+        return (n or filename) .. "." .. suffix
     end
 end
 
+-- print("1 " .. file.addsuffix("name","new")                   .. " -> name.new")
+-- print("2 " .. file.addsuffix("name.old","new")               .. " -> name.old")
+-- print("3 " .. file.addsuffix("name.old","new",true)          .. " -> name.old.new")
+-- print("4 " .. file.addsuffix("name.old","new","new")         .. " -> name.new")
+-- print("5 " .. file.addsuffix("name.old","new","old")         .. " -> name.old")
+-- print("6 " .. file.addsuffix("name.old","new","foo")         .. " -> name.new")
+-- print("7 " .. file.addsuffix("name.old","new",{"foo","bar"}) .. " -> name.new")
+-- print("8 " .. file.addsuffix("name.old","new",{"old","bar"}) .. " -> name.old")
 
-function file.replacesuffix(filename, suffix)
-    return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
+local suffix  = period * (1-period-slashes)^1 * -1
+local pattern = Cs((1-suffix)^0)
+
+function file.replacesuffix(name,suffix)
+    if suffix and suffix ~= "" then
+        return lpegmatch(pattern,name) .. "." .. suffix
+    else
+        return name
+    end
 end
 
-local trick_1 = char(1)
-local trick_2 = "^" .. trick_1 .. "/+"
+--
 
-function file.join(...) -- rather dirty
-    local lst = { ... }
-    local a, b = lst[1], lst[2]
-    if not a or a == "" then -- not a added
-        lst[1] = trick_1
-    elseif b and find(a,"^/+$") and find(b,"^/") then
-        lst[1] = ""
-        lst[2] = gsub(b,"^/+","")
-    end
-    local pth = concat(lst,"/")
-    pth = gsub(pth,"\\","/")
-    local a, b = match(pth,"^(.*://)(.*)$")
-    if a and b then
-        return a .. gsub(b,"//+","/")
-    end
-    a, b = match(pth,"^(//)(.*)$")
-    if a and b then
-        return a .. gsub(b,"//+","/")
-    end
-    pth = gsub(pth,trick_2,"")
-    return (gsub(pth,"//+","/"))
-end
-
--- local slash = P("/")
--- local colon = P(":")
-
--- local replacer  = lpeg.replacer(S("\\/")^1,"/")
--- local stripper  = Cs(P(slash)^0/"" * replacer)
--- local isnetwork = slash * slash * (1-slash) + (1-slash-colon)^1 * colon
--- local isroot    = slash^1 * -1
--- local hasroot   = slash^1
-
--- function file.newjoin(...) -- rather dirty
---     local lst = { ... }
---     local one = lst[1]
---     if lpegmatch(isnetwork,one) then
---         local two = lpegmatch(replacer,concat(lst,"/",2))
---         return one .. two
---     elseif lpegmatch(isroot,one) then
---         local two = lpegmatch(replacer,concat(lst,"/",2))
---         if lpegmatch(hasroot,two) then
---             return two
---         else
---             return "/" .. two
---         end
---     elseif one == "" then
---         return lpegmatch(stripper,concat(lst,"/",2))
---     else
---         return lpegmatch(replacer,concat(lst,"/"))
---     end
--- end
+local reslasher = lpeg.replacer(S("\\"),"/")
 
--- print(file.join("//","/y"))
--- print(file.join("/","/y"))
--- print(file.join("","/y"))
--- print(file.join("/x/","/y"))
--- print(file.join("x/","/y"))
--- print(file.join("http://","/y"))
--- print(file.join("http://a","/y"))
--- print(file.join("http:///a","/y"))
--- print(file.join("//nas-1","/y"))
+function file.reslash(str)
+    return lpegmatch(reslasher,str)
+end
 
 -- We should be able to use:
 --
+-- local writable = P(1) * P("w") * Cc(true)
+--
 -- function file.is_writable(name)
---     local a = attributes(name) or attributes(dirname(name,"."))
---     return a and sub(a.permissions,2,2) == "w"
+--     local a = attributes(name) or attributes(pathpart(name,"."))
+--     return a and lpegmatch(writable,a.permissions) or false
 -- end
 --
--- But after some testing Taco and I came up with:
+-- But after some testing Taco and I came up with the more robust
+-- variant:
 
 function file.is_writable(name)
     if lfs.isdir(name) then
@@ -3243,9 +3305,11 @@ function file.is_writable(name)
     return false
 end
 
+local readable = P("r") * Cc(true)
+
 function file.is_readable(name)
     local a = attributes(name)
-    return a and sub(a.permissions,1,1) == "r"
+    return a and lpegmatch(readable,a.permissions) or false
 end
 
 file.isreadable = file.is_readable -- depricated
@@ -3256,41 +3320,74 @@ function file.size(name)
     return a and a.size or 0
 end
 
--- todo: lpeg \\ / .. does not save much
-
-local checkedsplit = string.checkedsplit
-
-function file.splitpath(str,separator) -- string
-    str = gsub(str,"\\","/")
-    return checkedsplit(str,separator or io.pathseparator)
+function file.splitpath(str,separator) -- string .. reslash is a bonus (we could do a direct split)
+    return checkedsplit(lpegmatch(reslasher,str),separator or io.pathseparator)
 end
 
 function file.joinpath(tab,separator) -- table
     return concat(tab,separator or io.pathseparator) -- can have trailing //
 end
 
--- we can hash them weakly
+local stripper  = Cs(P(fwslash)^0/"" * reslasher)
+local isnetwork = fwslash * fwslash * (1-fwslash) + (1-fwslash-colon)^1 * colon
+local isroot    = fwslash^1 * -1
+local hasroot   = fwslash^1
+
+function file.join(...) -- rather dirty
+    local lst = { ... }
+    local one = lst[1]
+    if lpegmatch(isnetwork,one) then
+        local two = lpegmatch(reslasher,concat(lst,"/",2))
+        return one .. "/" .. two
+    elseif lpegmatch(isroot,one) then
+        local two = lpegmatch(reslasher,concat(lst,"/",2))
+        if lpegmatch(hasroot,two) then
+            return two
+        else
+            return "/" .. two
+        end
+    elseif one == "" then
+        return lpegmatch(stripper,concat(lst,"/",2))
+    else
+        return lpegmatch(reslasher,concat(lst,"/"))
+    end
+end
+
+-- print(file.join("c:/whatever","name"))
+-- print(file.join("//","/y"))
+-- print(file.join("/","/y"))
+-- print(file.join("","/y"))
+-- print(file.join("/x/","/y"))
+-- print(file.join("x/","/y"))
+-- print(file.join("http://","/y"))
+-- print(file.join("http://a","/y"))
+-- print(file.join("http:///a","/y"))
+-- print(file.join("//nas-1","/y"))
+
+-- The previous one fails on "a.b/c"  so Taco came up with a split based
+-- variant. After some skyping we got it sort of compatible with the old
+-- one. After that the anchoring to currentdir was added in a better way.
+-- Of course there are some optimizations too. Finally we had to deal with
+-- windows drive prefixes and things like sys://. Eventually gsubs and
+-- finds were replaced by lpegs.
 
+local drivespec    = R("az","AZ")^1 * colon
+local anchors      = fwslash + drivespec
+local untouched    = periods + (1-period)^1 * P(-1)
+local splitstarter = (Cs(drivespec * (bwslash/"/" + fwslash)^0) + Cc(false)) * Ct(lpeg.splitat(S("/\\")^1))
+local absolute     = fwslash
 
 function file.collapsepath(str,anchor)
-    if anchor and not find(str,"^/") and not find(str,"^%a:") then
+    if anchor and not lpegmatch(anchors,str) then
         str = getcurrentdir() .. "/" .. str
     end
     if str == "" or str =="." then
         return "."
-    elseif find(str,"^%.%.") then
-        str = gsub(str,"\\","/")
-        return str
-    elseif not find(str,"%.") then
-        str = gsub(str,"\\","/")
-        return str
-    end
-    str = gsub(str,"\\","/")
-    local starter, rest = match(str,"^(%a+:/*)(.-)$")
-    if starter then
-        str = rest
+    elseif lpegmatch(untouched,str) then
+        return lpegmatch(reslasher,str)
     end
-    local oldelements = checkedsplit(str,"/")
+    local starter, oldelements = lpegmatch(splitstarter,str)
+-- inspect(oldelements)
     local newelements = { }
     local i = #oldelements
     while i > 0 do
@@ -3320,7 +3417,7 @@ function file.collapsepath(str,anchor)
         return starter or "."
     elseif starter then
         return starter .. concat(newelements, '/')
-    elseif find(str,"^/") then
+    elseif lpegmatch(absolute,str) then
         return "/" .. concat(newelements,'/')
     else
         return concat(newelements, '/')
@@ -3337,29 +3434,21 @@ end
 -- test("a/./b/..") test("a/aa/../b/bb") test("a/.././././b/..") test("a/./././b/..")
 -- test("a/b/c/../..") test("./a/b/c/../..") test("a/b/c/../..")
 
+local validchars = R("az","09","AZ","--","..")
+local pattern_a  = lpeg.replacer(1-validchars)
+local pattern_a  = Cs((validchars + P(1)/"-")^1)
+local whatever   = P("-")^0 / ""
+local pattern_b  = Cs(whatever * (1 - whatever * -1)^1)
+
 function file.robustname(str,strict)
-    str = gsub(str,"[^%a%d%/%-%.\\]+","-")
+    str = lpegmatch(pattern_a,str) or str
     if strict then
-        return lower(gsub(str,"^%-*(.-)%-*$","%1"))
+        return lpegmatch(pattern_b,str) or str -- two step is cleaner (less backtracking)
     else
         return str
     end
 end
 
--- local pattern_a = lpeg.replacer(1-R("az","09","AZ","--",".."))
--- local pattern_a = Cs((R("az","09","AZ","--","..") + P(1)/"-")^1)
--- local whatever  = P("-")^0 / ""
--- local pattern_b = Cs(whatever * (1 - whatever * -1)^1)
-
--- function file.robustname(str,strict)
---     str = lpegmatch(pattern_a,str) or str
---     if strict then
---         return lpegmatch(pattern_b,str) or str -- two step is cleaner (less backtracking)
---     else
---         return str
---     end
--- end
-
 file.readdata = io.loaddata
 file.savedata = io.savedata
 
@@ -3367,92 +3456,17 @@ function file.copy(oldname,newname)
     file.savedata(newname,io.loaddata(oldname))
 end
 
--- lpeg variants, slightly faster, not always
-
--- local period    = P(".")
--- local slashes   = S("\\/")
--- local noperiod  = 1-period
--- local noslashes = 1-slashes
--- local name      = noperiod^1
-
--- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1
-
--- function file.suffixonly(name)
---     return lpegmatch(pattern,name) or ""
--- end
-
--- local pattern = Cs(((period * noperiod^1 * -1)/"" + 1)^1)
-
--- function file.removesuffix(name)
---     return lpegmatch(pattern,name)
--- end
-
--- local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1
-
--- function file.basename(name)
---     return lpegmatch(pattern,name) or name
--- end
-
--- local pattern = Cs ((1 - slashes * noslashes^1 * -1)^1)
-
--- function file.dirname(name)
---     return lpegmatch(pattern,name) or ""
--- end
-
--- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
-
--- function file.addsuffix(name, suffix)
---     local p = lpegmatch(pattern,name)
---     if p then
---         return name
---     else
---         return name .. "." .. suffix
---     end
--- end
-
--- local suffix = period * (1-period-slashes)^1 * -1
--- local pattern = Cs((1-suffix)^1)
-
--- function file.replacesuffix(name,suffix)
---     if suffix and suffix ~= "" then
---         return lpegmatch(pattern,name) .. "." .. suffix
---     else
---         return name
---     end
--- end
-
--- local path    = noslashes^0 * slashes^1
--- local suffix  = period * (1-period-slashes)^1 * -1
--- local pattern = path^0 * Cs((1-suffix)^1) * suffix^0
-
--- function file.nameonly(name)
---     return lpegmatch(pattern,name) or name
--- end
-
--- local test = file.suffixonly
--- local test = file.basename
--- local test = file.dirname
--- local test = file.addsuffix
--- local test = file.replacesuffix
--- local test = file.nameonly
-
--- print(1,test("./a/b/c/abd.def.xxx","!!!"))
--- print(2,test("./../b/c/abd.def.xxx","!!!"))
--- print(3,test("a/b/c/abd.def.xxx","!!!"))
--- print(4,test("a/b/c/def.xxx","!!!"))
--- print(5,test("a/b/c/def","!!!"))
--- print(6,test("def","!!!"))
--- print(7,test("def.xxx","!!!"))
-
--- local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
-
 -- also rewrite previous
 
 local letter    = R("az","AZ") + S("_-+")
 local separator = P("://")
 
-local qualified = P(".")^0 * P("/") + letter*P(":") + letter^1*separator + letter^1 * P("/")
-local rootbased = P("/") + letter*P(":")
+local qualified = period^0 * fwslash
+                + letter   * colon
+                + letter^1 * separator
+                + letter^1 * fwslash
+local rootbased = fwslash
+                + letter * colon
 
 lpeg.patterns.qualified = qualified
 lpeg.patterns.rootbased = rootbased
@@ -3467,61 +3481,6 @@ function file.is_rootbased_path(filename)
     return lpegmatch(rootbased,filename) ~= nil
 end
 
--- actually these are schemes
-
-local slash  = S("\\/")
-local period = P(".")
-local drive  = C(R("az","AZ")) * P(":")
-local path   = C(((1-slash)^0 * slash)^0)
-local suffix = period * C(P(1-period)^0 * P(-1))
-local base   = C((1-suffix)^0)
-local rest   = C(P(1)^0)
-
-drive  = drive  + Cc("")
-path   = path   + Cc("")
-base   = base   + Cc("")
-suffix = suffix + Cc("")
-
-local pattern_a =   drive * path  *   base * suffix
-local pattern_b =           path  *   base * suffix
-local pattern_c = C(drive * path) * C(base * suffix) -- trick: two extra captures
-local pattern_d =           path  *   rest
-
-function file.splitname(str,splitdrive)
-    if splitdrive then
-        return lpegmatch(pattern_a,str) -- returns drive, path, base, suffix
-    else
-        return lpegmatch(pattern_b,str) -- returns path, base, suffix
-    end
-end
-
-function file.splitbase(str)
-    return lpegmatch(pattern_d,str) -- returns path, base+suffix
-end
-
-function file.nametotable(str,splitdrive) -- returns table
-    local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str)
-    if splitdrive then
-        return {
-            path    = path,
-            drive   = drive,
-            subpath = subpath,
-            name    = name,
-            base    = base,
-            suffix  = suffix,
-        }
-    else
-        return {
-            path    = path,
-            name    = name,
-            base    = base,
-            suffix  = suffix,
-        }
-    end
-end
-
--- print(file.splitbase("a/b/c.txt"))
-
 -- function test(t) for k, v in next, t do print(v, "=>", file.splitname(v)) end end
 --
 -- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
@@ -3529,6 +3488,14 @@ end
 -- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
 -- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
 
+-- -- maybe:
+--
+-- if os.type == "windows" then
+--     local currentdir = getcurrentdir
+--     function getcurrentdir()
+--         return lpegmatch(reslasher,currentdir())
+--     end
+-- end
 
 -- for myself:
 
@@ -3537,6 +3504,21 @@ function file.strip(name,dir)
     return a ~= "" and a or name
 end
 
+-- local debuglist = {
+--     "pathpart", "basename", "nameonly", "suffixonly", "suffix", "dirname", "extname",
+--     "addsuffix", "removesuffix", "replacesuffix", "join",
+--     "strip","collapsepath", "joinpath", "splitpath",
+-- }
+
+-- for i=1,#debuglist do
+--     local name = debuglist[i]
+--     local f = file[name]
+--     file[name] = function(...)
+--         print(name,f(...))
+--         return f(...)
+--     end
+-- end
+
 
 end -- of closure
 
@@ -3661,7 +3643,7 @@ if not modules then modules = { } end modules ['l-url'] = {
     license   = "see context related readme files"
 }
 
-local char, gmatch, gsub, format, byte, find = string.char, string.gmatch, string.gsub, string.format, string.byte, string.find
+local char, format, byte = string.char, string.format, string.byte
 local concat = table.concat
 local tonumber, type = tonumber, type
 local P, C, R, S, Cs, Cc, Ct, Cf, Cg, V = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cs, lpeg.Cc, lpeg.Ct, lpeg.Cf, lpeg.Cg, lpeg.V
@@ -3700,6 +3682,8 @@ local nothing     = Cc("")
 local escapedchar = (percent * C(hexdigit * hexdigit)) / tochar
 local escaped     = (plus / " ") + escapedchar
 
+local noslash     = P("/") / ""
+
 -- we assume schemes with more than 1 character (in order to avoid problems with windows disks)
 -- we also assume that when we have a scheme, we also have an authority
 --
@@ -3878,29 +3862,23 @@ function url.construct(hash) -- dodo: we need to escape !
     return lpegmatch(escaper,concat(fullurl))
 end
 
-function url.filename(filename) -- why no lpeg here ?
-    local t = hashed(filename)
-    return (t.scheme == "file" and (gsub(t.path,"^/([a-zA-Z])([:|])/)","%1:"))) or filename
+local pattern = Cs(noslash * R("az","AZ") * (S(":|")/":") * noslash * P(1)^0)
+
+function url.filename(filename)
+    local spec = hashed(filename)
+    local path = spec.path
+    return (spec.scheme == "file" and path and lpegmatch(pattern,path)) or filename
 end
 
+-- print(url.filename("/c|/test"))
+-- print(url.filename("/c/test"))
+
 local function escapestring(str)
     return lpegmatch(escaper,str)
 end
 
 url.escape = escapestring
 
--- function url.query(str) -- separator could be an option
---     if type(str) == "string" then
---         local t = { }
---         for k, v in gmatch(str,"([^&=]*)=([^&=]*)") do
---             t[k] = v
---         end
---         return t
---     else
---         return str
---     end
--- end
-
 function url.query(str)
     if type(str) == "string" then
         return lpegmatch(splitquery,str) or ""
@@ -3928,14 +3906,19 @@ end
 
 -- /test/ | /test | test/ | test => test
 
+local pattern = Cs(noslash^0 * (1 - noslash * P(-1))^0)
+
 function url.barepath(path)
     if not path or path == "" then
         return ""
     else
-        return (gsub(path,"^/?(.-)/?$","%1"))
+        return lpegmatch(pattern,path)
     end
 end
 
+-- print(url.barepath("/test"),url.barepath("test/"),url.barepath("/test/"),url.barepath("test"))
+-- print(url.barepath("/x/yz"),url.barepath("x/yz/"),url.barepath("/x/yz/"),url.barepath("x/yz"))
+
 
 
 
@@ -6191,8 +6174,8 @@ end
 -- for chem (currently one level)
 
 local value     = P(lbrace * C((nobrace + nestedbraces)^0) * rbrace)
-                + C(digit^1 * lparent * (noparent + nestedparents)^0 * rparent)
-                + C((nestedbraces + (1-comma))^0)
+                + C(digit^1 * lparent * (noparent + nestedparents)^1 * rparent)
+                + C((nestedbraces + (1-comma))^1)
 local pattern_a = spaces * Ct(value*(separator*value)^0)
 
 local function repeater(n,str)
@@ -6216,15 +6199,15 @@ local function repeater(n,str)
 end
 
 local value     = P(lbrace * C((nobrace + nestedbraces)^0) * rbrace)
-                + (C(digit^1)/tonumber * lparent * Cs((noparent + nestedparents)^0) * rparent) / repeater
-                + C((nestedbraces + (1-comma))^0)
+                + (C(digit^1)/tonumber * lparent * Cs((noparent + nestedparents)^1) * rparent) / repeater
+                + C((nestedbraces + (1-comma))^1)
 local pattern_b = spaces * Ct(value*(separator*value)^0)
 
-function parsers.settings_to_array_with_repeat(str,expand)
+function parsers.settings_to_array_with_repeat(str,expand) -- beware: "" =>  { }
     if expand then
-        return lpegmatch(pattern_b,str)
+        return lpegmatch(pattern_b,str) or { }
     else
-        return lpegmatch(pattern_a,str)
+        return lpegmatch(pattern_a,str) or { }
     end
 end
 
diff --git a/scripts/context/stubs/mswin/mtxrun.lua b/scripts/context/stubs/mswin/mtxrun.lua
index be3acb7da..01c601eb5 100644
--- a/scripts/context/stubs/mswin/mtxrun.lua
+++ b/scripts/context/stubs/mswin/mtxrun.lua
@@ -1625,7 +1625,8 @@ function lpeg.replacer(one,two,makefunction)
         elseif no == 1 then
             local o = one[1]
             one, two = P(o[1]), o[2]
-            pattern = Cs(((1-one)^1 + one/two)^0)
+         -- pattern = Cs(((1-one)^1 + one/two)^0)
+            pattern = Cs((one/two + 1)^0)
         else
             for i=1,no do
                 local o = one[i]
@@ -1636,7 +1637,28 @@ function lpeg.replacer(one,two,makefunction)
     else
         one = P(one)
         two = two or ""
-        pattern = Cs(((1-one)^1 + one/two)^0)
+     -- pattern = Cs(((1-one)^1 + one/two)^0)
+        pattern = Cs((one/two +1)^0)
+    end
+    if makefunction then
+        return function(str)
+            return lpegmatch(pattern,str)
+        end
+    else
+        return pattern
+    end
+end
+
+function lpeg.finder(lst,makefunction)
+    local pattern
+    if type(lst) == "table" then
+        local p = P(false)
+        for i=1,#lst do
+            p = p + P(lst[i])
+        end
+        pattern = (p + 1)^0
+    else
+        pattern = (P(lst) + 1)^0
     end
     if makefunction then
         return function(str)
@@ -3058,66 +3080,145 @@ file       = file or { }
 local file = file
 
 local insert, concat = table.insert, table.concat
-local find, gmatch, match, gsub, sub, char, lower = string.find, string.gmatch, string.match, string.gsub, string.sub, string.char, string.lower
+local match = string.match
 local lpegmatch = lpeg.match
 local getcurrentdir, attributes = lfs.currentdir, lfs.attributes
+local checkedsplit = string.checkedsplit
+
+-- local patterns = file.patterns or { }
+-- file.patterns  = patterns
 
-local P, R, S, C, Cs, Cp, Cc = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc
+local P, R, S, C, Cs, Cp, Cc, Ct = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc, lpeg.Ct
 
-local function dirname(name,default)
-    return match(name,"^(.+)[/\\].-$") or (default or "")
+local colon     = P(":")
+local period    = P(".")
+local periods   = P("..")
+local fwslash   = P("/")
+local bwslash   = P("\\")
+local slashes   = S("\\/")
+local noperiod  = 1-period
+local noslashes = 1-slashes
+local name      = noperiod^1
+local suffix    = period/"" * (1-period-slashes)^1 * -1
+
+local pattern = C((noslashes^0 * slashes^1)^1)
+
+local function pathpart(name,default)
+    return lpegmatch(pattern,name) or default or ""
 end
 
+local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1
+
 local function basename(name)
-    return match(name,"^.+[/\\](.-)$") or name
+    return lpegmatch(pattern,name) or name
 end
 
--- local function nameonly(name)
---     return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
--- end
+local pattern = (noslashes^0 * slashes^1)^0 * Cs((1-suffix)^1) * suffix^0
 
 local function nameonly(name)
-    return (gsub(match(name,"^.+[/\\](.-)$") or name,"%.[%a%d]+$",""))
+    return lpegmatch(pattern,name) or name
 end
 
-local function suffixonly(name,default)
-    return match(name,"^.+%.([^/\\]-)$") or default or ""
-end
+local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1
 
-local function splitname(name)
-    local n, s = match(name,"^(.+)%.([^/\\]-)$")
-    return n or name, s or ""
+local function suffixonly(name)
+    return lpegmatch(pattern,name) or ""
 end
 
+file.pathpart   = pathpart
 file.basename   = basename
-
-file.pathpart   = dirname
-file.dirname    = dirname
-
 file.nameonly   = nameonly
-
 file.suffixonly = suffixonly
-file.extname    = suffixonly -- obsolete
 file.suffix     = suffixonly
 
-function file.removesuffix(filename)
-    return (gsub(filename,"%.[%a%d]+$",""))
+file.dirname    = pathpart   -- obsolete
+file.extname    = suffixonly -- obsolete
+
+-- actually these are schemes
+
+local drive  = C(R("az","AZ")) * colon
+local path   = C(((1-slashes)^0 * slashes)^0)
+local suffix = period * C(P(1-period)^0 * P(-1))
+local base   = C((1-suffix)^0)
+local rest   = C(P(1)^0)
+
+drive  = drive  + Cc("")
+path   = path   + Cc("")
+base   = base   + Cc("")
+suffix = suffix + Cc("")
+
+local pattern_a =   drive * path  *   base * suffix
+local pattern_b =           path  *   base * suffix
+local pattern_c = C(drive * path) * C(base * suffix) -- trick: two extra captures
+local pattern_d =           path  *   rest
+
+function file.splitname(str,splitdrive)
+    if splitdrive then
+        return lpegmatch(pattern_a,str) -- returns drive, path, base, suffix
+    else
+        return lpegmatch(pattern_b,str) -- returns path, base, suffix
+    end
+end
+
+function file.splitbase(str)
+    return lpegmatch(pattern_d,str) -- returns path, base+suffix
+end
+
+function file.nametotable(str,splitdrive) -- returns table
+    local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str)
+    if splitdrive then
+        return {
+            path    = path,
+            drive   = drive,
+            subpath = subpath,
+            name    = name,
+            base    = base,
+            suffix  = suffix,
+        }
+    else
+        return {
+            path    = path,
+            name    = name,
+            base    = base,
+            suffix  = suffix,
+        }
+    end
+end
+
+local pattern = Cs(((period * noperiod^1 * -1)/"" + 1)^1)
+
+function file.removesuffix(name)
+    return lpegmatch(pattern,name)
 end
 
+-- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
+--
+-- function file.addsuffix(name, suffix)
+--     local p = lpegmatch(pattern,name)
+--     if p then
+--         return name
+--     else
+--         return name .. "." .. suffix
+--     end
+-- end
+
+local suffix  = period/"" * (1-period-slashes)^1 * -1
+local pattern = Cs((noslashes^0 * slashes^1)^0 * ((1-suffix)^1)) * Cs(suffix)
+
 function file.addsuffix(filename, suffix, criterium)
     if not suffix or suffix == "" then
         return filename
     elseif criterium == true then
         return filename .. "." .. suffix
     elseif not criterium then
-        local n, s = splitname(filename)
+        local n, s = lpegmatch(pattern,filename)
         if not s or s == "" then
             return filename .. "." .. suffix
         else
             return filename
         end
     else
-        local n, s = splitname(filename)
+        local n, s = lpegmatch(pattern,filename)
         if s and s ~= "" then
             local t = type(criterium)
             if t == "table" then
@@ -3134,88 +3235,49 @@ function file.addsuffix(filename, suffix, criterium)
                 end
             end
         end
-        return n .. "." .. suffix
+        return (n or filename) .. "." .. suffix
     end
 end
 
+-- print("1 " .. file.addsuffix("name","new")                   .. " -> name.new")
+-- print("2 " .. file.addsuffix("name.old","new")               .. " -> name.old")
+-- print("3 " .. file.addsuffix("name.old","new",true)          .. " -> name.old.new")
+-- print("4 " .. file.addsuffix("name.old","new","new")         .. " -> name.new")
+-- print("5 " .. file.addsuffix("name.old","new","old")         .. " -> name.old")
+-- print("6 " .. file.addsuffix("name.old","new","foo")         .. " -> name.new")
+-- print("7 " .. file.addsuffix("name.old","new",{"foo","bar"}) .. " -> name.new")
+-- print("8 " .. file.addsuffix("name.old","new",{"old","bar"}) .. " -> name.old")
 
-function file.replacesuffix(filename, suffix)
-    return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
+local suffix  = period * (1-period-slashes)^1 * -1
+local pattern = Cs((1-suffix)^0)
+
+function file.replacesuffix(name,suffix)
+    if suffix and suffix ~= "" then
+        return lpegmatch(pattern,name) .. "." .. suffix
+    else
+        return name
+    end
 end
 
-local trick_1 = char(1)
-local trick_2 = "^" .. trick_1 .. "/+"
+--
 
-function file.join(...) -- rather dirty
-    local lst = { ... }
-    local a, b = lst[1], lst[2]
-    if not a or a == "" then -- not a added
-        lst[1] = trick_1
-    elseif b and find(a,"^/+$") and find(b,"^/") then
-        lst[1] = ""
-        lst[2] = gsub(b,"^/+","")
-    end
-    local pth = concat(lst,"/")
-    pth = gsub(pth,"\\","/")
-    local a, b = match(pth,"^(.*://)(.*)$")
-    if a and b then
-        return a .. gsub(b,"//+","/")
-    end
-    a, b = match(pth,"^(//)(.*)$")
-    if a and b then
-        return a .. gsub(b,"//+","/")
-    end
-    pth = gsub(pth,trick_2,"")
-    return (gsub(pth,"//+","/"))
-end
-
--- local slash = P("/")
--- local colon = P(":")
-
--- local replacer  = lpeg.replacer(S("\\/")^1,"/")
--- local stripper  = Cs(P(slash)^0/"" * replacer)
--- local isnetwork = slash * slash * (1-slash) + (1-slash-colon)^1 * colon
--- local isroot    = slash^1 * -1
--- local hasroot   = slash^1
-
--- function file.newjoin(...) -- rather dirty
---     local lst = { ... }
---     local one = lst[1]
---     if lpegmatch(isnetwork,one) then
---         local two = lpegmatch(replacer,concat(lst,"/",2))
---         return one .. two
---     elseif lpegmatch(isroot,one) then
---         local two = lpegmatch(replacer,concat(lst,"/",2))
---         if lpegmatch(hasroot,two) then
---             return two
---         else
---             return "/" .. two
---         end
---     elseif one == "" then
---         return lpegmatch(stripper,concat(lst,"/",2))
---     else
---         return lpegmatch(replacer,concat(lst,"/"))
---     end
--- end
+local reslasher = lpeg.replacer(S("\\"),"/")
 
--- print(file.join("//","/y"))
--- print(file.join("/","/y"))
--- print(file.join("","/y"))
--- print(file.join("/x/","/y"))
--- print(file.join("x/","/y"))
--- print(file.join("http://","/y"))
--- print(file.join("http://a","/y"))
--- print(file.join("http:///a","/y"))
--- print(file.join("//nas-1","/y"))
+function file.reslash(str)
+    return lpegmatch(reslasher,str)
+end
 
 -- We should be able to use:
 --
+-- local writable = P(1) * P("w") * Cc(true)
+--
 -- function file.is_writable(name)
---     local a = attributes(name) or attributes(dirname(name,"."))
---     return a and sub(a.permissions,2,2) == "w"
+--     local a = attributes(name) or attributes(pathpart(name,"."))
+--     return a and lpegmatch(writable,a.permissions) or false
 -- end
 --
--- But after some testing Taco and I came up with:
+-- But after some testing Taco and I came up with the more robust
+-- variant:
 
 function file.is_writable(name)
     if lfs.isdir(name) then
@@ -3243,9 +3305,11 @@ function file.is_writable(name)
     return false
 end
 
+local readable = P("r") * Cc(true)
+
 function file.is_readable(name)
     local a = attributes(name)
-    return a and sub(a.permissions,1,1) == "r"
+    return a and lpegmatch(readable,a.permissions) or false
 end
 
 file.isreadable = file.is_readable -- depricated
@@ -3256,41 +3320,74 @@ function file.size(name)
     return a and a.size or 0
 end
 
--- todo: lpeg \\ / .. does not save much
-
-local checkedsplit = string.checkedsplit
-
-function file.splitpath(str,separator) -- string
-    str = gsub(str,"\\","/")
-    return checkedsplit(str,separator or io.pathseparator)
+function file.splitpath(str,separator) -- string .. reslash is a bonus (we could do a direct split)
+    return checkedsplit(lpegmatch(reslasher,str),separator or io.pathseparator)
 end
 
 function file.joinpath(tab,separator) -- table
     return concat(tab,separator or io.pathseparator) -- can have trailing //
 end
 
--- we can hash them weakly
+local stripper  = Cs(P(fwslash)^0/"" * reslasher)
+local isnetwork = fwslash * fwslash * (1-fwslash) + (1-fwslash-colon)^1 * colon
+local isroot    = fwslash^1 * -1
+local hasroot   = fwslash^1
+
+function file.join(...) -- rather dirty
+    local lst = { ... }
+    local one = lst[1]
+    if lpegmatch(isnetwork,one) then
+        local two = lpegmatch(reslasher,concat(lst,"/",2))
+        return one .. "/" .. two
+    elseif lpegmatch(isroot,one) then
+        local two = lpegmatch(reslasher,concat(lst,"/",2))
+        if lpegmatch(hasroot,two) then
+            return two
+        else
+            return "/" .. two
+        end
+    elseif one == "" then
+        return lpegmatch(stripper,concat(lst,"/",2))
+    else
+        return lpegmatch(reslasher,concat(lst,"/"))
+    end
+end
+
+-- print(file.join("c:/whatever","name"))
+-- print(file.join("//","/y"))
+-- print(file.join("/","/y"))
+-- print(file.join("","/y"))
+-- print(file.join("/x/","/y"))
+-- print(file.join("x/","/y"))
+-- print(file.join("http://","/y"))
+-- print(file.join("http://a","/y"))
+-- print(file.join("http:///a","/y"))
+-- print(file.join("//nas-1","/y"))
+
+-- The previous one fails on "a.b/c"  so Taco came up with a split based
+-- variant. After some skyping we got it sort of compatible with the old
+-- one. After that the anchoring to currentdir was added in a better way.
+-- Of course there are some optimizations too. Finally we had to deal with
+-- windows drive prefixes and things like sys://. Eventually gsubs and
+-- finds were replaced by lpegs.
 
+local drivespec    = R("az","AZ")^1 * colon
+local anchors      = fwslash + drivespec
+local untouched    = periods + (1-period)^1 * P(-1)
+local splitstarter = (Cs(drivespec * (bwslash/"/" + fwslash)^0) + Cc(false)) * Ct(lpeg.splitat(S("/\\")^1))
+local absolute     = fwslash
 
 function file.collapsepath(str,anchor)
-    if anchor and not find(str,"^/") and not find(str,"^%a:") then
+    if anchor and not lpegmatch(anchors,str) then
         str = getcurrentdir() .. "/" .. str
     end
     if str == "" or str =="." then
         return "."
-    elseif find(str,"^%.%.") then
-        str = gsub(str,"\\","/")
-        return str
-    elseif not find(str,"%.") then
-        str = gsub(str,"\\","/")
-        return str
-    end
-    str = gsub(str,"\\","/")
-    local starter, rest = match(str,"^(%a+:/*)(.-)$")
-    if starter then
-        str = rest
+    elseif lpegmatch(untouched,str) then
+        return lpegmatch(reslasher,str)
     end
-    local oldelements = checkedsplit(str,"/")
+    local starter, oldelements = lpegmatch(splitstarter,str)
+-- inspect(oldelements)
     local newelements = { }
     local i = #oldelements
     while i > 0 do
@@ -3320,7 +3417,7 @@ function file.collapsepath(str,anchor)
         return starter or "."
     elseif starter then
         return starter .. concat(newelements, '/')
-    elseif find(str,"^/") then
+    elseif lpegmatch(absolute,str) then
         return "/" .. concat(newelements,'/')
     else
         return concat(newelements, '/')
@@ -3337,29 +3434,21 @@ end
 -- test("a/./b/..") test("a/aa/../b/bb") test("a/.././././b/..") test("a/./././b/..")
 -- test("a/b/c/../..") test("./a/b/c/../..") test("a/b/c/../..")
 
+local validchars = R("az","09","AZ","--","..")
+local pattern_a  = lpeg.replacer(1-validchars)
+local pattern_a  = Cs((validchars + P(1)/"-")^1)
+local whatever   = P("-")^0 / ""
+local pattern_b  = Cs(whatever * (1 - whatever * -1)^1)
+
 function file.robustname(str,strict)
-    str = gsub(str,"[^%a%d%/%-%.\\]+","-")
+    str = lpegmatch(pattern_a,str) or str
     if strict then
-        return lower(gsub(str,"^%-*(.-)%-*$","%1"))
+        return lpegmatch(pattern_b,str) or str -- two step is cleaner (less backtracking)
     else
         return str
     end
 end
 
--- local pattern_a = lpeg.replacer(1-R("az","09","AZ","--",".."))
--- local pattern_a = Cs((R("az","09","AZ","--","..") + P(1)/"-")^1)
--- local whatever  = P("-")^0 / ""
--- local pattern_b = Cs(whatever * (1 - whatever * -1)^1)
-
--- function file.robustname(str,strict)
---     str = lpegmatch(pattern_a,str) or str
---     if strict then
---         return lpegmatch(pattern_b,str) or str -- two step is cleaner (less backtracking)
---     else
---         return str
---     end
--- end
-
 file.readdata = io.loaddata
 file.savedata = io.savedata
 
@@ -3367,92 +3456,17 @@ function file.copy(oldname,newname)
     file.savedata(newname,io.loaddata(oldname))
 end
 
--- lpeg variants, slightly faster, not always
-
--- local period    = P(".")
--- local slashes   = S("\\/")
--- local noperiod  = 1-period
--- local noslashes = 1-slashes
--- local name      = noperiod^1
-
--- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1
-
--- function file.suffixonly(name)
---     return lpegmatch(pattern,name) or ""
--- end
-
--- local pattern = Cs(((period * noperiod^1 * -1)/"" + 1)^1)
-
--- function file.removesuffix(name)
---     return lpegmatch(pattern,name)
--- end
-
--- local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1
-
--- function file.basename(name)
---     return lpegmatch(pattern,name) or name
--- end
-
--- local pattern = Cs ((1 - slashes * noslashes^1 * -1)^1)
-
--- function file.dirname(name)
---     return lpegmatch(pattern,name) or ""
--- end
-
--- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
-
--- function file.addsuffix(name, suffix)
---     local p = lpegmatch(pattern,name)
---     if p then
---         return name
---     else
---         return name .. "." .. suffix
---     end
--- end
-
--- local suffix = period * (1-period-slashes)^1 * -1
--- local pattern = Cs((1-suffix)^1)
-
--- function file.replacesuffix(name,suffix)
---     if suffix and suffix ~= "" then
---         return lpegmatch(pattern,name) .. "." .. suffix
---     else
---         return name
---     end
--- end
-
--- local path    = noslashes^0 * slashes^1
--- local suffix  = period * (1-period-slashes)^1 * -1
--- local pattern = path^0 * Cs((1-suffix)^1) * suffix^0
-
--- function file.nameonly(name)
---     return lpegmatch(pattern,name) or name
--- end
-
--- local test = file.suffixonly
--- local test = file.basename
--- local test = file.dirname
--- local test = file.addsuffix
--- local test = file.replacesuffix
--- local test = file.nameonly
-
--- print(1,test("./a/b/c/abd.def.xxx","!!!"))
--- print(2,test("./../b/c/abd.def.xxx","!!!"))
--- print(3,test("a/b/c/abd.def.xxx","!!!"))
--- print(4,test("a/b/c/def.xxx","!!!"))
--- print(5,test("a/b/c/def","!!!"))
--- print(6,test("def","!!!"))
--- print(7,test("def.xxx","!!!"))
-
--- local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
-
 -- also rewrite previous
 
 local letter    = R("az","AZ") + S("_-+")
 local separator = P("://")
 
-local qualified = P(".")^0 * P("/") + letter*P(":") + letter^1*separator + letter^1 * P("/")
-local rootbased = P("/") + letter*P(":")
+local qualified = period^0 * fwslash
+                + letter   * colon
+                + letter^1 * separator
+                + letter^1 * fwslash
+local rootbased = fwslash
+                + letter * colon
 
 lpeg.patterns.qualified = qualified
 lpeg.patterns.rootbased = rootbased
@@ -3467,61 +3481,6 @@ function file.is_rootbased_path(filename)
     return lpegmatch(rootbased,filename) ~= nil
 end
 
--- actually these are schemes
-
-local slash  = S("\\/")
-local period = P(".")
-local drive  = C(R("az","AZ")) * P(":")
-local path   = C(((1-slash)^0 * slash)^0)
-local suffix = period * C(P(1-period)^0 * P(-1))
-local base   = C((1-suffix)^0)
-local rest   = C(P(1)^0)
-
-drive  = drive  + Cc("")
-path   = path   + Cc("")
-base   = base   + Cc("")
-suffix = suffix + Cc("")
-
-local pattern_a =   drive * path  *   base * suffix
-local pattern_b =           path  *   base * suffix
-local pattern_c = C(drive * path) * C(base * suffix) -- trick: two extra captures
-local pattern_d =           path  *   rest
-
-function file.splitname(str,splitdrive)
-    if splitdrive then
-        return lpegmatch(pattern_a,str) -- returns drive, path, base, suffix
-    else
-        return lpegmatch(pattern_b,str) -- returns path, base, suffix
-    end
-end
-
-function file.splitbase(str)
-    return lpegmatch(pattern_d,str) -- returns path, base+suffix
-end
-
-function file.nametotable(str,splitdrive) -- returns table
-    local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str)
-    if splitdrive then
-        return {
-            path    = path,
-            drive   = drive,
-            subpath = subpath,
-            name    = name,
-            base    = base,
-            suffix  = suffix,
-        }
-    else
-        return {
-            path    = path,
-            name    = name,
-            base    = base,
-            suffix  = suffix,
-        }
-    end
-end
-
--- print(file.splitbase("a/b/c.txt"))
-
 -- function test(t) for k, v in next, t do print(v, "=>", file.splitname(v)) end end
 --
 -- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
@@ -3529,6 +3488,14 @@ end
 -- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
 -- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
 
+-- -- maybe:
+--
+-- if os.type == "windows" then
+--     local currentdir = getcurrentdir
+--     function getcurrentdir()
+--         return lpegmatch(reslasher,currentdir())
+--     end
+-- end
 
 -- for myself:
 
@@ -3537,6 +3504,21 @@ function file.strip(name,dir)
     return a ~= "" and a or name
 end
 
+-- local debuglist = {
+--     "pathpart", "basename", "nameonly", "suffixonly", "suffix", "dirname", "extname",
+--     "addsuffix", "removesuffix", "replacesuffix", "join",
+--     "strip","collapsepath", "joinpath", "splitpath",
+-- }
+
+-- for i=1,#debuglist do
+--     local name = debuglist[i]
+--     local f = file[name]
+--     file[name] = function(...)
+--         print(name,f(...))
+--         return f(...)
+--     end
+-- end
+
 
 end -- of closure
 
@@ -3661,7 +3643,7 @@ if not modules then modules = { } end modules ['l-url'] = {
     license   = "see context related readme files"
 }
 
-local char, gmatch, gsub, format, byte, find = string.char, string.gmatch, string.gsub, string.format, string.byte, string.find
+local char, format, byte = string.char, string.format, string.byte
 local concat = table.concat
 local tonumber, type = tonumber, type
 local P, C, R, S, Cs, Cc, Ct, Cf, Cg, V = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cs, lpeg.Cc, lpeg.Ct, lpeg.Cf, lpeg.Cg, lpeg.V
@@ -3700,6 +3682,8 @@ local nothing     = Cc("")
 local escapedchar = (percent * C(hexdigit * hexdigit)) / tochar
 local escaped     = (plus / " ") + escapedchar
 
+local noslash     = P("/") / ""
+
 -- we assume schemes with more than 1 character (in order to avoid problems with windows disks)
 -- we also assume that when we have a scheme, we also have an authority
 --
@@ -3878,29 +3862,23 @@ function url.construct(hash) -- dodo: we need to escape !
     return lpegmatch(escaper,concat(fullurl))
 end
 
-function url.filename(filename) -- why no lpeg here ?
-    local t = hashed(filename)
-    return (t.scheme == "file" and (gsub(t.path,"^/([a-zA-Z])([:|])/)","%1:"))) or filename
+local pattern = Cs(noslash * R("az","AZ") * (S(":|")/":") * noslash * P(1)^0)
+
+function url.filename(filename)
+    local spec = hashed(filename)
+    local path = spec.path
+    return (spec.scheme == "file" and path and lpegmatch(pattern,path)) or filename
 end
 
+-- print(url.filename("/c|/test"))
+-- print(url.filename("/c/test"))
+
 local function escapestring(str)
     return lpegmatch(escaper,str)
 end
 
 url.escape = escapestring
 
--- function url.query(str) -- separator could be an option
---     if type(str) == "string" then
---         local t = { }
---         for k, v in gmatch(str,"([^&=]*)=([^&=]*)") do
---             t[k] = v
---         end
---         return t
---     else
---         return str
---     end
--- end
-
 function url.query(str)
     if type(str) == "string" then
         return lpegmatch(splitquery,str) or ""
@@ -3928,14 +3906,19 @@ end
 
 -- /test/ | /test | test/ | test => test
 
+local pattern = Cs(noslash^0 * (1 - noslash * P(-1))^0)
+
 function url.barepath(path)
     if not path or path == "" then
         return ""
     else
-        return (gsub(path,"^/?(.-)/?$","%1"))
+        return lpegmatch(pattern,path)
     end
 end
 
+-- print(url.barepath("/test"),url.barepath("test/"),url.barepath("/test/"),url.barepath("test"))
+-- print(url.barepath("/x/yz"),url.barepath("x/yz/"),url.barepath("/x/yz/"),url.barepath("x/yz"))
+
 
 
 
@@ -6191,8 +6174,8 @@ end
 -- for chem (currently one level)
 
 local value     = P(lbrace * C((nobrace + nestedbraces)^0) * rbrace)
-                + C(digit^1 * lparent * (noparent + nestedparents)^0 * rparent)
-                + C((nestedbraces + (1-comma))^0)
+                + C(digit^1 * lparent * (noparent + nestedparents)^1 * rparent)
+                + C((nestedbraces + (1-comma))^1)
 local pattern_a = spaces * Ct(value*(separator*value)^0)
 
 local function repeater(n,str)
@@ -6216,15 +6199,15 @@ local function repeater(n,str)
 end
 
 local value     = P(lbrace * C((nobrace + nestedbraces)^0) * rbrace)
-                + (C(digit^1)/tonumber * lparent * Cs((noparent + nestedparents)^0) * rparent) / repeater
-                + C((nestedbraces + (1-comma))^0)
+                + (C(digit^1)/tonumber * lparent * Cs((noparent + nestedparents)^1) * rparent) / repeater
+                + C((nestedbraces + (1-comma))^1)
 local pattern_b = spaces * Ct(value*(separator*value)^0)
 
-function parsers.settings_to_array_with_repeat(str,expand)
+function parsers.settings_to_array_with_repeat(str,expand) -- beware: "" =>  { }
     if expand then
-        return lpegmatch(pattern_b,str)
+        return lpegmatch(pattern_b,str) or { }
     else
-        return lpegmatch(pattern_a,str)
+        return lpegmatch(pattern_a,str) or { }
     end
 end
 
diff --git a/scripts/context/stubs/unix/mtxrun b/scripts/context/stubs/unix/mtxrun
index be3acb7da..01c601eb5 100644
--- a/scripts/context/stubs/unix/mtxrun
+++ b/scripts/context/stubs/unix/mtxrun
@@ -1625,7 +1625,8 @@ function lpeg.replacer(one,two,makefunction)
         elseif no == 1 then
             local o = one[1]
             one, two = P(o[1]), o[2]
-            pattern = Cs(((1-one)^1 + one/two)^0)
+         -- pattern = Cs(((1-one)^1 + one/two)^0)
+            pattern = Cs((one/two + 1)^0)
         else
             for i=1,no do
                 local o = one[i]
@@ -1636,7 +1637,28 @@ function lpeg.replacer(one,two,makefunction)
     else
         one = P(one)
         two = two or ""
-        pattern = Cs(((1-one)^1 + one/two)^0)
+     -- pattern = Cs(((1-one)^1 + one/two)^0)
+        pattern = Cs((one/two +1)^0)
+    end
+    if makefunction then
+        return function(str)
+            return lpegmatch(pattern,str)
+        end
+    else
+        return pattern
+    end
+end
+
+function lpeg.finder(lst,makefunction)
+    local pattern
+    if type(lst) == "table" then
+        local p = P(false)
+        for i=1,#lst do
+            p = p + P(lst[i])
+        end
+        pattern = (p + 1)^0
+    else
+        pattern = (P(lst) + 1)^0
     end
     if makefunction then
         return function(str)
@@ -3058,66 +3080,145 @@ file       = file or { }
 local file = file
 
 local insert, concat = table.insert, table.concat
-local find, gmatch, match, gsub, sub, char, lower = string.find, string.gmatch, string.match, string.gsub, string.sub, string.char, string.lower
+local match = string.match
 local lpegmatch = lpeg.match
 local getcurrentdir, attributes = lfs.currentdir, lfs.attributes
+local checkedsplit = string.checkedsplit
+
+-- local patterns = file.patterns or { }
+-- file.patterns  = patterns
 
-local P, R, S, C, Cs, Cp, Cc = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc
+local P, R, S, C, Cs, Cp, Cc, Ct = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc, lpeg.Ct
 
-local function dirname(name,default)
-    return match(name,"^(.+)[/\\].-$") or (default or "")
+local colon     = P(":")
+local period    = P(".")
+local periods   = P("..")
+local fwslash   = P("/")
+local bwslash   = P("\\")
+local slashes   = S("\\/")
+local noperiod  = 1-period
+local noslashes = 1-slashes
+local name      = noperiod^1
+local suffix    = period/"" * (1-period-slashes)^1 * -1
+
+local pattern = C((noslashes^0 * slashes^1)^1)
+
+local function pathpart(name,default)
+    return lpegmatch(pattern,name) or default or ""
 end
 
+local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1
+
 local function basename(name)
-    return match(name,"^.+[/\\](.-)$") or name
+    return lpegmatch(pattern,name) or name
 end
 
--- local function nameonly(name)
---     return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
--- end
+local pattern = (noslashes^0 * slashes^1)^0 * Cs((1-suffix)^1) * suffix^0
 
 local function nameonly(name)
-    return (gsub(match(name,"^.+[/\\](.-)$") or name,"%.[%a%d]+$",""))
+    return lpegmatch(pattern,name) or name
 end
 
-local function suffixonly(name,default)
-    return match(name,"^.+%.([^/\\]-)$") or default or ""
-end
+local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1
 
-local function splitname(name)
-    local n, s = match(name,"^(.+)%.([^/\\]-)$")
-    return n or name, s or ""
+local function suffixonly(name)
+    return lpegmatch(pattern,name) or ""
 end
 
+file.pathpart   = pathpart
 file.basename   = basename
-
-file.pathpart   = dirname
-file.dirname    = dirname
-
 file.nameonly   = nameonly
-
 file.suffixonly = suffixonly
-file.extname    = suffixonly -- obsolete
 file.suffix     = suffixonly
 
-function file.removesuffix(filename)
-    return (gsub(filename,"%.[%a%d]+$",""))
+file.dirname    = pathpart   -- obsolete
+file.extname    = suffixonly -- obsolete
+
+-- actually these are schemes
+
+local drive  = C(R("az","AZ")) * colon
+local path   = C(((1-slashes)^0 * slashes)^0)
+local suffix = period * C(P(1-period)^0 * P(-1))
+local base   = C((1-suffix)^0)
+local rest   = C(P(1)^0)
+
+drive  = drive  + Cc("")
+path   = path   + Cc("")
+base   = base   + Cc("")
+suffix = suffix + Cc("")
+
+local pattern_a =   drive * path  *   base * suffix
+local pattern_b =           path  *   base * suffix
+local pattern_c = C(drive * path) * C(base * suffix) -- trick: two extra captures
+local pattern_d =           path  *   rest
+
+function file.splitname(str,splitdrive)
+    if splitdrive then
+        return lpegmatch(pattern_a,str) -- returns drive, path, base, suffix
+    else
+        return lpegmatch(pattern_b,str) -- returns path, base, suffix
+    end
+end
+
+function file.splitbase(str)
+    return lpegmatch(pattern_d,str) -- returns path, base+suffix
+end
+
+function file.nametotable(str,splitdrive) -- returns table
+    local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str)
+    if splitdrive then
+        return {
+            path    = path,
+            drive   = drive,
+            subpath = subpath,
+            name    = name,
+            base    = base,
+            suffix  = suffix,
+        }
+    else
+        return {
+            path    = path,
+            name    = name,
+            base    = base,
+            suffix  = suffix,
+        }
+    end
+end
+
+local pattern = Cs(((period * noperiod^1 * -1)/"" + 1)^1)
+
+function file.removesuffix(name)
+    return lpegmatch(pattern,name)
 end
 
+-- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
+--
+-- function file.addsuffix(name, suffix)
+--     local p = lpegmatch(pattern,name)
+--     if p then
+--         return name
+--     else
+--         return name .. "." .. suffix
+--     end
+-- end
+
+local suffix  = period/"" * (1-period-slashes)^1 * -1
+local pattern = Cs((noslashes^0 * slashes^1)^0 * ((1-suffix)^1)) * Cs(suffix)
+
 function file.addsuffix(filename, suffix, criterium)
     if not suffix or suffix == "" then
         return filename
     elseif criterium == true then
         return filename .. "." .. suffix
     elseif not criterium then
-        local n, s = splitname(filename)
+        local n, s = lpegmatch(pattern,filename)
         if not s or s == "" then
             return filename .. "." .. suffix
         else
             return filename
         end
     else
-        local n, s = splitname(filename)
+        local n, s = lpegmatch(pattern,filename)
         if s and s ~= "" then
             local t = type(criterium)
             if t == "table" then
@@ -3134,88 +3235,49 @@ function file.addsuffix(filename, suffix, criterium)
                 end
             end
         end
-        return n .. "." .. suffix
+        return (n or filename) .. "." .. suffix
     end
 end
 
+-- print("1 " .. file.addsuffix("name","new")                   .. " -> name.new")
+-- print("2 " .. file.addsuffix("name.old","new")               .. " -> name.old")
+-- print("3 " .. file.addsuffix("name.old","new",true)          .. " -> name.old.new")
+-- print("4 " .. file.addsuffix("name.old","new","new")         .. " -> name.new")
+-- print("5 " .. file.addsuffix("name.old","new","old")         .. " -> name.old")
+-- print("6 " .. file.addsuffix("name.old","new","foo")         .. " -> name.new")
+-- print("7 " .. file.addsuffix("name.old","new",{"foo","bar"}) .. " -> name.new")
+-- print("8 " .. file.addsuffix("name.old","new",{"old","bar"}) .. " -> name.old")
 
-function file.replacesuffix(filename, suffix)
-    return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
+local suffix  = period * (1-period-slashes)^1 * -1
+local pattern = Cs((1-suffix)^0)
+
+function file.replacesuffix(name,suffix)
+    if suffix and suffix ~= "" then
+        return lpegmatch(pattern,name) .. "." .. suffix
+    else
+        return name
+    end
 end
 
-local trick_1 = char(1)
-local trick_2 = "^" .. trick_1 .. "/+"
+--
 
-function file.join(...) -- rather dirty
-    local lst = { ... }
-    local a, b = lst[1], lst[2]
-    if not a or a == "" then -- not a added
-        lst[1] = trick_1
-    elseif b and find(a,"^/+$") and find(b,"^/") then
-        lst[1] = ""
-        lst[2] = gsub(b,"^/+","")
-    end
-    local pth = concat(lst,"/")
-    pth = gsub(pth,"\\","/")
-    local a, b = match(pth,"^(.*://)(.*)$")
-    if a and b then
-        return a .. gsub(b,"//+","/")
-    end
-    a, b = match(pth,"^(//)(.*)$")
-    if a and b then
-        return a .. gsub(b,"//+","/")
-    end
-    pth = gsub(pth,trick_2,"")
-    return (gsub(pth,"//+","/"))
-end
-
--- local slash = P("/")
--- local colon = P(":")
-
--- local replacer  = lpeg.replacer(S("\\/")^1,"/")
--- local stripper  = Cs(P(slash)^0/"" * replacer)
--- local isnetwork = slash * slash * (1-slash) + (1-slash-colon)^1 * colon
--- local isroot    = slash^1 * -1
--- local hasroot   = slash^1
-
--- function file.newjoin(...) -- rather dirty
---     local lst = { ... }
---     local one = lst[1]
---     if lpegmatch(isnetwork,one) then
---         local two = lpegmatch(replacer,concat(lst,"/",2))
---         return one .. two
---     elseif lpegmatch(isroot,one) then
---         local two = lpegmatch(replacer,concat(lst,"/",2))
---         if lpegmatch(hasroot,two) then
---             return two
---         else
---             return "/" .. two
---         end
---     elseif one == "" then
---         return lpegmatch(stripper,concat(lst,"/",2))
---     else
---         return lpegmatch(replacer,concat(lst,"/"))
---     end
--- end
+local reslasher = lpeg.replacer(S("\\"),"/")
 
--- print(file.join("//","/y"))
--- print(file.join("/","/y"))
--- print(file.join("","/y"))
--- print(file.join("/x/","/y"))
--- print(file.join("x/","/y"))
--- print(file.join("http://","/y"))
--- print(file.join("http://a","/y"))
--- print(file.join("http:///a","/y"))
--- print(file.join("//nas-1","/y"))
+function file.reslash(str)
+    return lpegmatch(reslasher,str)
+end
 
 -- We should be able to use:
 --
+-- local writable = P(1) * P("w") * Cc(true)
+--
 -- function file.is_writable(name)
---     local a = attributes(name) or attributes(dirname(name,"."))
---     return a and sub(a.permissions,2,2) == "w"
+--     local a = attributes(name) or attributes(pathpart(name,"."))
+--     return a and lpegmatch(writable,a.permissions) or false
 -- end
 --
--- But after some testing Taco and I came up with:
+-- But after some testing Taco and I came up with the more robust
+-- variant:
 
 function file.is_writable(name)
     if lfs.isdir(name) then
@@ -3243,9 +3305,11 @@ function file.is_writable(name)
     return false
 end
 
+local readable = P("r") * Cc(true)
+
 function file.is_readable(name)
     local a = attributes(name)
-    return a and sub(a.permissions,1,1) == "r"
+    return a and lpegmatch(readable,a.permissions) or false
 end
 
 file.isreadable = file.is_readable -- depricated
@@ -3256,41 +3320,74 @@ function file.size(name)
     return a and a.size or 0
 end
 
--- todo: lpeg \\ / .. does not save much
-
-local checkedsplit = string.checkedsplit
-
-function file.splitpath(str,separator) -- string
-    str = gsub(str,"\\","/")
-    return checkedsplit(str,separator or io.pathseparator)
+function file.splitpath(str,separator) -- string .. reslash is a bonus (we could do a direct split)
+    return checkedsplit(lpegmatch(reslasher,str),separator or io.pathseparator)
 end
 
 function file.joinpath(tab,separator) -- table
     return concat(tab,separator or io.pathseparator) -- can have trailing //
 end
 
--- we can hash them weakly
+local stripper  = Cs(P(fwslash)^0/"" * reslasher)
+local isnetwork = fwslash * fwslash * (1-fwslash) + (1-fwslash-colon)^1 * colon
+local isroot    = fwslash^1 * -1
+local hasroot   = fwslash^1
+
+function file.join(...) -- rather dirty
+    local lst = { ... }
+    local one = lst[1]
+    if lpegmatch(isnetwork,one) then
+        local two = lpegmatch(reslasher,concat(lst,"/",2))
+        return one .. "/" .. two
+    elseif lpegmatch(isroot,one) then
+        local two = lpegmatch(reslasher,concat(lst,"/",2))
+        if lpegmatch(hasroot,two) then
+            return two
+        else
+            return "/" .. two
+        end
+    elseif one == "" then
+        return lpegmatch(stripper,concat(lst,"/",2))
+    else
+        return lpegmatch(reslasher,concat(lst,"/"))
+    end
+end
+
+-- print(file.join("c:/whatever","name"))
+-- print(file.join("//","/y"))
+-- print(file.join("/","/y"))
+-- print(file.join("","/y"))
+-- print(file.join("/x/","/y"))
+-- print(file.join("x/","/y"))
+-- print(file.join("http://","/y"))
+-- print(file.join("http://a","/y"))
+-- print(file.join("http:///a","/y"))
+-- print(file.join("//nas-1","/y"))
+
+-- The previous one fails on "a.b/c"  so Taco came up with a split based
+-- variant. After some skyping we got it sort of compatible with the old
+-- one. After that the anchoring to currentdir was added in a better way.
+-- Of course there are some optimizations too. Finally we had to deal with
+-- windows drive prefixes and things like sys://. Eventually gsubs and
+-- finds were replaced by lpegs.
 
+local drivespec    = R("az","AZ")^1 * colon
+local anchors      = fwslash + drivespec
+local untouched    = periods + (1-period)^1 * P(-1)
+local splitstarter = (Cs(drivespec * (bwslash/"/" + fwslash)^0) + Cc(false)) * Ct(lpeg.splitat(S("/\\")^1))
+local absolute     = fwslash
 
 function file.collapsepath(str,anchor)
-    if anchor and not find(str,"^/") and not find(str,"^%a:") then
+    if anchor and not lpegmatch(anchors,str) then
         str = getcurrentdir() .. "/" .. str
     end
     if str == "" or str =="." then
         return "."
-    elseif find(str,"^%.%.") then
-        str = gsub(str,"\\","/")
-        return str
-    elseif not find(str,"%.") then
-        str = gsub(str,"\\","/")
-        return str
-    end
-    str = gsub(str,"\\","/")
-    local starter, rest = match(str,"^(%a+:/*)(.-)$")
-    if starter then
-        str = rest
+    elseif lpegmatch(untouched,str) then
+        return lpegmatch(reslasher,str)
     end
-    local oldelements = checkedsplit(str,"/")
+    local starter, oldelements = lpegmatch(splitstarter,str)
+-- inspect(oldelements)
     local newelements = { }
     local i = #oldelements
     while i > 0 do
@@ -3320,7 +3417,7 @@ function file.collapsepath(str,anchor)
         return starter or "."
     elseif starter then
         return starter .. concat(newelements, '/')
-    elseif find(str,"^/") then
+    elseif lpegmatch(absolute,str) then
         return "/" .. concat(newelements,'/')
     else
         return concat(newelements, '/')
@@ -3337,29 +3434,21 @@ end
 -- test("a/./b/..") test("a/aa/../b/bb") test("a/.././././b/..") test("a/./././b/..")
 -- test("a/b/c/../..") test("./a/b/c/../..") test("a/b/c/../..")
 
+local validchars = R("az","09","AZ","--","..")
+local pattern_a  = lpeg.replacer(1-validchars)
+local pattern_a  = Cs((validchars + P(1)/"-")^1)
+local whatever   = P("-")^0 / ""
+local pattern_b  = Cs(whatever * (1 - whatever * -1)^1)
+
 function file.robustname(str,strict)
-    str = gsub(str,"[^%a%d%/%-%.\\]+","-")
+    str = lpegmatch(pattern_a,str) or str
     if strict then
-        return lower(gsub(str,"^%-*(.-)%-*$","%1"))
+        return lpegmatch(pattern_b,str) or str -- two step is cleaner (less backtracking)
     else
         return str
     end
 end
 
--- local pattern_a = lpeg.replacer(1-R("az","09","AZ","--",".."))
--- local pattern_a = Cs((R("az","09","AZ","--","..") + P(1)/"-")^1)
--- local whatever  = P("-")^0 / ""
--- local pattern_b = Cs(whatever * (1 - whatever * -1)^1)
-
--- function file.robustname(str,strict)
---     str = lpegmatch(pattern_a,str) or str
---     if strict then
---         return lpegmatch(pattern_b,str) or str -- two step is cleaner (less backtracking)
---     else
---         return str
---     end
--- end
-
 file.readdata = io.loaddata
 file.savedata = io.savedata
 
@@ -3367,92 +3456,17 @@ function file.copy(oldname,newname)
     file.savedata(newname,io.loaddata(oldname))
 end
 
--- lpeg variants, slightly faster, not always
-
--- local period    = P(".")
--- local slashes   = S("\\/")
--- local noperiod  = 1-period
--- local noslashes = 1-slashes
--- local name      = noperiod^1
-
--- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1
-
--- function file.suffixonly(name)
---     return lpegmatch(pattern,name) or ""
--- end
-
--- local pattern = Cs(((period * noperiod^1 * -1)/"" + 1)^1)
-
--- function file.removesuffix(name)
---     return lpegmatch(pattern,name)
--- end
-
--- local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1
-
--- function file.basename(name)
---     return lpegmatch(pattern,name) or name
--- end
-
--- local pattern = Cs ((1 - slashes * noslashes^1 * -1)^1)
-
--- function file.dirname(name)
---     return lpegmatch(pattern,name) or ""
--- end
-
--- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
-
--- function file.addsuffix(name, suffix)
---     local p = lpegmatch(pattern,name)
---     if p then
---         return name
---     else
---         return name .. "." .. suffix
---     end
--- end
-
--- local suffix = period * (1-period-slashes)^1 * -1
--- local pattern = Cs((1-suffix)^1)
-
--- function file.replacesuffix(name,suffix)
---     if suffix and suffix ~= "" then
---         return lpegmatch(pattern,name) .. "." .. suffix
---     else
---         return name
---     end
--- end
-
--- local path    = noslashes^0 * slashes^1
--- local suffix  = period * (1-period-slashes)^1 * -1
--- local pattern = path^0 * Cs((1-suffix)^1) * suffix^0
-
--- function file.nameonly(name)
---     return lpegmatch(pattern,name) or name
--- end
-
--- local test = file.suffixonly
--- local test = file.basename
--- local test = file.dirname
--- local test = file.addsuffix
--- local test = file.replacesuffix
--- local test = file.nameonly
-
--- print(1,test("./a/b/c/abd.def.xxx","!!!"))
--- print(2,test("./../b/c/abd.def.xxx","!!!"))
--- print(3,test("a/b/c/abd.def.xxx","!!!"))
--- print(4,test("a/b/c/def.xxx","!!!"))
--- print(5,test("a/b/c/def","!!!"))
--- print(6,test("def","!!!"))
--- print(7,test("def.xxx","!!!"))
-
--- local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
-
 -- also rewrite previous
 
 local letter    = R("az","AZ") + S("_-+")
 local separator = P("://")
 
-local qualified = P(".")^0 * P("/") + letter*P(":") + letter^1*separator + letter^1 * P("/")
-local rootbased = P("/") + letter*P(":")
+local qualified = period^0 * fwslash
+                + letter   * colon
+                + letter^1 * separator
+                + letter^1 * fwslash
+local rootbased = fwslash
+                + letter * colon
 
 lpeg.patterns.qualified = qualified
 lpeg.patterns.rootbased = rootbased
@@ -3467,61 +3481,6 @@ function file.is_rootbased_path(filename)
     return lpegmatch(rootbased,filename) ~= nil
 end
 
--- actually these are schemes
-
-local slash  = S("\\/")
-local period = P(".")
-local drive  = C(R("az","AZ")) * P(":")
-local path   = C(((1-slash)^0 * slash)^0)
-local suffix = period * C(P(1-period)^0 * P(-1))
-local base   = C((1-suffix)^0)
-local rest   = C(P(1)^0)
-
-drive  = drive  + Cc("")
-path   = path   + Cc("")
-base   = base   + Cc("")
-suffix = suffix + Cc("")
-
-local pattern_a =   drive * path  *   base * suffix
-local pattern_b =           path  *   base * suffix
-local pattern_c = C(drive * path) * C(base * suffix) -- trick: two extra captures
-local pattern_d =           path  *   rest
-
-function file.splitname(str,splitdrive)
-    if splitdrive then
-        return lpegmatch(pattern_a,str) -- returns drive, path, base, suffix
-    else
-        return lpegmatch(pattern_b,str) -- returns path, base, suffix
-    end
-end
-
-function file.splitbase(str)
-    return lpegmatch(pattern_d,str) -- returns path, base+suffix
-end
-
-function file.nametotable(str,splitdrive) -- returns table
-    local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str)
-    if splitdrive then
-        return {
-            path    = path,
-            drive   = drive,
-            subpath = subpath,
-            name    = name,
-            base    = base,
-            suffix  = suffix,
-        }
-    else
-        return {
-            path    = path,
-            name    = name,
-            base    = base,
-            suffix  = suffix,
-        }
-    end
-end
-
--- print(file.splitbase("a/b/c.txt"))
-
 -- function test(t) for k, v in next, t do print(v, "=>", file.splitname(v)) end end
 --
 -- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
@@ -3529,6 +3488,14 @@ end
 -- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
 -- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
 
+-- -- maybe:
+--
+-- if os.type == "windows" then
+--     local currentdir = getcurrentdir
+--     function getcurrentdir()
+--         return lpegmatch(reslasher,currentdir())
+--     end
+-- end
 
 -- for myself:
 
@@ -3537,6 +3504,21 @@ function file.strip(name,dir)
     return a ~= "" and a or name
 end
 
+-- local debuglist = {
+--     "pathpart", "basename", "nameonly", "suffixonly", "suffix", "dirname", "extname",
+--     "addsuffix", "removesuffix", "replacesuffix", "join",
+--     "strip","collapsepath", "joinpath", "splitpath",
+-- }
+
+-- for i=1,#debuglist do
+--     local name = debuglist[i]
+--     local f = file[name]
+--     file[name] = function(...)
+--         print(name,f(...))
+--         return f(...)
+--     end
+-- end
+
 
 end -- of closure
 
@@ -3661,7 +3643,7 @@ if not modules then modules = { } end modules ['l-url'] = {
     license   = "see context related readme files"
 }
 
-local char, gmatch, gsub, format, byte, find = string.char, string.gmatch, string.gsub, string.format, string.byte, string.find
+local char, format, byte = string.char, string.format, string.byte
 local concat = table.concat
 local tonumber, type = tonumber, type
 local P, C, R, S, Cs, Cc, Ct, Cf, Cg, V = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cs, lpeg.Cc, lpeg.Ct, lpeg.Cf, lpeg.Cg, lpeg.V
@@ -3700,6 +3682,8 @@ local nothing     = Cc("")
 local escapedchar = (percent * C(hexdigit * hexdigit)) / tochar
 local escaped     = (plus / " ") + escapedchar
 
+local noslash     = P("/") / ""
+
 -- we assume schemes with more than 1 character (in order to avoid problems with windows disks)
 -- we also assume that when we have a scheme, we also have an authority
 --
@@ -3878,29 +3862,23 @@ function url.construct(hash) -- dodo: we need to escape !
     return lpegmatch(escaper,concat(fullurl))
 end
 
-function url.filename(filename) -- why no lpeg here ?
-    local t = hashed(filename)
-    return (t.scheme == "file" and (gsub(t.path,"^/([a-zA-Z])([:|])/)","%1:"))) or filename
+local pattern = Cs(noslash * R("az","AZ") * (S(":|")/":") * noslash * P(1)^0)
+
+function url.filename(filename)
+    local spec = hashed(filename)
+    local path = spec.path
+    return (spec.scheme == "file" and path and lpegmatch(pattern,path)) or filename
 end
 
+-- print(url.filename("/c|/test"))
+-- print(url.filename("/c/test"))
+
 local function escapestring(str)
     return lpegmatch(escaper,str)
 end
 
 url.escape = escapestring
 
--- function url.query(str) -- separator could be an option
---     if type(str) == "string" then
---         local t = { }
---         for k, v in gmatch(str,"([^&=]*)=([^&=]*)") do
---             t[k] = v
---         end
---         return t
---     else
---         return str
---     end
--- end
-
 function url.query(str)
     if type(str) == "string" then
         return lpegmatch(splitquery,str) or ""
@@ -3928,14 +3906,19 @@ end
 
 -- /test/ | /test | test/ | test => test
 
+local pattern = Cs(noslash^0 * (1 - noslash * P(-1))^0)
+
 function url.barepath(path)
     if not path or path == "" then
         return ""
     else
-        return (gsub(path,"^/?(.-)/?$","%1"))
+        return lpegmatch(pattern,path)
     end
 end
 
+-- print(url.barepath("/test"),url.barepath("test/"),url.barepath("/test/"),url.barepath("test"))
+-- print(url.barepath("/x/yz"),url.barepath("x/yz/"),url.barepath("/x/yz/"),url.barepath("x/yz"))
+
 
 
 
@@ -6191,8 +6174,8 @@ end
 -- for chem (currently one level)
 
 local value     = P(lbrace * C((nobrace + nestedbraces)^0) * rbrace)
-                + C(digit^1 * lparent * (noparent + nestedparents)^0 * rparent)
-                + C((nestedbraces + (1-comma))^0)
+                + C(digit^1 * lparent * (noparent + nestedparents)^1 * rparent)
+                + C((nestedbraces + (1-comma))^1)
 local pattern_a = spaces * Ct(value*(separator*value)^0)
 
 local function repeater(n,str)
@@ -6216,15 +6199,15 @@ local function repeater(n,str)
 end
 
 local value     = P(lbrace * C((nobrace + nestedbraces)^0) * rbrace)
-                + (C(digit^1)/tonumber * lparent * Cs((noparent + nestedparents)^0) * rparent) / repeater
-                + C((nestedbraces + (1-comma))^0)
+                + (C(digit^1)/tonumber * lparent * Cs((noparent + nestedparents)^1) * rparent) / repeater
+                + C((nestedbraces + (1-comma))^1)
 local pattern_b = spaces * Ct(value*(separator*value)^0)
 
-function parsers.settings_to_array_with_repeat(str,expand)
+function parsers.settings_to_array_with_repeat(str,expand) -- beware: "" =>  { }
     if expand then
-        return lpegmatch(pattern_b,str)
+        return lpegmatch(pattern_b,str) or { }
     else
-        return lpegmatch(pattern_a,str)
+        return lpegmatch(pattern_a,str) or { }
     end
 end
 
-- 
cgit v1.2.3