annotate src/main/kotlin/name/blackcap/passman/Shplitter.kt @ 28:287eadf5ab30 default tip

Check for timeouts inside subcommands while in interactive mode as well.
author David Barts <n5jrn@me.com>
date Wed, 31 Jul 2024 11:21:18 -0700
parents ea65ab890f66
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
19
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
1 package name.blackcap.passman
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
2
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
3 // A state is represented by a state-executing function (see below).
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
4 typealias State = (Char) -> Boolean
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
5
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
6 // Support for simplified *nix shell style splitting into tokens. We are
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
7 // focused on splitting things into tokens only. No variable expansion,
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
8 // no ~home expansion, etc. Backslash quotes the next character. Single
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
9 // quotes quote everything literally up to the next closing single quote.
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
10 // Double quotes quote to the closing double quote but honor backslashes
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
11 // (i.e. "\"\nice\"" -> "nice"). No \t, \n, \r etc. backslash escapes
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
12 // supported (KISS).
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
13 class Shplitter() {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
14 private val QUOTING = setOf<State>(::inSingle, ::inDouble)
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
15 private val WHITESPACE = setOf<Char>(' ', '\t', '\n')
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
16 private var oldStates = mutableListOf<State>()
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
17 private var state: State = ::space
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
18 private var accum = mutableListOf<String>()
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
19 private var current = StringBuilder()
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
20
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
21 val complete: Boolean
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
22 get() = state == ::space || state == ::nonspace
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
23
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
24 // Feeds more input into this tokenizer
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
25 fun feed(input: String) : Unit {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
26 for (ch in input) {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
27 while (state(ch))
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
28 ;
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
29 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
30 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
31
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
32 fun split(): Iterable<String> {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
33 if (complete) {
20
4391afcf6bd0 Fix more bugs, correct more bad tests.
David Barts <n5jrn@me.com>
parents: 19
diff changeset
34 if (state == ::nonspace) {
4391afcf6bd0 Fix more bugs, correct more bad tests.
David Barts <n5jrn@me.com>
parents: 19
diff changeset
35 popState()
19
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
36 accum.add(current.toString())
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
37 current.clear()
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
38 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
39 return accum
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
40 } else {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
41 throw IllegalStateException("incomplete quoted expression")
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
42 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
43 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
44
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
45 // State transitions
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
46
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
47 private fun pushState(newState: State): Unit {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
48 oldStates.add(state)
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
49 state = newState
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
50 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
51
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
52 private fun popState(): Unit {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
53 state = oldStates.removeLast()
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
54 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
55
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
56 private fun lastState(): State = oldStates.last()
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
57
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
58 // States. A state is represented by a function that accepts the
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
59 // character currently being processed, and returns whether it should
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
60 // immediately transition to the next state without reading a new
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
61 // character.
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
62
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
63 private fun space(ch: Char): Boolean =
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
64 when (ch) {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
65 in WHITESPACE -> { false }
20
4391afcf6bd0 Fix more bugs, correct more bad tests.
David Barts <n5jrn@me.com>
parents: 19
diff changeset
66 '\'' -> { pushState(::nonspace); pushState(::inSingle); false }
4391afcf6bd0 Fix more bugs, correct more bad tests.
David Barts <n5jrn@me.com>
parents: 19
diff changeset
67 '"' -> { pushState(::nonspace); pushState(::inDouble); false }
4391afcf6bd0 Fix more bugs, correct more bad tests.
David Barts <n5jrn@me.com>
parents: 19
diff changeset
68 '\\' -> { pushState(::nonspace); pushState(::backslash); false }
19
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
69 else -> { pushState(::nonspace); true }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
70 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
71
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
72 private fun nonspace(ch: Char): Boolean =
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
73 when (ch) {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
74 in WHITESPACE -> {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
75 accum.add(current.toString())
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
76 current.clear()
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
77 popState()
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
78 false
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
79 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
80 '\'' -> {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
81 pushState(::inSingle)
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
82 false
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
83 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
84 '"' -> {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
85 pushState(::inDouble)
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
86 false
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
87 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
88 '\\' -> {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
89 pushState(::backslash)
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
90 false
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
91 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
92 else -> {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
93 current.append(ch)
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
94 false
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
95 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
96 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
97
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
98 private fun inSingle(ch: Char): Boolean =
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
99 when (ch) {
20
4391afcf6bd0 Fix more bugs, correct more bad tests.
David Barts <n5jrn@me.com>
parents: 19
diff changeset
100 '\'' -> { popState(); false }
19
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
101 else -> { current.append(ch); false }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
102 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
103
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
104 private fun inDouble(ch: Char): Boolean =
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
105 when (ch) {
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
106 '\\' -> { pushState(::backslash); false }
20
4391afcf6bd0 Fix more bugs, correct more bad tests.
David Barts <n5jrn@me.com>
parents: 19
diff changeset
107 '"' -> { popState(); false }
19
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
108 else -> { current.append(ch); false }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
109 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
110
21
ea65ab890f66 More work to support interactive feature.
David Barts <n5jrn@me.com>
parents: 20
diff changeset
111 // XXX - multiline commands achieved with backslash-newline
ea65ab890f66 More work to support interactive feature.
David Barts <n5jrn@me.com>
parents: 20
diff changeset
112 // sequences not supported, so this only works with single-
ea65ab890f66 More work to support interactive feature.
David Barts <n5jrn@me.com>
parents: 20
diff changeset
113 // line commands.
19
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
114 private fun backslash(ch: Char): Boolean {
21
ea65ab890f66 More work to support interactive feature.
David Barts <n5jrn@me.com>
parents: 20
diff changeset
115 // continue existing string no matter what
ea65ab890f66 More work to support interactive feature.
David Barts <n5jrn@me.com>
parents: 20
diff changeset
116 current.append(ch)
ea65ab890f66 More work to support interactive feature.
David Barts <n5jrn@me.com>
parents: 20
diff changeset
117 popState()
ea65ab890f66 More work to support interactive feature.
David Barts <n5jrn@me.com>
parents: 20
diff changeset
118 return false
19
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
119 }
7d80cbcb67bb add shlex-style splitter and tests
David Barts <n5jrn@me.com>
parents:
diff changeset
120 }