This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Check first few chars of argument to ca65 macro?

Check first few chars of argument to ca65 macro?
by on (#201798)
I've been wondering if there's any way to improve on this:

Code:
                .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:
    .if (.match (.left (7, {arg}), entity_))


But this doesn't work. Interestingly, this doesn't do what I expect either:

Code:
    .out .string(.left(7, {arg}))


I actually get the entire contents of arg.
Re: Check first few chars of argument to ca65 macro?
by on (#201802)
You could write a macro:
Code:
.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:
.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).
Re: Check first few chars of argument to ca65 macro?
by on (#201803)
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.
Re: Check first few chars of argument to ca65 macro?
by on (#201804)
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.
Re: Check first few chars of argument to ca65 macro?
by on (#201805)
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?
Re: Check first few chars of argument to ca65 macro?
by on (#201806)
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.
Re: Check first few chars of argument to ca65 macro?
by on (#201808)
thefox wrote:
You could write a macro:
Code:
.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:
.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.
Re: Check first few chars of argument to ca65 macro?
by on (#201809)
Wait, I understand now. ret is an argument to the macro, .set is in fact documented.
Re: Check first few chars of argument to ca65 macro?
by on (#201815)
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:

.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!
Re: Check first few chars of argument to ca65 macro?
by on (#201825)
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.
Re: Check first few chars of argument to ca65 macro?
by on (#201887)
I found a neat way to accomplish the same "starts with" logic on a macro argument without a custom helper macro:

Code:
    .if .xmatch (.left (1, .ident(.sprintf("%.7s", .string(arg)))), entity_)
Re: Check first few chars of argument to ca65 macro?
by on (#201903)
GradualGames wrote:
I found a neat way to accomplish the same "starts with" logic on a macro argument without a custom helper macro:

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

Ah, that's cool. It can be simplified a little further:
Code:
.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:
.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
Re: Check first few chars of argument to ca65 macro?
by on (#201906)
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...
Re: Check first few chars of argument to ca65 macro?
by on (#201910)
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:
.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"
Re: Check first few chars of argument to ca65 macro?
by on (#201912)
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:
.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.
Re: Check first few chars of argument to ca65 macro?
by on (#201915)
GradualGames wrote:
Not sure I understand all the syntax there, particularly the double %%'s.

Those are there because of the inner sprintf constructs the format string for the outer sprintf. So, for example .sprintf("%%.%ds", 9) would turn into "%.9s" which is used as the format string in the outer sprintf.

(Note that unlike C sprintf, ca65 does not support specifying the field precision as an argument with "*", so nested sprintfs are needed.)
Re: Check first few chars of argument to ca65 macro?
by on (#201916)
thefox wrote:
GradualGames wrote:
Not sure I understand all the syntax there, particularly the double %%'s.

Those are there because of the inner sprintf constructs the format string for the outer sprintf. So, for example .sprintf("%%.%ds", 9) would turn into "%.9s" which is used as the format string in the outer sprintf.

(Note that unlike C sprintf, ca65 does not support specifying the field precision as an argument with "*", so nested sprintfs are needed.)

Ah, %% is an escape for itself then? Gotcha. Cool! There's a couple of other things I don't understand in there, it looks like you're doing a multiply on strlens somewhere, too, what's that for? *edit* I think I understand now that < is returning 0 or 1 so you can prevent it from parameterizing sprintf with a negative number, the trickery you mentioned, I assume. Neat.
Re: Check first few chars of argument to ca65 macro?
by on (#201917)
GradualGames wrote:
There's a couple of other things I don't understand in there, it looks like you're doing a multiply on strlens somewhere, too, what's that for?

That's the "trickery" I mentioned. (.strlen(ends_with) < .strlen(string)) evaluates to 0 if the ends_with is shorter than string (and 1 otherwise). The multiplication then forces the expression value to 0 to avoid a negative value going into the format string. E.g., "%.-7s" would not be a valid format string.
Re: Check first few chars of argument to ca65 macro?
by on (#201919)
thefox wrote:
GradualGames wrote:
There's a couple of other things I don't understand in there, it looks like you're doing a multiply on strlens somewhere, too, what's that for?

That's the "trickery" I mentioned. (.strlen(ends_with) < .strlen(string)) evaluates to 0 if the ends_with is shorter than string (and 1 otherwise). The multiplication then forces the expression value to 0 to avoid a negative value going into the format string. E.g., "%.-7s" would not be a valid format string.

I can't seem to get ca65 to be happy with it. Trying to compile the following:

Wondering if it's a linecont issue. Like if I use .linecont + and then add some \ 's if it'll work.

Code:
.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)
.if string_ends_with("hello", "llo")
    .out "yes"
.else
    .out "no"
.endif
Re: Check first few chars of argument to ca65 macro?
by on (#201921)
GradualGames wrote:
thefox wrote:
GradualGames wrote:
There's a couple of other things I don't understand in there, it looks like you're doing a multiply on strlens somewhere, too, what's that for?

That's the "trickery" I mentioned. (.strlen(ends_with) < .strlen(string)) evaluates to 0 if the ends_with is shorter than string (and 1 otherwise). The multiplication then forces the expression value to 0 to avoid a negative value going into the format string. E.g., "%.-7s" would not be a valid format string.


I can't seem to get ca65 to be happy with it. Trying to compile the following:

Wondering if it's a linecont issue. Like if I use .linecont + and then add some \ 's if it'll work.

Code:
.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)
.if string_ends_with("hello", "llo")
    .out "yes"
.else
    .out "no"
.endif

You should not use parens when calling macros. Try:
Code:
.if string_ends_with "hello", "llo"
    .out "yes"
.else
    .out "no"
.endif
Re: Check first few chars of argument to ca65 macro?
by on (#201922)
thefox wrote:
You should not use parens when calling macros. Try:
Code:
.if string_ends_with "hello", "llo"
    .out "yes"
.else
    .out "no"
.endif

Oh! :oops: Probably just force of habit with C style macros... I haven't thus far used any in my ca65 programs, only normal ca65 macros.
Re: Check first few chars of argument to ca65 macro?
by on (#201997)
thefox wrote:
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.

Very clever!