diff --git a/unvis.go b/unvis.go index 9d59b9e..f8dd6b1 100644 --- a/unvis.go +++ b/unvis.go @@ -63,9 +63,12 @@ func newParser(input string, flag VisFlag) *unvisParser { // ::= ()* // ::= ("\" ) | ("%" ) | // ::= any rune -// ::= ("x" ) | ("M") | | -// ::= [0-9a-f] [0-9a-f] +// ::= ("x" ) | ("M" ) | ("^" | +// ::= ("-" ) | ("^" ) +// ::= any rune +// ::= "?" | any rune // ::= "\" | "n" | "r" | "b" | "a" | "v" | "t" | "f" +// ::= [0-9a-f] [0-9a-f] // ::= [0-7] ([0-7] ([0-7])?)? func unvisPlainRune(p *unvisParser) ([]byte, error) { @@ -155,6 +158,57 @@ func unvisEscapeDigits(p *unvisParser, base int, force bool) ([]byte, error) { return []byte{char}, nil } +func unvisEscapeCtrl(p *unvisParser, mask byte) ([]byte, error) { + ch, err := p.Peek() + if err != nil { + return nil, fmt.Errorf("escape ctrl: %s", err) + } + if ch > unicode.MaxLatin1 { + return nil, fmt.Errorf("escape ctrl: code %q outside latin-1 encoding", ch) + } + + char := byte(ch) & 0x1f + if ch == '?' { + char = 0x7f + } + + p.Next() + return []byte{mask | char}, nil +} + +func unvisEscapeMeta(p *unvisParser) ([]byte, error) { + ch, err := p.Peek() + if err != nil { + return nil, fmt.Errorf("escape meta: %s", err) + } + + mask := byte(0x80) + + switch ch { + case '^': + // The same as "\^..." except we apply a mask. + p.Next() + return unvisEscapeCtrl(p, mask) + + case '-': + p.Next() + + ch, err := p.Peek() + if err != nil { + return nil, fmt.Errorf("escape meta1: %s", err) + } + if ch > unicode.MaxLatin1 { + return nil, fmt.Errorf("escape meta1: code %q outside latin-1 encoding", ch) + } + + // Add mask to character. + p.Next() + return []byte{mask | byte(ch)}, nil + } + + return nil, fmt.Errorf("escape meta: unknown escape char: %s", err) +} + func unvisEscapeSequence(p *unvisParser) ([]byte, error) { ch, err := p.Peek() if err != nil { @@ -173,10 +227,13 @@ func unvisEscapeSequence(p *unvisParser) ([]byte, error) { p.Next() return unvisEscapeDigits(p, 16, true) - case 'M': - // TODO case '^': - // TODO + p.Next() + return unvisEscapeCtrl(p, 0x00) + + case 'M': + p.Next() + return unvisEscapeMeta(p) default: return unvisEscapeCStyle(p) diff --git a/unvis_test.go b/unvis_test.go index 66d60eb..44e0a1a 100644 --- a/unvis_test.go +++ b/unvis_test.go @@ -35,6 +35,58 @@ func TestUnvisError(t *testing.T) { } } +func TestUnvisCStyleEscape(t *testing.T) { + for _, test := range []struct { + input string + expected string + }{ + {"", ""}, + {"\\n\\v\\t\\s", "\n\v\t "}, + {"\\\\n\\tt", "\\n\tt"}, + {"\\b", "\b"}, + {"\\r\\b\\n", "\r\b\n"}, + {"\\a\\a\\b", "\x07\x07\b"}, + {"\\f\\s\\E", "\f \x1b"}, + // Hidden markers. They actually aren't generated by vis(3) but for + // some reason, they're supported... + {"test\\\ning", "testing"}, + {"test\\$\\$ing", "testing"}, + } { + got, err := Unvis(test.input, DefaultVisFlags) + if err != nil { + t.Errorf("unexpected error doing unvis(%q): %q", test.input, err) + continue + } + if got != test.expected { + t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got) + } + } +} + +func TestUnvisMetaEscape(t *testing.T) { + for _, test := range []struct { + input string + expected string + }{ + {"", ""}, + {"\\M^ ?\\^ ", "\x80?\x00"}, + {"\\M- ?\\^?", "\xa0?\x7f"}, + {"\\M-x butterfly\\M^?", "\xf8 butterfly\xff"}, + {"\\M^X steady-hand \\^& needle", "\x98 steady-hand \x06 needle"}, + // TODO: Add some more of these tests, but I need to have some + // secondary source to verify these outputs properly. + } { + got, err := Unvis(test.input, DefaultVisFlags) + if err != nil { + t.Errorf("unexpected error doing unvis(%q): %q", test.input, err) + continue + } + if got != test.expected { + t.Errorf("expected unvis(%q) = %q, got %q", test.input, test.expected, got) + } + } +} + func TestUnvisOctalEscape(t *testing.T) { for _, test := range []struct { input string