obviously this is not right but I am not sure how ...
# getting-started
r
obviously this is not right but I am not sure how to pattern match over what a string might start with
Copy code
val input = readln()
when input.startsWith() {
    "y" -> {...}
    "n" -> {...}
    "help" -> {...}
    "attack" -> {...}
    "teleport" -> {...}
}
I could obviously do the following:
Copy code
when {
    input.startsWith("y") -> {...}
    input.startsWith("n") -> {...}
    input.startsWith("help") -> {...}
    input.startsWith("attack") -> {...}
    input.startsWith("teleport") -> {...}
}
but that is terribly repetitive
Another unsatisfying option is to create a function that takes in a list of those string cases, and then returns that string case when it returns true on inputStartsWith(x), which I would then
when
over. This sucks because it requires writing each string twice. Like, for 5 string options, I would have to write 10 references.
And I can't use a .filter / .find, because I need to retain knowledge of which string it matched, to do further nested checks (i.e once I know the first word was "teleport", I have more
when
questions I need to ask nested.)
k
It seems these are complete words or common abbreviations. Are these prefixes always delimited with a space? If so, you can do this:
Copy code
when (input.substringBefore(' ')) {
    "y" -> {...}
    "n" -> {...}
    "help" -> {...}
    "attack" -> {...}
    "teleport" -> {...}
}
r
yeah the concern is that I want to avoid early conclusions
y is a command, but yonder might also be a command
so I need to do some further lookahead and see if 'onder' follows, nested within if 'y'
user could either mean y = yes or yonder
yeah I think they are always delimited by space actually.
l
Is the string list static? i.e. Is there a finite amount of prefixes?
r
yeah but there can be recursive references. like
magic create commandbox "teleport base"
or
magic create commandbox "magic create diamondsword && give self diamondsword"
like, command blocks in minecraft
so a game mechanic where you can execute god commands
l
Actually, with my idea, you'll still have a
when (prefix) { ... }
so there is not much gain
r
and those god commands might include other god commands
y
I would do something like:
Copy code
@JvmInline value class Prefix(val prefix: String)
fun startingWith(prefix: String) = Prefix(prefix)
operator fun Prefix.contains(string: String) = string.startsWith(prefix)
when(input) {
  in startingWith("y") -> ...
  in startingWith("n") -> ...
}
r
@Klitos Kyriacou how could i do your approach with arbitrary long space delimiters
i.e for both
attack john
attack    john
dont think splitting will work here.
since that requires me knowing which index i'll be at.
k
It doesn't matter how long the delimiter is.
substringBefore(' ')
will stop at the first
' '
character, so both
"attack john".substringBefore(' ')
and
"attack     john".substringBefore(' ')
will return
"attack"
.
r
oh cool
k
Also, if you have no delimiter, it will return the string itself. So
"y".substringBefore(' ')
will return
"y"
.
l
If you want to split an arbitrary sequence of spaces, you can do it with Regex:
.split(" +".toRegex())
but in an example such as
magic create commandbox "teleport base"
, the quotes will end up in different strings
👆 1
s
you need to tokenize the command before you start parsing it—that involves splitting on whatever delimiters you want and handling whatever language constructs (like strings) you want to support before evaluating what it means
☝️ 1
e
it is entirely possible to set up your own categorizations of items, such as
Copy code
@JvmInline
value class StringsStartingWith(val prefix: String) {
    operator fun contains(string: String): Boolean = prefix in string
}

val input = readln()
when (input) {
    in StringsStartingWith("y") -> {...}
    in StringsStartingWith("n") -> {...}
    in StringsStartingWith("help") -> {...}
    in StringsStartingWith("attack") -> {...}
    in StringsStartingWith("teleport") -> {...}
}
but I 100% echo what Shawn says. the way to do this is to tokenize before parsing. it's more correct, more extensible, and easier in the end.
j
Copy code
val cases = listOf(
            "y" to { println("1") },
            "n" to { println("2") },
            "help" to { println("3") },
            "attack" to { println("4") },
            "teleport" to { println("5") },
        )
        val input = "helppp"
        cases.firstOrNull { input.startsWith(it.first) }?.second?.invoke() ?: println("we hit the else")
r
@Shawn that kicks the can down the road. The tokenization process will involve exactly the same question of "startingWith"
s
No, when you're tokenizing or lexing, you're breaking the statement into atoms that you can then do syntactic construction and matching on. Once the statement is tokenized, you hand off to your parser which can build a syntax tree that you evaluate by matching node contents against the commands you support. There's no "startingWith" involved because you'll be matching the node against some set of known commands. This'll be especially important because you plan on supporting statements with constructs like infix boolean operators and arbitrary evaluation depths (though this one you could probably cheese a little by deferring evaluation, i.e. storing the payload statements in your "command box" and not evaluating them until the box is used—this would just mean that your command box needs access to the compiler pipeline, which, depending on how you organize these components, could be a little awkward to implement)
If you want to accept
y
and
n
as aliases for
yes
and
no
, respectively, I would recommend just adding those tokens as commands that also map to
yes
and
no
, rather than trying to do partial string matching that excludes other possibilities