If with three ors

I’m getting this error:

journalism_class line 117: Invalid expression at char 38, expected no more tokens, found: BOOLEAN_OPERATOR [or]

The relevant lines:

*temp hair_insult
*if (hair = "short") or (hair = "long") or (hair = "chin-lenth")
    *set hair_insult "boring"
*else
    *set hair_insult "ridiculous"

The hair variable is set to "chin-length" in my current tests, and the *if line is 117. I’m not sure what’s going on here.

I tried adding parenthesis around things like this:

*if (hair = "short") or ((hair = "long") or (hair = "chin-lenth"))

but that seems to always fall into the *else clause.

Any help?

You need one more set of parentheses I think. So like

*if ((var = "whatever") or ((var = "something") or (var = "who knows")))

At least, I THINK that’s the problem. Don’t quote me, but I always end up putting another just to be safe and it seems to work out. Try it out, and if I’m full of hot air, well hopefully someone else knows.

1 Like

FWIW, I fixed my typo (chin-lenth) and now I’m getting this:

journalism_class line 119: It is illegal to fall in to an *else statement; you must *goto or *finish before the end of the indented block.

That’s whenever hair is “chin-length”, “long” or “short”. Do you have to use a *goto in an *if if you also use an *else?

For giggles, I did this:

*temp hair_insult "ridiculous"
*if (hair = "short") or ((hair = "long") or (hair = "chin-length"))
    *set hair_insult "boring"

With no *else clause and it works. Just seems like poor style. Not as expressive of my intention.

Just tried this and it didn’t impact my "fall in to an *else" error. :sob:

Did you try

*if ((hair = “short”) or ((hair = “long”) or (hair = “chin-length”)))

You have to be careful of the brackets.

Also do you need a *goto after that if statement or a finish? I’m not sure since I don’t generally use else myself.

If using more than two conditions each pair must be grouped using extra brackets, although it doesn’t matter which two are paired so long as your logic still says what you want it to say (e.g. in your case “short” and “long” could be paired, or “long” and “chin-length” as shown in @FairyGodfeather’s example). If you had four conditions rather than three, you would have to group two pairs for the *if to work properly, etc.

Any use of *else (and *elseif) in an *if statement must always tell ChoiceScript what to do in each particular case - whether *goto, *finish, *goto_scene, *ending, or whatever - otherwise it will result in the fall out error you’re experiencing there.

As Mr V says, your primary problem is the need for a goto or similar command if you’re going to use *else. (You could use a bunch of *ifs without needing a *goto, but as soon as elseif or else come into it, or if it follows a *choice, ChoiceScript wants you to tell it what to do).

As for the parenthesis requirements for 3+ conditions, I talked about it in posts 8 and 11 of this thread:

I… think this means that *else doesn’t actually do anything. Like… isn’t this:

*if some_boolean_expression
    It was TROOOO!
    *goto anyway
*else
    It was UNTROOOO!

*label anyway
Anyway, it was what it was

equivalent to this:

*if some_boolean_expression
    It was TROOOO!
    *goto anyway

It was UNTROOOO!

*label anyway
Anyway, it was what it was

If that’s true, then it seems like *else is actually just… fluff? Seems weird. In other languages, getting into any if, elseif or else will then skip to the end of the whole structure, rather than advancing into the next clause of it.

Anyway, I can work with this. Thanks, everyone. Just seems weird to me.

On your first example, you’d want a “*goto anyway” after the else clause as well for it to work – but yes, in that case it’s equivalent to your second example.

It’s the non-Boolean variables where elseif and else come into their own. Say for example you want one text if stat <= 0, a different one if stat > 0 but < 5, and so on for three other stat ranges. That quickly gets easier using elseifs, and using “else” at the end is a handy catchall to make sure you haven’t missed anything.

It’s not necessary – you can do it all with ifs (and without gotos). But nor is it fluff, and it doesn’t just advance you to the next clause.

I must not be understanding something about this bit… because it seems to be contradicting itself. Or maybe I’m not understanding the error message. The way I interpreted the phrase “fall in to an *else statement” was that it was running along and proceeding from line to line, then found an *else when it would have expected an instruction to skip the *else.

Maybe an example would help. With line numbers:

1  *if str > 5
2      So, so strong.
3  *else
4      Puny weakling.
5
6  But good of heart.

So, this will blow up if str is 6 because it runs line 1, sees that the boolean expression evaluates to true, the proceeds to line 2 and prints it. In most languages, I would expect it to skip to line 5. But I think the “fall in to” phrase means that the ChoiceScript interpreter next goes to line 3, sees the *else and decides (rightly) that that can’t be what you want, so complains with the error we’re seeing.

What am I missing? Anyway, that’s what I meant by “advancing into the next clause”.

*else does need a *goto (unnecessary most of the time if you are only using *if). But if you’re sifting out a lot of complicated variables, it’s invaluable.

Let’s say I have a male character in one of my scenes (yes, this is a scene I’m working on.) If you prefer girls, he’s replaced with the female version of that character, so you should go to the section with the female character - that’s the *if. If you’re asexual, you should skip past the bit where you have an option to flirt with him - that’s the *elseif. *Else then keeps me from having to nest every single other condition that might apply if you are neither attracted to only females, or asexual.

