Mercurial > cgi-bin > hgweb.cgi > PassMan
annotate src/main/kotlin/name/blackcap/passman/Shplitter.kt @ 21:ea65ab890f66
More work to support interactive feature.
author | David Barts <n5jrn@me.com> |
---|---|
date | Tue, 02 Jul 2024 11:27:39 -0700 |
parents | 4391afcf6bd0 |
children |
rev | line source |
---|---|
19 | 1 package name.blackcap.passman |
2 | |
3 // A state is represented by a state-executing function (see below). | |
4 typealias State = (Char) -> Boolean | |
5 | |
6 // Support for simplified *nix shell style splitting into tokens. We are | |
7 // focused on splitting things into tokens only. No variable expansion, | |
8 // no ~home expansion, etc. Backslash quotes the next character. Single | |
9 // quotes quote everything literally up to the next closing single quote. | |
10 // Double quotes quote to the closing double quote but honor backslashes | |
11 // (i.e. "\"\nice\"" -> "nice"). No \t, \n, \r etc. backslash escapes | |
12 // supported (KISS). | |
13 class Shplitter() { | |
14 private val QUOTING = setOf<State>(::inSingle, ::inDouble) | |
15 private val WHITESPACE = setOf<Char>(' ', '\t', '\n') | |
16 private var oldStates = mutableListOf<State>() | |
17 private var state: State = ::space | |
18 private var accum = mutableListOf<String>() | |
19 private var current = StringBuilder() | |
20 | |
21 val complete: Boolean | |
22 get() = state == ::space || state == ::nonspace | |
23 | |
24 // Feeds more input into this tokenizer | |
25 fun feed(input: String) : Unit { | |
26 for (ch in input) { | |
27 while (state(ch)) | |
28 ; | |
29 } | |
30 } | |
31 | |
32 fun split(): Iterable<String> { | |
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 | 36 accum.add(current.toString()) |
37 current.clear() | |
38 } | |
39 return accum | |
40 } else { | |
41 throw IllegalStateException("incomplete quoted expression") | |
42 } | |
43 } | |
44 | |
45 // State transitions | |
46 | |
47 private fun pushState(newState: State): Unit { | |
48 oldStates.add(state) | |
49 state = newState | |
50 } | |
51 | |
52 private fun popState(): Unit { | |
53 state = oldStates.removeLast() | |
54 } | |
55 | |
56 private fun lastState(): State = oldStates.last() | |
57 | |
58 // States. A state is represented by a function that accepts the | |
59 // character currently being processed, and returns whether it should | |
60 // immediately transition to the next state without reading a new | |
61 // character. | |
62 | |
63 private fun space(ch: Char): Boolean = | |
64 when (ch) { | |
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 | 69 else -> { pushState(::nonspace); true } |
70 } | |
71 | |
72 private fun nonspace(ch: Char): Boolean = | |
73 when (ch) { | |
74 in WHITESPACE -> { | |
75 accum.add(current.toString()) | |
76 current.clear() | |
77 popState() | |
78 false | |
79 } | |
80 '\'' -> { | |
81 pushState(::inSingle) | |
82 false | |
83 } | |
84 '"' -> { | |
85 pushState(::inDouble) | |
86 false | |
87 } | |
88 '\\' -> { | |
89 pushState(::backslash) | |
90 false | |
91 } | |
92 else -> { | |
93 current.append(ch) | |
94 false | |
95 } | |
96 } | |
97 | |
98 private fun inSingle(ch: Char): Boolean = | |
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 | 101 else -> { current.append(ch); false } |
102 } | |
103 | |
104 private fun inDouble(ch: Char): Boolean = | |
105 when (ch) { | |
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 | 108 else -> { current.append(ch); false } |
109 } | |
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 | 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 | 119 } |
120 } |