[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  Lua
. . -> [Subject]  SplitSpeedwalk (a function done in LPEG)

SplitSpeedwalk (a function done in LPEG)

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page


Posted by WillFa   USA  (525 posts)  [Biography] bio
Date Sat 27 Jun 2009 08:04 AM (UTC)

Amended on Fri 09 Oct 2009 05:53 PM (UTC) by WillFa

Message
So you have a super-long, nifty speedwalk that you included comments in. You want to be able to get at your comments in your Lua script.

GetMapperItem can retrieve your comments for you, but there's no SetMappingString. So you can't load your saved speedwalk into the Mapper.

EvaluateSpeedwalk strips the comments out.

What can ya do?


function SplitSpeedwalk(speedwalk)
    lpeg.locale(lpeg)
    local P, C, Cs, V, Ct, Cg, S, Cc = lpeg.P, lpeg.C, lpeg.Cs, lpeg.V, lpeg.Ct, lpeg.Cg, lpeg.S, lpeg.Cc
    local digit, space = lpeg.digit, lpeg.space
    
    local function ExpandCounter (repeated, cmd) 
        local tmp = {} 
        repeated = math.max(1,tonumber(repeated))
        for i = 1,repeated do 
            tmp[#tmp+1] = cmd 
        end 
        return unpack(tmp) 
    end 
    
    local function er (c, i, lvl)
        error(string.format("Bad Speedwalk character near '%s' at position %d", c:sub(i, i), i) , 4 )
    end
    
    local SWdirs = {n = "north", s = "south", e = "east", w = "west", 
                    u = "up", d = "down", f= GetInfo(19),                 }
    local Macros = {L = "lock ", O = "open ", C = "close ", K = "unlock ",} 
    
    local tmp = {}
    for k,_ in pairs(SWdirs) do 
        SWdirs[k:upper()] = SWdirs[k] 
        tmp[#tmp+1] = k
        tmp[#tmp+1] = k:upper()
    end
    local DirChars = table.concat(tmp)
    tmp = nil
    
    local function SWmacros (x,y)
        return Macros[x] .. y 
    end
    
    local
    SpeedWalk = P{"grammar",
                grammar = Ct( ( V"comment"    +                 -- Capture to a table: {a comment} or
                                V"action"     +                 -- an action like LW or
                                V"repeatable" +                 -- a dir that might have a #: 2w 3(jump) e  or
                                space^1       )^0)              -- ignore white space, 0 or more captures. 
                             * (-1 + P(er)),                    -- match to end of string or error.
                comment = C(P"{" * (1-P"}")^1 * P"}"),          -- PCRE: (\{[^\}]+\})
                counter = C(digit^1) + Cc(1) ,                  -- match a number or default to 1 
                special = P"(" * C( (1- P")" )^1) * P")",       -- PCRE: \(([^\)]+)\)
                action  = Cg( C(S"LOCK") * V"dir")/SWmacros,    -- string.gsub(s, "([LOCK])([nsewudfNSEWUDF])", SWMacros) -- kinda
                dir     = Cs(S(DirChars) / SWdirs),             -- string.gsub(s, "([nsewudfNSEWUDF])", SWdirs)
                repeatable = Cg( V"counter" * space^0 *         -- similar to string.gsub(s, "(%d+)%s*([nse...])", ExpandCounter)
                               ( V"special" + V"dir" )) / ExpandCounter, -- but V"counter" is a lot more complex than (%d) :)
                }
    return SpeedWalk:match(speedwalk)
end


This returns a table of the evaluated speedwalk, with comments still included. It is meant to replicate the behavior of GetMappingItem, not Evaluate Speedwalk. Speedwalks with special directions (forward/backward) will be parsed as "forward/backward", not just "forward".

**This has been modified after Erendir's post. I don't see a reason to save bad code for posterity. :)
[Go to top] top

Posted by Erendir   Germany  (47 posts)  [Biography] bio
Date Reply #1 on Sat 27 Jun 2009 05:48 PM (UTC)

Amended on Sat 27 Jun 2009 05:49 PM (UTC) by Erendir

Message
Some fixes:

local P, C, Cs, V, Ct, Cg, S = lpeg.P, lpeg.C, lpeg.Cs, lpeg.V, lpeg.Ct, lpeg.Cg, lpeg.S

and with grammar like this:

grammar = Ct( ( ( (V"counter")^-1 * ( V"special" + V"swmacro" + V"dir"  )
                  + V"comment" + lpeg.digit^1*P(er) )/ EvalCounter
                  + lpeg.space^1)^1) * (-1 + P(er)),

it'll be not more possibly to multiply comments ;)
[Go to top] top

Posted by WillFa   USA  (525 posts)  [Biography] bio
Date Reply #2 on Sat 27 Jun 2009 06:45 PM (UTC)
Message
Good change!

Although, EvaluateSpeedwalk("4 s") is syntacticly valid - though it does look dumb.

I'll edit the original post with the fixes and changes.
[Go to top] top

Posted by Nick Gammon   Australia  (22,973 posts)  [Biography] bio   Forum Administrator
Date Reply #3 on Sat 27 Jun 2009 10:17 PM (UTC)

Amended on Sat 27 Jun 2009 10:20 PM (UTC) by Nick Gammon

Message
Looks like a fabulous idea! I love to see LPEG being used in new ways. I must admit when I added LPEG I didn't envisage all these novel ways it could help.

Let's test:


print (SplitSpeedwalk("5N 4W (ne/sw) (say open sesame/kick door) 5U 4(se/nw)"))  --> nil


Oops.

I got that example from the EvaluateSpeedwalk help page.

It looks like the upper-case directions are throwing it out. EvaluateSpeedwalk accepts the upper-case directions like N, S, E, W.

The help text spells it out:


Directions

The directions recognised are: N:north, S:south, E:east, W:west, U:up, D:down.


Interestingly, it accepts LW (lock west) but not W on its own.

You might also want to build in support for the filler (f) character.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by WillFa   USA  (525 posts)  [Biography] bio
Date Reply #4 on Sat 27 Jun 2009 10:39 PM (UTC)

Amended on Sat 27 Jun 2009 11:48 PM (UTC) by WillFa

Message
Nice catch Nick.