Example very helpful! You’ve pretty clearly got the right interpretation of the error message, and as I’m a non-programmer who only knows ChoiceScript, I’ve got no idea why the language isn’t designed to skip to the end of the block when it sees an *else.

So I’m happy to speculate that the *gotos that follow if/elseif/else are fluff (i.e. the language could be written to make them superfluous). But leaving the gotos out of it for the moment, there’s a useful difference between

You demonstrate your strength by
*if str > 20
  throwing a boulder over the castle wall.
*if (str < 21) and (str > 10)
  pulling a loaded hay-cart for a mile.
*if (str < 11) and (str > 5)
  staving in a barrel with a single axe-blow.
*if (str < 6) and (str > 0)
  hoisting a child under each arm.
*if str <= 0
  almost managing a chin-up.

and

You demonstrate your strength by
*if str > 20
  throwing a boulder over the castle wall.
*elseif str > 10
  pulling a loaded hay-cart for a mile.
*elseif str > 5
  staving in a barrel with a single axe-blow.
*elseif str > 0
  hoisting a child under each arm.
*else
  almost managing a chin-up.

And I find it convenient enough that I usually don’t mind copy-pasting a *goto next_label after every other line in the latter case.

It may well be that ChoiceScript could be rewritten to make those *gotos gratuitous… but as far as I can see, that’s not quite the same thing as saying that in the current version *else/*elseif does nothing.

1 Like

I’m responsible for the annoying behavior with *else. Here’s the background, which may or may not make sense if you don’t have a programming background.

ChoiceScript has a relatively unusual design goal: the goal is to support very large functions, hundreds or even thousands of lines long. In general-purpose programming languages (basically any programming language in ordinary use, like JavaScript, Python, C, etc.) having functions that are more than a few dozen lines long is a bad idea.

They’re a bad idea because they can often require jumping to another part of the code that you can’t even see on the screen. The standard example I use is:

*choice
    #Be very naughty.
        […30 lines of code…]
       Santa refuses to give you a present.
   #Be mostly nice.
       […30 lines of code…]
       Santa gives you a present reluctantly.
   #Be as nice as can be.
       […30 lines of code…]
       Santa gives you a present enthusiastically.

Inside the gift box is a video game!

The same principle applies to *if/*else chains.

*if naughty > 70
    [...30 lines of code...]
    Santa refuses to give you a present.
*elseif naughty > 50
    [...30 lines of code...]
    Santa gives you a present reluctantly.
*else
    [...30 lines of code...]
    Santa gives you a present enthusiastically.

Inside the gift box is a video game!

If the example is short enough, then it’s obvious that there’s a bug here: player gets a video game even if Santa refuses to provide one. But when the code gets long enough, when the “video game” line is more than a hundred lines away from the “Santa refuses” line, it’s very hard to find this bug just by reading the code.

In a regular programming language, you’d probably handle it like this:

*choice
    #Be very naughty.
        *gosub naughty
    #Be mostly nice.
        *gosub mostly_nice
    #Be as nice as can be.
        *gosub very_nice

Inside the gift box is a video game!

*label naughty
[…30 lines of code…]
Sanda refuses to give you a present.
*return

*label mostly_nice
[…30 lines of code…]
Santa gives you a present reluctantly.
*return

*label very_nice
[…30 lines of code…]
Santa gives you a present enthusiastically.
*return

But the goal of ChoiceScript was to allow you to have hundreds of lines of code right there on the spot, without using subroutines. So, how could we do it?

I decided to require a *goto at the end of every indented block; before every *else or *elseif and before every #option you’d have to *goto (or *finish or *ending etc.)

*choice
    #Be very naughty.
        […30 lines of code…]
        Santa refuses to give you a present.
        *goto present
    #Be mostly nice.
        […30 lines of code…]
        Santa gives you a present reluctantly.
        *goto present
    #Be as nice as can be.
        […30 lines of code…]
        Santa gives you a present enthusiastically.

*label present
Inside the gift box is a video game!

That makes it obvious that the *goto present line right next to the “Santa refuses” line is an error.

And so it is forbidden to “fall into” *else or an #option. (It’s OK to “fall out” of the last #option or *else because that’s guaranteed not to jump to a line of code you can’t see: you’ll just go to the next line.)

The primary drawback of this rule is that it’s outrageously annoying for short code. If you really do want a *choice or *if/*elseif/*else chain that’s under a dozen lines long, having to add those extra *goto lines is a lot of extra typing.

@benhamill is also right that this means that *else is kinda “fluffy.” In a language with *if and *goto, you never really need an *else command at all; you can rewrite all of your *elseif commands to be simple *if statements. But I think you’ll find that the code is usually easier to read when you use *else and *elseif commands, even if it means that you have to add *goto lines.

For the record, another approach I’ve considered is to explicitly forbid large jumps. So if you omit the *goto you might get an error like this:

*line 34: Large jump error. After this line, we’d need to jump ahead 68 lines to line 102; that’s too big a jump to be safe. Add a goto statement so we know that’s what you meant to do.

(Perhaps the number would be configurable by the author; you could set it to 0 to get today’s annoying behavior, or turn off “large jump errors" completely.)

But I haven’t done that yet, partly because it would require work, but also because it would make ChoiceScript even weirder than it is today. :blush:

5 Likes

This is absolutely fascinating background. Thanks so much for providing it. This makes a lot of sense. :smile: