diff --git a/contentstream/scanner_test.go b/contentstream/scanner_test.go index 4d0f9cd..da7b59f 100644 --- a/contentstream/scanner_test.go +++ b/contentstream/scanner_test.go @@ -345,3 +345,47 @@ func TestReadDictValueErrors(t *testing.T) { }) } } + +// Every readArray element kind, including the ones TJ rarely uses (bool, null, +// name, nested array/dict). +func TestArrayMixedElementKinds(t *testing.T) { + ops := collect(t, "[42 3.14 /Nm (s) <41> true false null [1 2] << /K 9 >>] TJ") + if len(ops) != 1 || ops[0].Operator != "TJ" { + t.Fatalf("want one TJ op, got %+v", ops) + } + got := ops[0].Operands[0].Array + wantKinds := []contentstream.Kind{ + contentstream.KindNumber, contentstream.KindNumber, contentstream.KindName, + contentstream.KindString, contentstream.KindString, contentstream.KindBool, + contentstream.KindBool, contentstream.KindNull, contentstream.KindArray, + contentstream.KindDict, + } + if len(got) != len(wantKinds) { + t.Fatalf("array len = %d, want %d", len(got), len(wantKinds)) + } + for i, want := range wantKinds { + if got[i].Kind != want { + t.Errorf("element %d Kind = %v, want %v", i, got[i].Kind, want) + } + } +} + +func TestArrayElementErrors(t *testing.T) { + for _, src := range []string{"[ foo ] n", "[1 2"} { // bad keyword in array; unterminated + t.Run(src, func(t *testing.T) { + if _, err := contentstream.New([]byte(src)).Next(); err == nil { + t.Fatal("expected an error, got nil") + } + }) + } +} + +// Int must report ok=false on int64 overflow (not silently wrap) and for non-numbers. +func TestOperandIntEdgeCases(t *testing.T) { + if _, ok := collect(t, "/Name n")[0].Operands[0].Int(); ok { + t.Error("name operand yielded an int") + } + if _, ok := collect(t, "99999999999999999999999999 n")[0].Operands[0].Int(); ok { + t.Error("overflowing integer literal yielded an int") + } +} diff --git a/internal/lex/lex_test.go b/internal/lex/lex_test.go index b587324..0d551ab 100644 --- a/internal/lex/lex_test.go +++ b/internal/lex/lex_test.go @@ -219,3 +219,67 @@ func TestLexerAccessors(t *testing.T) { t.Errorf("Remaining = %q, want world", got) } } + +// Literal-string escapes/newlines (§7.3.4.2) not already in TestLexerLiteralString. +func TestLexerLiteralStringEscapes(t *testing.T) { + cases := []struct { + name, in, want string + }{ + {"named_escapes", "(\\t\\b\\f\\r)", "\t\b\f\r"}, + {"cr_line_continuation", "(a\\\rb)", "ab"}, + {"crlf_line_continuation", "(a\\\r\nb)", "ab"}, + {"unknown_escape_keeps_char", "(\\q)", "q"}, + {"bare_cr_to_lf", "(a\rb)", "a\nb"}, + {"bare_crlf_to_lf", "(a\r\nb)", "a\nb"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + tok, err := New([]byte(c.in)).Next() + if err != nil { + t.Fatalf("Next: %v", err) + } + if tok.Kind != LitString { + t.Fatalf("kind = %v, want LitString", tok.Kind) + } + if string(tok.Bytes) != c.want { + t.Errorf("got %q, want %q", tok.Bytes, c.want) + } + }) + } +} + +func TestLexerLiteralStringErrors(t *testing.T) { + for _, in := range []string{"(\\", "(abc"} { // trailing backslash; unterminated + if _, err := New([]byte(in)).Next(); err == nil { + t.Errorf("%q: expected an error, got nil", in) + } + } +} + +// A misclassified delimiter byte (§7.2.2) would break tokenisation, so pin the set. +func TestIsDelimiter(t *testing.T) { + for _, c := range []byte("()<>[]{}/%") { + if !IsDelimiter(c) { + t.Errorf("IsDelimiter(%q) = false, want true", c) + } + } + for _, c := range []byte("aZ0 \t.\\") { + if IsDelimiter(c) { + t.Errorf("IsDelimiter(%q) = true, want false", c) + } + } +} + +func TestHexDigit(t *testing.T) { + valid := map[byte]int{'0': 0, '9': 9, 'a': 10, 'f': 15, 'A': 10, 'F': 15} + for c, want := range valid { + if v, ok := hexDigit(c); !ok || v != want { + t.Errorf("hexDigit(%q) = (%d, %v), want (%d, true)", c, v, ok, want) + } + } + for _, c := range []byte("gG/ \x00") { + if _, ok := hexDigit(c); ok { + t.Errorf("hexDigit(%q) = ok, want not-ok", c) + } + } +} diff --git a/xref_test.go b/xref_test.go index 26f3d9c..435d536 100644 --- a/xref_test.go +++ b/xref_test.go @@ -440,3 +440,22 @@ func TestSkipEOL(t *testing.T) { }) } } + +func TestIndexEOL(t *testing.T) { + cases := []struct { + buf string + pos int + want int + }{ + {"ab\ncd", 0, 2}, + {"ab\rcd", 0, 2}, + {"abcd", 0, -1}, + {"a\nb", 2, -1}, + {"", 0, -1}, + } + for _, tc := range cases { + if got := indexEOL([]byte(tc.buf), tc.pos); got != tc.want { + t.Errorf("indexEOL(%q, %d) = %d, want %d", tc.buf, tc.pos, got, tc.want) + } + } +}