function SplitSpeedwalk(speedwalk)
    lpeg.locale(lpeg)
    local P, C, Cs, V, Ct, Cg, S = lpeg.P, lpeg.C, lpeg.Cs, lpeg.V, lpeg.Ct, lpeg.Cg, lpeg.S
    local repeated = 1
    local function SWcounter(x) repeated = math.max(1,math.min(x,99)) end
    local function EvalCounter (cmd) 
        local tmp = {} 
        for i = 1,repeated do 
            tmp[#tmp+1] = cmd 
        end 
        repeated = 1
        return unpack(tmp) 
    end 
    local function er (c, i) 
        error(string.format("Bad Speedwalk character near '%s' at position %d", c:sub(i, i), i) )
    end
    local SWdirs = {n = "north", s = "south", 
                    w = "west", e = "east", 
                    u = "up", d = "down", 
                    f= GetInfo(19),
                    }
    local tmp = {}                    
    for k,_ in pairs(SWdirs) do 
        SWdirs[k:upper()] = SWdirs[k] 
        tmp[#tmp+1] = k
        tmp[#tmp+1] = k:upper()
    end
    local DirChars = table.concat(tmp)
    tmp = nil
    
    local function SWmacros (x,y)
        local tmp= {L = "lock ", O = "open ", C = "close ", K = "unlock ",} 
        if tmp[x] and SWdirs[y:lower()] then
            return tmp[x] .. SWdirs[y:lower()] 
        else
            error("Invalid Speedwalk Macro.")
        end
    end
    
    local
    SpeedWalk = P{"grammar",
                grammar = Ct( ( V"comment" + 
                              (V"counter"^-1 * lpeg.space^0 * V"repeatable") +
                              (lpeg.space^1 )
                            )^1) * (-1 + P(er)),
                comment = C(P"{" * (1-P"}")^1 * P"}"),
                counter = C(lpeg.digit^1) /SWcounter,
                repeatable = ( V"special" + V"swmacro" + V"dir"  ) / EvalCounter,
                special = P"(" * C((1-P")")^1)* P")",
                swmacro = Cg( C(S"LOCK") * C(1))/SWmacros,
                dir = Cs(S(DirChars) / SWdirs),
                }

    return SpeedWalk:match(speedwalk) or er(speedwalk,1)
end


Now parses

/tprint(SplitSpeedwalk([[3e 2 S 
       {Foo} 2(jump/fall)3LW ]]))

Correctly with:

1="east"
2="east"
3="east"
4="south"
5="south"
6="{Foo}"
7="jump/fall"
8="jump/fall"
9="lock west"
10="lock west"
11="lock west"


Just like GetMappingItem, this is meant to return "fwd/back" directions; the "jump/fall" isn't a bug.

[Go to top] top

Posted by Erendir   Germany  (47 posts)  [Biography] bio
Date Reply #5 on Sun 28 Jun 2009 04:19 AM (UTC)
Message
a small "optimization":

                grammar = Ct( ( V"comment" + 
                              (V"counter"^-1 * lpeg.space^0 * V"repeatable") +
                              (lpeg.space^1 )
                            )^0) * (-1 + P(er)),


makes

return SpeedWalk:match(speedwalk) or er(speedwalk,1)

obsolete.
[Go to top] top

Posted by WillFa   USA  (525 posts)  [Biography] bio
Date Reply #6 on Sun 28 Jun 2009 06:41 AM (UTC)

Amended on Sun 28 Jun 2009 06:42 AM (UTC) by WillFa

Message
Good to know. I was expecting that I'd get a compiler error that the "Grammar could match on empty string".

Strangely enough, I can't seem to make a grammar that causes the error now. I've gotten them at times in the past.


(First post has current code again.)
[Go to top] top

Posted by Erendir   Germany  (47 posts)  [Biography] bio
Date Reply #7 on Sun 28 Jun 2009 12:26 PM (UTC)
Message
>error that the "Grammar could match on empty string".
it's possible with lpeg.P(-1):match("") - no error
What You mean is the "loop body may accept empty string"-error, like
err = (lpeg.P(1)^0)^1
err:match("")
[Go to top] top

Posted by WillFa   USA  (525 posts)  [Biography] bio
Date Reply #8 on Sun 28 Jun 2009 11:46 PM (UTC)

Amended on Fri 09 Oct 2009 05:54 PM (UTC) by WillFa

Message
I did not like the SWcounter function, and realized I could do it in lpeg. I got rid of 1 function and an upvalue by tweaking the grammar.

I also realized that EvalSW doesn't like things like "2LW".

I found a quirk in EvalSW; it silently changes "0w" to "w" or "1w". So that's emulated.

The error function has a level specified high enough to point to what called SplitSpeedwalk.

There's one new quirk in this function, "Lf" (Lock fillercommand) is valid syntax, though still a dumb thing for a user to do, like "0w", so I ain't fixin' it. :p

And now with commenty goodness of the lpeg for people that are curious what this gobbledygook means!


function SplitSpeedwalk(speedwalk)
    lpeg.locale(lpeg)
    local P, C, Cs, V, Ct, Cg, S, Cc = lpeg.P, lpeg.C, lpeg.Cs, lpeg.V, lpeg.Ct, lpeg.Cg, lpeg.S, lpeg.Cc
    local digit, space = lpeg.digit, lpeg.space
    
    local function ExpandCounter (repeated, cmd) 
        local tmp = {} 
        repeated = math.max(1,tonumber(repeated))
        for i = 1,repeated do 
            tmp[#tmp+1] = cmd 
        end 
        return unpack(tmp) 
    end 
    
    local function er (c, i, lvl)
        error(string.format("Bad Speedwalk character near '%s' at position %d", c:sub(i, i), i) , 4 )
    end
    
    local SWdirs = {n = "north", s = "south", e = "east", w = "west", 
                    u = "up", d = "down", f= GetInfo(19),                 }
    local Macros = {L = "lock ", O = "open ", C = "close ", K = "unlock ",} 
    
    local tmp = {}
    for k,_ in pairs(SWdirs) do 
        SWdirs[k:upper()] = SWdirs[k] 
        tmp[#tmp+1] = k
        tmp[#tmp+1] = k:upper()
    end
    local DirChars = table.concat(tmp)
    tmp = nil
    
    local function SWmacros (x,y)
        return Macros[x] .. y 
    end
    
    local
    SpeedWalk = P{"grammar",
                grammar = Ct( ( V"comment"    +                 -- Capture to a table: {a comment} or
                                V"action"     +                 -- an action like LW or
                                V"repeatable" +                 -- a dir that might have a #: 2w 3(jump) e  or
                                space^1       )^0)              -- ignore white space, 0 or more captures. 
                             * (-1 + P(er)),                    -- match to end of string or error.
                comment = C(P"{" * (1-P"}")^1 * P"}"),          -- PCRE: (\{[^\}]+\})
                counter = C(digit^1) + Cc(1) ,                  -- match a number or default to 1 
                special = P"(" * C( (1- P")" )^1) * P")",       -- PCRE: \(([^\)]+)\)
                action  = Cg( C(S"LOCK") * V"dir")/SWmacros,    -- string.gsub(s, "([LOCK])([nsewudfNSEWUDF])", SWMacros) -- kinda
                dir     = Cs(S(DirChars) / SWdirs),             -- string.gsub(s, "([nsewudfNSEWUDF])", SWdirs)
                repeatable = Cg( V"counter" * space^0 *         -- similar to string.gsub(s, "(%d+)%s*([nse...])", ExpandCounter)
                               ( V"special" + V"dir" )) / ExpandCounter, -- but V"counter" is a lot more complex than (%d) :)
                }
    return SpeedWalk:match(speedwalk)
end


p.s. this is why I take so long to code a plugin. I end up writing the same code 13 times. :\
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


24,011 views.

It is now over 60 days since the last post. This thread is closed.     [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]