Check first few chars of argument to ca65 macro?

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

User avatar
GradualGames
Posts: 1107
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Check first few chars of argument to ca65 macro?

Post by GradualGames » Sun Aug 06, 2017 10:53 am

I've been wondering if there's any way to improve on this:

Code: Select all

                .if (.strat(.string(arg), 0) = 'e' && \
                     .strat(.string(arg), 1) = 'n' && \
                     .strat(.string(arg), 2) = 't' && \
                     .strat(.string(arg), 3) = 'i' && \
                     .strat(.string(arg), 4) = 't' && \
                     .strat(.string(arg), 5) = 'y' && \
                     .strat(.string(arg), 6) = '_')
It works, but it's damn ugly. I would expect to be able to do something like this:

Code: Select all

    .if (.match (.left (7, {arg}), entity_))
But this doesn't work. Interestingly, this doesn't do what I expect either:

Code: Select all

    .out .string(.left(7, {arg}))
I actually get the entire contents of arg.

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by thefox » Sun Aug 06, 2017 11:45 am

You could write a macro:

Code: Select all

.macro string_starts_with string, starts_with, ret
  ret .set 1
  .if .strlen(string) < .strlen(starts_with)
    ret .set 0
    .exitmac
  .endif
  .repeat .strlen(starts_with), i
    .if .strat(string, i) <> .strat(starts_with, i)
      ret .set 0
      .exitmac
    .endif
  .endrepeat
.endmacro
Usage:

Code: Select all

.macro my_macro arg
  .local is_entity
  string_starts_with .string(arg), "entity_", is_entity
  .if is_entity
    .out "arg started with 'entity_'"
  .else
    .out "arg didn't start with 'entity_'"
  .endif
.endmacro
EDIT: Fixed a bug (macro accepted anything starting with "e", "en", "ent", etc).
Last edited by thefox on Sun Aug 06, 2017 2:20 pm, edited 1 time in total.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

User avatar
GradualGames
Posts: 1107
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by GradualGames » Sun Aug 06, 2017 11:49 am

I was considering trying something like that. But why the heck doesn't .left work? I use it, but it only ever works with "1" as the parameter. Anything else fails.

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by thefox » Sun Aug 06, 2017 11:52 am

GradualGames wrote:I was considering trying something like that. But why the heck doesn't .left work? I use it, but it only ever works with "1" as the parameter. Anything else fails.
.left works on tokens, not strings. A string is always a single token.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

User avatar
GradualGames
Posts: 1107
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by GradualGames » Sun Aug 06, 2017 11:57 am

thefox wrote:
GradualGames wrote:I was considering trying something like that. But why the heck doesn't .left work? I use it, but it only ever works with "1" as the parameter. Anything else fails.
.left works on tokens, not strings. A string is always a single token.
Is there some place in the documentation that clarifies the difference between strings, tokens, token lists, etc that I haven't yet spotted?

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by thefox » Sun Aug 06, 2017 12:09 pm

GradualGames wrote:Is there some place in the documentation that clarifies the difference between strings, tokens, token lists, etc that I haven't yet spotted?
Not to my knowledge. There's a list of tokens at https://github.com/cc65/cc65/blob/maste ... 65/token.h.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

User avatar
GradualGames
Posts: 1107
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by GradualGames » Sun Aug 06, 2017 12:13 pm

thefox wrote:You could write a macro:

Code: Select all

.macro string_starts_with string, starts_with, ret
  ret .set 0
  .if .strlen(string) < .strlen(starts_with)
    .exitmac
  .endif
  .repeat .strlen(starts_with), i
    .if .strat(string, i) = .strat(starts_with, i)
      ret .set 1
      .exitmac
    .endif
  .endrepeat
.endmacro
Usage:

Code: Select all

.macro my_macro arg
  .local is_entity
  string_starts_with .string(arg), "entity_", is_entity
  .if is_entity
    .out "arg started with 'entity_'"
  .else
    .out "arg didn't start with 'entity_'"
  .endif
.endmacro
What is ret .set ? I don't see this in the ca65 doc either.

User avatar
GradualGames
Posts: 1107
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by GradualGames » Sun Aug 06, 2017 12:18 pm

Wait, I understand now. ret is an argument to the macro, .set is in fact documented.

User avatar
GradualGames
Posts: 1107
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by GradualGames » Sun Aug 06, 2017 1:09 pm

I was able to get your macro to work, but I had to change a couple of things. I think you had the logic reversed (start true, but exit if false)

Code: Select all


.macro string_starts_with string, sub_string
    starts_with .set 1
    .if .strlen(string) < .strlen(sub_string)
        starts_with .set 0
        .exitmac
    .endif
    .repeat .strlen(sub_string), i
        .if !(.strat(string, i) = .strat(sub_string, i))
            starts_with .set 0
            .exitmac
        .endif
    .endrepeat
.endmacro

Much nicer using this. Thanks for the idea!

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by thefox » Sun Aug 06, 2017 2:20 pm

GradualGames wrote:I was able to get your macro to work, but I had to change a couple of things. I think you had the logic reversed (start true, but exit if false)
Oops, you're right. I edited my post.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

User avatar
GradualGames
Posts: 1107
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by GradualGames » Mon Aug 07, 2017 10:08 am

I found a neat way to accomplish the same "starts with" logic on a macro argument without a custom helper macro:

Code: Select all

    .if .xmatch (.left (1, .ident(.sprintf("%.7s", .string(arg)))), entity_)

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by thefox » Mon Aug 07, 2017 11:29 am

GradualGames wrote:I found a neat way to accomplish the same "starts with" logic on a macro argument without a custom helper macro:

Code: Select all

    .if .xmatch (.left (1, .ident(.sprintf("%.7s", .string(arg)))), entity_)
Ah, that's cool. It can be simplified a little further:

Code: Select all

.if .xmatch(.sprintf("%.7s", .string(arg)), "entity_")
  ; ...
.endif
Here's a version with a generic .define style "string_starts_with" macro based on the same idea:

Code: Select all

.define string_starts_with(string, starts_with) .xmatch(.sprintf(.sprintf("%%.%ds", .strlen(starts_with)), string), starts_with)
; ...
.if string_starts_with .string(arg), "entity_"
  ; ...
.endif
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

User avatar
GradualGames
Posts: 1107
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by GradualGames » Mon Aug 07, 2017 11:47 am

Just for the heck of it, I'm trying to think if there would be a way to do an "ends with" as well with as little code. I haven't thought of anything yet. One would probably have to use .strat and .strlen(arg) and walk back indices from the end of the string in that case. Aw well can't have everything. :lol: Unless there's more voodoo possible with .sprintf I haven't thought of. C lets you do pointer arithmetic but I don't think ca65 is exposing that type of functionality even though it might be calling sprintf in the C runtime under the hood after forwarding various parameters along...

User avatar
thefox
Posts: 3141
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by thefox » Mon Aug 07, 2017 12:13 pm

GradualGames wrote:Just for the heck of it, I'm trying to think if there would be a way to do an "ends with" as well with as little code. I haven't thought of anything yet. One would probably have to use .strat and .strlen(arg) and walk back indices from the end of the string in that case. Aw well can't have everything. :lol: Unless there's more voodoo possible with .sprintf I haven't thought of. C lets you do pointer arithmetic but I don't think ca65 is exposing that type of functionality even though it might be calling sprintf in the C runtime under the hood after forwarding various parameters along...
Challenge accepted. I think this one should work:

Code: Select all

.define string_ends_with(string, ends_with) .xmatch(.sprintf(.sprintf("%%.%ds%%s", (.strlen(ends_with) < .strlen(string)) * (.strlen(string) - .strlen(ends_with))), string, ends_with), string)
It works by stripping off .strlen(string) - .strlen(ends_with) characters from the end of string, then appends ends_with to it, and compares to the original string. Some trickery was needed to avoid putting a negative number in the format string in case ends_with is longer than string. (ca65 doesn't short circuit expression evaluation so can't use .and to safeguard it.)

EDIT: "shorter than" => "longer than"
Last edited by thefox on Thu Sep 28, 2017 10:15 am, edited 1 time in total.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi

User avatar
GradualGames
Posts: 1107
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Check first few chars of argument to ca65 macro?

Post by GradualGames » Mon Aug 07, 2017 12:23 pm

thefox wrote:
GradualGames wrote:Just for the heck of it, I'm trying to think if there would be a way to do an "ends with" as well with as little code. I haven't thought of anything yet. One would probably have to use .strat and .strlen(arg) and walk back indices from the end of the string in that case. Aw well can't have everything. :lol: Unless there's more voodoo possible with .sprintf I haven't thought of. C lets you do pointer arithmetic but I don't think ca65 is exposing that type of functionality even though it might be calling sprintf in the C runtime under the hood after forwarding various parameters along...
Challenge accepted. I think this one should work:

Code: Select all

.define string_ends_with(string, ends_with) .xmatch(.sprintf(.sprintf("%%.%ds%%s", (.strlen(ends_with) < .strlen(string)) * (.strlen(string) - .strlen(ends_with))), string, ends_with), string)
It works by stripping off .strlen(string) - .strlen(ends_with) characters from the end of string, then appends ends_with to it, and compares to the original string. Some trickery was needed to avoid putting a negative number in the format string in case ends_with is shorter than string. (ca65 doesn't short circuit expression evaluation so can't use .and to safeguard it.)
:shock: Brilliant! Would that be possible without using C style macros in conjunction with the other facilities? Guessing not. Not sure I understand all the syntax there, particularly the double %%'s.

Post Reply