lyarenei / mausmakro

Mouse macro player for automating various time consuming and repeating tasks
MIT License
1 stars 0 forks source link

[Proposal] API improvement #1

Open MatyasKriz opened 3 years ago

MatyasKriz commented 3 years ago

Hey! Love this little autoclicker (with image detection functionality no less, very nice), though I feel like I'm reading ASM.

I'm taking the simple.mkr into account as I write this, so there might be more possible improvements for use-cases not presented there.

The proposal will be using two-space indentation to make it easier on the eyes, the indentation is not the object of interest and I agree with accepting \t, [2 spaces], and [4 spaces] as per the EBNF.

My API example proposal:

select_thing {
  # Making this a block instead of a single instruction allows
  # us to add further configuration down the road as well as use parameters.
  # Relative paths should use '.' or '..' to prevent path clashes.
  # "10s" is the timeout of finding the image.
  image('./common/foo.png', 10s) {
    # `coordinates` is automatically injected, an alias `coords` can be used as well.
    click(coordinates)
    wait(2s)
    # You can perform simple arithmetics.
    click(coords + (5, -10))
  }
  # ...
}
# ...
automate_other(image_path) {
  click(image(image_path, 7s))
  wait(1s)

  select_thing()

  # return is implicit
}

And along with this, abolishing PROC and MACRO, instead using just the identifier, as seen in the example. Returns should be implicit then and MACRO (which would become the only method) would behave consistently whether or not the user specifies a RETURN at the end.

The api can be changed and improved further, so I'm just throwing this out there to see if it sticks, we can work out the details as we go along if you're interested in going this path.

I agree with keeping the script simple, so adding complex ideas like loops, recursion, and optional parameters is something that would be a nice addition, ease of use comes first.

Thanks for considering any of my points and coming to my TED talk!

MatyasKriz commented 3 years ago

Making the IMAGE into image { ... } would also remove the need for IF for the moment. You could write something like

image('./common/bar.png', 15s) {
  click(coords)
} else {
  # don't()
}
lyarenei commented 3 years ago

Hi, thanks for your feedback.

In regards to indentation, I think that's a leftover from a very early prototype, which I forgot to remove. As far as I know, the indentation is not enforced like for example in python, so you are free to choose whatever indentation you want as long as you conform to other rules of the grammar. I should probably remove that indentation rule to avoid confusion.

As for the ASM-like syntax, this was intentional - to keep the language very simple, so even a user without any programming experience can use it. But maybe this one would be just better to resolve with GUI implementation. But there's only a CLI now and I'm not sure if your proposals would make the written code still as easy to understand for non-programmer users.

Otherwise, I think your proposal has some good points. But it would unfortunately require an extensive rewriting of the parser and intra-code generator that would also probably need a reconsideration of its structure. I'm not saying this is a big project or anything, but it's still a work what would need to be done.

Could you please at least write an EBNF which would describe this proposal? I think that would be a good first step to see if it's viable or not. Maybe even a GUI could be implemented with new functionality in mind and then the backend could be rewritten to accept new grammar, what do you think?

And as for the images being changed as you propose - to remove the IF statements - how do you propose describing inverse cases? That is, if the image is not found?

Thanks for the answers!

MatyasKriz commented 3 years ago

Well, if it makes sense to do something only if the image is not found, I guess there's a valid use-case.

I'm not going to convert it to the Lark thing, but in text the base code would look like this:

IDENTIFIER      : (LETTER | "_" | "-" | DIGIT)+
FILE            : FILENAME "." EXTENSION

EXPR            : EXPR (("+" | "-") TERM)?
TERM            : TERM
// Needed for injected `coordinates`.
                | IDENTIFIER
                | "(" TERM ")"
                | COORDINATES
                | TIME
                | STRING
                | MAUS_CALL
COORDINATES     : "-"? INT WS* "," WS* "-"? INT
TIME            : INT WS* ("s" | "m" | "h")
// Anything `.*`, I don't know the EBNF equivalent. Probably excluding newlines.
STRING          : "'" .* "'"

MAUS_CALL_BODY  : MAUS_CALL BODY?
MAUS_CALL       : IDENTIFIER "(" (WS* ARGUMENT+)? ")"
ARGUMENT        : EXPR ","? WS*

MAUS_DECL       : IDENTIFIER ("(" WS* PARAMETER+ ")")? BODY
PARAMETER       : IDENTIFIER ","? WS*

// Can be used for returning from function early, that's why the TERM is optional.
RETURN          : "return" TERM?
BODY            : "{" (INSTRUCTION | CONDITIONAL)+ "}"
INSTRUCTION     : MAUS_CALL_BODY | RETURN
// Is it possible to mark the "not" somehow to not need (almost) duplicate rules?
CONDITIONAL     : "if" "not"? MAUS_CALL BODY ("else" BODY)?

TOP_LEVEL       : MAUS_DECL+

Where click, double_click, find, pause, pclick, pfind, wait would be built-in mauses. I mean functions.

I removed jumping and labels completely, because recursion can be used for loops (and I can already hear you complaining about the call stack; no, because the language is so simple, you can squash the recursion into a simple loop when interpreting it).

Whitespace isn't fully thought out and my knowledge isn't really fresh on EBNF, so take it with a grain of salt.

lyarenei commented 3 years ago

I've rewritten the ebnf for lark - leaving it here for furture reference:

%import common.DIGIT
%import common.INT
%import common.LETTER
%import common.NEWLINE
%import common.WS

%ignore /#[^\n]*/
%ignore NEWLINE
%ignore WS

IDENTIFIER      : (LETTER | "_" | "-" | DIGIT)+
COORDINATES     : "-"? INT WS* "," WS* "-"? INT
TIME            : INT WS* ("s" | "m" | "h")
STRING          : /'.+'/
                | /".+"/
PARAMETER       : IDENTIFIER ","? WS*

term            : IDENTIFIER
                | "(" term ")"
                | COORDINATES
                | TIME
                | STRING
                | maus_call

maus_call       : IDENTIFIER "(" (WS* argument+)? ")"
return          : "return" term?
argument        : expr ","? WS*
expr            : (("+" | "-") term)?
body            : "{" (instruction | conditional)+ "}"
maus_call_body  : maus_call body?
instruction     : maus_call_body | return
conditional     : "if" "not"? maus_call body ("else" body)?
maus_decl       : IDENTIFIER ("(" WS* PARAMETER+ ")")? body

start           : maus_decl+

I'll leave this issue at that for now. As I wish to preserve the simplicity offered by the language which is currently used, I'm now thinking about how to proceed. One solution could be to implement a GUI, the other could be to introduce modularity (either with support for external modules or with command line switches). As I don't have an experience with either, I'll need to think it through and probably do some research too.

MatyasKriz commented 3 years ago

Yeah, I was thinking a GUI might solve this as well. It sure is a problem that can be solved by multiple ways.