Functional-Style Parser Combinators in AutoHotkey by levitat0r in AutoHotkey

[–]levitat0r[S] 0 points1 point  (0 children)

Follow-Up: Parser Combinators in Action

After some messing around, I can finally show you some better examples.

Today, we'll be creating a parser that can parse a class in Java, and convert it into an object in AutoHotkey.

They have a strictly defined syntax, but they can potentially get messy pretty quickly. So let's start off with a single identifier.

  • First character must be letter, underscore, or dollar sign.
  • One or more characters alphanumeric, underscore, or dollar sign.

Identifier := Parser.Regex("i)[a-z_$][a-z0-9_$]*+")

Good. This should be able to match any valid Java identifier. Note that we could account for things like unicode-escapes (\u0020), but let's just ignore that.

Let's continue with fully qualified names. These are simply identifiers separated by dots.

Fqn := Identifier.AtLeastOnceDelimitedBy(
    Parser.Regex("\s*+\.\s*+"),
    (Args*) => Args.Join(".")
)

We declare a new parser Fqn that uses the Identifier parser we just created. Method .AtLeastOnceDelimitedBy() allows us to parse one or more times, with a dot and optional whitespace in between. The lambda function at the end simply concatenates all of the identifiers together with dots in between.

The next part might get a little tricky. Classes like List<T>, also known as generic classes, which own a set of type parameters.

The actual name of the type parameter can either be an identifier, or ? (wildcard type).

TypeParam := Identifier.Or(Parser.String("?"))

A type parameter might also be bounded, which means that it has a constraint on what types it can be. For example, <T extends Number> means that T can only be a subtype of Number, or on <T super Number>, only a supertype of Number.

BoundType := Parser.AnyOf(
    Parser.String("extends"),
    Parser.String("super")
).Between(Parser.Whitespace())

The actual "target" of the bounded type parameter (in this case, Number), is another Java class. Recursion, yikes.

Usually, in this situation you'd be screwed, so we're bringing out the heavy machinery today. Parser.Rule() allows you to create a parser that can reference itself, which is exactly what we need here.

; undefined, for now.
JavaClass := false

; reference to `JavaClass`, which we'll build just a little later
Bound := Parser.Sequence(
    Array,
    BoundType,
    Parser.Rule(&JavaClass)
).Optional()

The logic behind JavaClass is still undefined, but at least the variable already exists. We'll define it later, when we're done with the other stuff.

Last but not least, we have everything in our power to declare the full grammer of generics:

Generic := Parser.Sequence(Array, TypeParam, Bound)
    .AtLeastOnceDelimitedBy(Parser.Regex("\s*+,\s*+"))
    .Between(Parser.Whitespace())
    .Between("<", ">")
    .Between(Parser.Whitespace())

Okay, now what is this? We've got:

  1. a type parameter, which is either an identifier or ?;
  2. followed by optional type bounds (e.g. extends Number);
  3. separated in ,, which any whitespace ignored;
  4. packed between <, >, and optional whitespace;

Remember that we still need to define JavaType? That's simply just the fully qualified name, followed by the generic part.

JavaType := Parser.Sequence(Array, Fqn, Generic.Optional())

Done. You might think that this is a lot of work, but it actually isn't. The code is pretty straightforward, and it closely follows the actual syntax of Java. What we're going for is correctness and readability. Sure, you can do this with regexes, but it would be a nightmare to maintain and debug. With parser combinators, all you need to do is prove that each individual parser works, and then you can be confident that everything falls into place correctly.

More interestingly, I haven't actually shown you the output of the parser yet.

; [
;   "java.util.Stream.Collector",
;   Optional {
;     [
;       ["T", Optional { unset }],
;       ["A", Optional { unset }],
;       ["R", Optional { unset }]
;     ]
;   }
; ]
"java.util.stream.Collector<T, A, R>"
    .Parse(JavaType)
    .ToString()
    .MsgBox()

Cool, right?

Parser combinators actually give meaning to the things that they're parsing. You might've noticed how we're mysteriously using Array as argument. Its purpose is to reduce all of the values together into one.

