https://kotlinlang.org logo
#getting-started
Title
# getting-started
r

Ray Rahke

03/06/2024, 4:40 PM
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

Klitos Kyriacou

03/06/2024, 4:55 PM
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

Ray Rahke

03/06/2024, 4:57 PM
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

Luke

03/06/2024, 4:59 PM
Is the string list static? i.e. Is there a finite amount of prefixes?
r

Ray Rahke

03/06/2024, 5:01 PM
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

Luke

03/06/2024, 5:01 PM
Actually, with my idea, you'll still have a
when (prefix) { ... }
so there is not much gain
r

Ray Rahke

03/06/2024, 5:01 PM
and those god commands might include other god commands
y

Youssef Shoaib [MOD]

03/06/2024, 5:02 PM
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

Ray Rahke

03/06/2024, 5:58 PM
@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

Klitos Kyriacou

03/06/2024, 6:00 PM
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

Ray Rahke

03/06/2024, 6:01 PM
oh cool
k

Klitos Kyriacou

03/06/2024, 6:03 PM
Also, if you have no delimiter, it will return the string itself. So
"y".substringBefore(' ')
will return
"y"
.
l

Luke

03/06/2024, 6:51 PM
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

Shawn

03/06/2024, 7:32 PM
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

ephemient

03/06/2024, 10:07 PM
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

Jacob

03/07/2024, 6:44 AM
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

Ray Rahke

03/08/2024, 5:14 PM
@Shawn that kicks the can down the road. The tokenization process will involve exactly the same question of "startingWith"
s

Shawn

03/09/2024, 3:52 AM
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