iopshell/internal/textmutate/strmapper.go

154 lines
3.4 KiB
Go

/*
Copyright (c) 2021 Rasmus Moorats (nns)
This file is part of iopshell.
iopshell is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
iopshell is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with iopshell. If not, see <https://www.gnu.org/licenses/>.
*/
// If you do not wish to inflict pain upon yourself, turn back now.
package textmutate
import (
"strings"
)
type parser struct {
Input string
SChars []rune
Cursor int
escaped []int
}
// Is the character specified at `index` escaped?
func (p *parser) isEscaped(index int) bool {
for _, i := range p.escaped {
if i == index {
return true
}
}
return false
}
// Preproccess escaped characters and populate the
// `escaped` slice
func (p *parser) processEscapes() {
cursor := 0
for cursor < len(p.Input) {
if p.Input[cursor] == '\\' {
p.escaped = append(p.escaped, cursor+1)
cursor++
}
cursor++
}
}
// Simply returns whether the current char (or char + offset) is
// a special char (one specified in SChars)
func (p *parser) specialChar(offset ...int) bool {
cursor := p.Cursor
if len(offset) > 0 {
cursor += offset[0]
}
for _, char := range p.SChars {
if rune(p.Input[cursor]) == char {
return true
}
}
return false
}
// Consume a word until a special char and return it
func (p *parser) word() string {
var word strings.Builder
for p.Cursor < len(p.Input) {
if p.Input[p.Cursor] == '\\' && !p.isEscaped(p.Cursor) {
p.Cursor++
continue
}
if p.isEscaped(p.Cursor) {
switch c := (p.Input[p.Cursor]); c {
case 'a':
word.WriteByte('\a')
case 'b':
word.WriteByte('\b')
case 'f':
word.WriteByte('\f')
case 'n':
word.WriteByte('\n')
case 'r':
word.WriteByte('\r')
case 't':
word.WriteByte('\t')
case 'v':
word.WriteByte('\v')
case ':', ';', ',', '\\':
word.WriteByte(c)
}
} else if p.specialChar() {
p.Cursor++
break
} else {
word.WriteByte(p.Input[p.Cursor])
}
p.Cursor++
}
return word.String()
}
// Return what the next special char is
func (p *parser) peekChar() rune {
for i, char := range p.Input[p.Cursor:] {
if p.specialChar(i) && !p.isEscaped(i+p.Cursor) {
return char
}
}
return 0
}
// StrToMap takes an input string and creates a map out of it, for use
// in json requests. For example,
// "key1:key1-1:value1-1,key1-2:value1-2;key2:value2" returns
// {"key1": {"key1-1": "value1-1", "key1-2": "value1-2"}, "key2": "value2"}
func StrToMap(input string) (map[string]interface{}, int) {
out := make(map[string]interface{})
p := parser{Input: input,
SChars: []rune{':', ';', ','}}
p.processEscapes()
for key := p.word(); key != ""; key = p.word() {
char := p.peekChar()
switch char {
case ':':
newGen, cur := StrToMap(p.Input[p.Cursor:])
out[key] = newGen
p.Cursor += cur
case ',':
out[key] = p.word()
case ';':
out[key] = p.word()
return out, p.Cursor
default:
out[key] = p.word()
}
}
return out, p.Cursor
}