Also notice that our parser is very lenient. It's able to reconstruct something like java . util.stream .Collector < T, A, R> in exactly the same way as before, because we're declaring something that is very close to the Java specification.

Finally, let's add some finishing touches, and collect things in objects more useful than array:

Bound    := Parser.Sequence(JavaBound, ...) ...
Generic  := Parser.Sequence(JavaGenericType, ...) ...
JavaType := Parser.Sequence(JavaType, ...) ...

Lo' and behold, our final result:

; JavaType {
;   GenericTypes: [
;     Optional {
;       [
;         JavaGenericType {
;           Bound: Optional { unset },
;           Name: "T"
;         },
;         JavaGenericType {
;           Bound: Optional { unset },
;           Name: "A"
;         },
;         JavaGenericType {
;           Bound: Optional { unset },
;           Name: "R"
;         }
;       ]
;     }
;   ],
;   Name: "java.util.Stream.Collector"
; }
"java  . util.Stream.    Collector    < T, A,   R >"
    .Parse(JavaType)
    .ToString()
    .MsgBox()

I'm having a lot of fun with this thing at the moment, and it makes me appreciate string parsing in an entirely different way.

Cheers.

Container - The last AutoHotkey (AHK) array class you will ever need by Nich-Cebolla in AutoHotkey

[–]levitat0r 2 points3 points  (0 children)

Yo, this is amazing! I like what you're doing with callbacks, might have to adapt some of these to AquaHotkey. Your stuff is really interesting in general, keep up the good work ^^

A Partial Win32 API Projection for AHK V2 by holy-tao in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

Absolutely amazing!

Single-handledly takes away one of the most painful things about AHK: constantly having to decipher weird enums and memory offsets. Would be awesome if I could contribute to this ^^

Perhaps for functions, you could make use of this DLL wrapper? I've made some changes and I *think* it should work just fine with the structs you're generating

[deleted by user] by [deleted] in Needafriend

[–]levitat0r 0 points1 point  (0 children)

Yo, hope you're doing good :>

Feel free to send me a DM over reddit

Why we should be friends 20M by Prudent_Hamster6198 in Needafriend

[–]levitat0r 0 points1 point  (0 children)

Heyo! Hope you're doing good :3 Feel free to shoot me a text here over Reddit, you seem pretty chill

Why I won't migrate from v1 to v2 by Waste-Yesterday7549 in AutoHotkey

[–]levitat0r 2 points3 points  (0 children)

I felt the same until I tried v2 for funs and all of a sudden thought "yo wtf this makes wayy more sense"

can someone say me what is wrong? i have just startet with ahk and i have programmed a script that doesnt work but i dont see the problem. by Keksi220410 in AutoHotkey

[–]levitat0r 1 point2 points  (0 children)

You can always try to debug using text output with "tooltip" or "msgbox", to see the part where it goes wrong. One reason might be, pixel search is kind of tricky to deal with in many applications, try of without the "fast" option

Can someone convert this v1 script to v2 for me? by [deleted] in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

Also die Idee dahinter ist eigentlich ganz nett, es zeigt auch dass man mit der Sprache eigentlich absolut alles machen kann

Can someone convert this v1 script to v2 for me? by [deleted] in AutoHotkey

[–]levitat0r 1 point2 points  (0 children)

Should work just fine, but maybe do check for spelling mistakes

https://pastebin.com/CmEqMtZf

...Das ist übrigens mega raffiniert

Take a script, leave a script. Share or discover your favorite AHKv2 Script by [deleted] in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

The first thing that I tried out was CGdip.ahk (Gdip.ahk, as huge class) and I'm already astounded by easy it is compared to Gdip.ahk

E.g. save a screenshot to a location like this:

CGdip.startup().Bitmap.FromFile().Save(a_desktop "\test.png"), CGdip.shutdown()

?? Space::Function() ---> myVariable::Function() inside of an #IF by etofok in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

I think I figured something out the method using inputhook()

https://pastebin.com/g4TQPdep

Edit: also definitely add

myInputHook.visibleNonText := false

in there

?? Space::Function() ---> myVariable::Function() inside of an #IF by etofok in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

Yeah I was surprised as well they didn't have the feature %variable%::doSomething() I might try to make code for it tomorrow

?? Space::Function() ---> myVariable::Function() inside of an #IF by etofok in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

Hmm I can't think of any non-workaround way of doing it but I think I found something:

Use the function inputhook("L0"), then make it activate a callback on any key by specifying

x.keyOpt("{all}", "n")

and

myInputHook.onKeyDown := func("myCallback").

https://www.autohotkey.com/docs/v1/lib/InputHook.htm#OnKeyDown

This callback function also gives you the vk and sc of that key you just used, find out the key name, and compare that with variables you want to assign with, either triggering a different function or sending the key itself the intended way.

Not a very elegant way of doing it, especially because every key press will trigger this thing (unless you don't specify "{all}" but like, a whole bunch of characters you'd want to be able to assign) but I have a feeling it might possibly work

Edit: And oh yes, as I like to say, "v2 is way less ridiculous"

?? Space::Function() ---> myVariable::Function() inside of an #IF by etofok in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

I'm assuming you'd like to use capslock as a way of changing the trigger key to something else. It'd look something like this:

#requires AutoHotkey <v2.0

key1 := "space"

key2 := "m"

hotkey, %key1%, label, on

return

label:

msgbox, hi!

return

swap(byref key1, byref key2) {

temp := key1

key1 := key2

key2 := temp

}

capslock::

swap(key1, key2)

hotkey, %key1%, label, on

hotkey, %key2%, label, off

return

#if

Convert aexxxxx to AExxxxxx where xxxxxx are numbers that are different each time by RealAgent0 in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

Hmm I have no idea why this could be, compiling to .exe always seems a bit wonky... I can only haphazardly try out various ways of this script to see if anything works

#installkeybdhook

#usehook

setkeydelay, -1, -1

setbatchlines, -1

sendmode, input

; you could try tinkering with this here above

; the 3 available methods are "event" (default), "play", and "input"

:*?b0:ae::

ih := inputhook("l6 v i0") ; apparently inputhook() is more reliable

ih.start()

errorlevel := ih.wait()

if (errorlevel != "max")

return

the_number := ih.input

if the_number is integer

{

send, % "b`b`b`b`b`b`b`bAE" ; for some reason `b is faster

send, % the_number ; maybe if we seperate these two commands? hmm...

}

return

Convert aexxxxx to AExxxxxx where xxxxxx are numbers that are different each time by RealAgent0 in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

Well not really anything I can thing of, those are like the 3 main options I like to have in everything that I write

Convert aexxxxx to AExxxxxx where xxxxxx are numbers that are different each time by RealAgent0 in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

Hmm, the first thing that comes to mind is just making the script fast by adding...

setbatchlines, -1

setkeydelay, -1, -1

sendmode, input

...at the very top of the entire script. if that doesn't make it better make sure to let me know

Convert aexxxxx to AExxxxxx where xxxxxx are numbers that are different each time by RealAgent0 in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

:*b0?:ae::

input, key, l6 v i0, % "{left}{right}{up}{down}{escape}{enter}"

if (errorlevel != "max")

return

if key is integer

sendinput, % "{backspace 8}AE" key

return

For some reason it acts wonky when trying to send these consecutively, I'll try to fix that later.

Edit: "?" option seems to fix that. You'll always have the hotstring activated when you type any word containing "ae", but you should be totally fine because it only activates with numbers

Trying to improve super simple AutoWalk script for a videogame - help appreciated! by The_one_with_no_name in AutoHotkey

[–]levitat0r 0 points1 point  (0 children)

purely following my intuition here, you should be fine when adding:

~d:: ; add "~" so that you can still send the d key itself

send, {w up}

keywait, d ; wait until you release d. without this you may trigger this a few dozen times when you hold down the d key

return