From b378b8dd6310407b978c37c687ae96ac4381e8e8 Mon Sep 17 00:00:00 2001 From: Erik Rasmussen Date: Sat, 18 Jan 2025 14:56:50 -0600 Subject: [PATCH] Scanner refactor (#8) * Refactor parser and scanner for cleaner code Significant changes include: - Simplified the NewParser function in parser.go by removing unnecessary steps and adding a next() call. - Refactored the Scan function in scanner.go to return position, token, and literal directly instead of setting them as properties on the Scanner struct. - Updated tests in scanner_test.go to reflect these changes. * Enhanced error handling in parser and scanner The parser's error matching has been improved to check for substrings, providing more flexibility. The scanner now advances to the next token after identifying an identifier, ensuring proper sequence progression. Adjustments were also made to the scanner tests: literal comparison no longer trims spaces from input, and position calculation now accounts for an extra character. Lastly, a change was made in how scanning errors are handled during testing. * Added PositionFor function and corresponding tests A new function, PositionFor, has been added to the token package. This function returns the Position value for a given file position. If the provided position is out of bounds, it's adjusted to match File.Offset behavior. Alongside this addition, a test suite has also been created to ensure that calling PositionFor yields equivalent results as calling file.PositionFor(p, false). * Refactor scanner initialization and add position method The scanner's initialization has been refactored for better readability. The creation of the bufio.Scanner and setting its split function is now done before creating the Scanner struct. A new method, Position, has been added to the Scanner which returns a token's position in a file. In addition, changes have been made to the scanner tests. The test case for scanning an identifier followed by a token has been replaced with one for scanning an identifier followed by whitespace. Additional assertions have also been added to ensure that after scanning an identifier or special character, the next scan returns EOF (end of file). * Enhanced scanner test coverage Added new tests to the scanner_test.go file. These include a test for the Position function, ensuring it's equivalent to calling file.PositionFor(p, false). Also added checks for token position and newline tokens in existing tests. Corrected an issue with scanning newline followed by token. * Refactored scanner logic and expanded tests The scanning logic has been refactored for better readability and efficiency. The 'done' flag check is now performed after the offset update, ensuring accurate line addition in case of newline characters. In addition, the test coverage for the scanner functionality has been significantly expanded. New test cases have been added to verify correct token scanning following a newline character, with various input scenarios considered. This ensures robustness of the scanner across different use-cases. * Refactor scanner tests for readability Significant changes include: - Removed redundant test cases in the scanner_test.go file - Simplified token scanning tests by focusing on space-separated tokens only - This results in a cleaner, more maintainable test suite --- parser.go | 21 +-- parser_test.go | 4 +- scanner.go | 86 +++++---- scanner_test.go | 392 +++++++++++++++++++++-------------------- token/position.go | 8 + token/position_test.go | 35 ++++ 6 files changed, 299 insertions(+), 247 deletions(-) create mode 100644 token/position_test.go diff --git a/parser.go b/parser.go index 3694bb3..eb94e9d 100644 --- a/parser.go +++ b/parser.go @@ -19,15 +19,13 @@ type Parser struct { } func NewParser(r io.Reader, file *token.File) *Parser { - s := NewScanner(r, file) - s.Scan() // TODO: Cleaner priming - - return &Parser{ - s: s, - file: file, // TODO: Same file? Different file? - tok: s.Token(), - lit: s.Literal(), + p := &Parser{ + s: NewScanner(r, file), + file: file, } + p.next() + + return p } func (p *Parser) ParseFile() (*ast.File, error) { @@ -56,12 +54,7 @@ func (p *Parser) error(pos token.Pos, msg string) { } func (p *Parser) next() { - if p.s.Scan() { - // TODO: p.pos - p.tok, p.lit = p.s.Token(), p.s.Literal() - } else { - p.tok = token.EOF - } + p.pos, p.tok, p.lit = p.s.Scan() } func (p *Parser) parseFile() *ast.File { diff --git a/parser_test.go b/parser_test.go index 45aa5d7..64a4fea 100644 --- a/parser_test.go +++ b/parser_test.go @@ -35,6 +35,8 @@ var _ = Describe("Parser", func() { _, err := p.ParseFile() - Expect(err).To(MatchError("expected 'IDENT'")) + Expect(err).To(MatchError( + ContainSubstring("expected 'IDENT'"), + )) }) }) diff --git a/scanner.go b/scanner.go index 367017b..eb9d654 100644 --- a/scanner.go +++ b/scanner.go @@ -21,11 +21,13 @@ type Scanner struct { } func NewScanner(r io.Reader, file *token.File) *Scanner { + scanner := bufio.NewScanner(r) + scanner.Split(ScanTokens) + s := &Scanner{ - s: bufio.NewScanner(r), + s: scanner, file: file, } - s.s.Split(ScanTokens) s.next() return s @@ -35,97 +37,93 @@ func (s Scanner) Err() error { return s.s.Err() } -func (s Scanner) Token() token.Token { - return s.tok -} - -func (s Scanner) Literal() string { - return s.lit +func (s Scanner) Position(pos token.Pos) token.Position { + return token.PositionFor(s.file, pos) } -func (s Scanner) Pos() token.Pos { - return s.file.Pos(s.offset) -} - -func (s *Scanner) Scan() bool { +func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) { if s.done { - s.tok = token.EOF - return false + pos = s.file.Pos(s.offset) + tok = token.EOF + return } + s.skipWhitespace() + + // current token start + pos = s.file.Pos(s.offset) var atNewline bool - s.skipWhitespace() switch txt := s.s.Text(); { case token.IsIdentifier(txt): - s.lit = txt + lit = txt + s.next() if len(txt) > 1 { - s.tok = token.Lookup(txt) + tok = token.Lookup(txt) } else { - s.tok = token.IDENT + tok = token.IDENT } default: + s.next() switch txt { case "=": - s.tok = token.RECURSIVE_ASSIGN + tok = token.RECURSIVE_ASSIGN case ":=": - s.tok = token.SIMPLE_ASSIGN + tok = token.SIMPLE_ASSIGN case "::=": - s.tok = token.POSIX_ASSIGN + tok = token.POSIX_ASSIGN case ":::=": - s.tok = token.IMMEDIATE_ASSIGN + tok = token.IMMEDIATE_ASSIGN case "?=": - s.tok = token.IFNDEF_ASSIGN + tok = token.IFNDEF_ASSIGN case "!=": - s.tok = token.SHELL_ASSIGN + tok = token.SHELL_ASSIGN case ",": - s.tok = token.COMMA + tok = token.COMMA case "\n": atNewline = true - s.tok = token.NEWLINE + tok = token.NEWLINE case "\t": - s.tok = token.TAB + tok = token.TAB case "(": - s.tok = token.LPAREN + tok = token.LPAREN case ")": - s.tok = token.RPAREN + tok = token.RPAREN case "{": - s.tok = token.LBRACE + tok = token.LBRACE case "}": - s.tok = token.RBRACE + tok = token.RBRACE case "$": - s.tok = token.DOLLAR + tok = token.DOLLAR case ":": - s.tok = token.COLON + tok = token.COLON case ";": - s.tok = token.SEMI + tok = token.SEMI case "|": - s.tok = token.PIPE + tok = token.PIPE case "#": // TODO // s.lit = s.scanComment() - s.tok = token.COMMENT + tok = token.COMMENT default: - s.tok = token.UNSUPPORTED + tok = token.UNSUPPORTED s.lit = txt } } - s.next() if atNewline && s.done { - s.tok = token.EOF - return false - } else { - return true + tok = token.EOF } + + return } func (s *Scanner) next() { - s.done = !s.s.Scan() s.offset = s.rdOffset if bytes.ContainsRune(s.s.Bytes(), '\n') { s.file.AddLine(s.offset) } + s.done = !s.s.Scan() s.rdOffset += len(s.s.Bytes()) } diff --git a/scanner_test.go b/scanner_test.go index 0efb320..d3ab0b2 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -5,6 +5,7 @@ import ( gotoken "go/token" "math" "strings" + "testing/quick" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -21,6 +22,23 @@ var _ = Describe("Scanner", func() { file = gotoken.NewFileSet().AddFile("test", 1, math.MaxInt-2) }) + Describe("Position", func() { + It("should be equivalent to calling file.PositionFor(p, false)", func() { + s := make.NewScanner(&bytes.Buffer{}, file) + + err := quick.Check(func(p int) bool { + pos := token.Pos(p) + + expected := file.PositionFor(pos, false) + actual := s.Position(pos) + + return actual == expected + }, nil) + + Expect(err).NotTo(HaveOccurred()) + }) + }) + DescribeTable("Scan identifier", Entry(nil, "ident"), Entry(nil, "./file/path"), @@ -35,11 +53,14 @@ var _ = Describe("Scanner", func() { buf := bytes.NewBufferString(input) s := make.NewScanner(buf, file) - Expect(s.Scan()).To(BeTrueBecause("scanned a token")) - Expect(s.Token()).To(Equal(token.IDENT)) - Expect(s.Literal()).To(Equal(strings.TrimSpace(input))) - Expect(s.Scan()).To(BeFalseBecause("at EOF")) - Expect(s.Token()).To(Equal(token.EOF)) + pos, tok, lit := s.Scan() + Expect(tok).To(Equal(token.IDENT)) + Expect(lit).To(Equal(input)) + Expect(pos).To(Equal(token.Pos(1))) + + pos, tok, lit = s.Scan() + Expect(tok).To(Equal(token.EOF)) + Expect(pos).To(Equal(token.Pos(len(input) + 1))) }, ) @@ -57,17 +78,31 @@ var _ = Describe("Scanner", func() { buf := bytes.NewBufferString(input) s := make.NewScanner(buf, file) - Expect(s.Scan()).To(BeTrueBecause("scanned a token")) - Expect(s.Token()).To(Equal(token.IDENT)) - Expect(s.Literal()).To(Equal(strings.TrimSpace(input))) - Expect(s.Scan()).To(BeFalseBecause("at EOF")) - Expect(s.Token()).To(Equal(token.EOF)) + pos, tok, lit := s.Scan() + Expect(tok).To(Equal(token.IDENT)) + Expect(lit).To(Equal(strings.TrimSpace(input))) + Expect(pos).To(Equal(token.Pos(1))) + Expect(s.Position(pos)).To(Equal(token.Position{ + Filename: file.Name(), + Offset: 0, + Line: 1, + Column: 1, + })) + + pos, tok, lit = s.Scan() + Expect(tok).To(Equal(token.EOF)) + Expect(pos).To(Equal(token.Pos(len(input)))) + Expect(s.Position(pos)).To(Equal(token.Position{ + Filename: file.Name(), + Offset: len(input) - 1, + Line: 1, + Column: len(input), + })) }, ) - DescribeTable("Scan ident followed by token", + DescribeTable("Scan ident followed by whitespace", Entry(nil, "ident $"), - Entry(nil, "ident:"), Entry(nil, "ident :"), Entry(nil, "ident ;"), Entry(nil, "ident |"), @@ -82,16 +117,19 @@ var _ = Describe("Scanner", func() { Entry(nil, "ident {"), Entry(nil, "ident }"), Entry(nil, "ident ,"), - Entry(nil, "ident\n\t"), func(input string) { buf := bytes.NewBufferString(input) s := make.NewScanner(buf, file) - ok := s.Scan() + pos, tok, lit := s.Scan() + Expect(tok).To(Equal(token.IDENT)) + Expect(lit).To(Equal("ident")) + Expect(pos).To(Equal(token.Pos(1))) - Expect(s.Token()).To(Equal(token.IDENT)) - Expect(s.Literal()).To(Equal("ident")) - Expect(ok).To(BeTrueBecause("scanned a token")) + pos, tok, lit = s.Scan() + // File base + Length of the identifier + whitespace + Expect(pos).To(Equal(token.Pos(7))) + Expect(tok).NotTo(Equal(token.IDENT)) }, ) @@ -112,14 +150,17 @@ var _ = Describe("Scanner", func() { Entry(nil, "}", token.RBRACE), Entry(nil, ",", token.COMMA), Entry(nil, "\t", token.TAB), + Entry(nil, "\n\n", token.NEWLINE), func(input string, expected token.Token) { buf := bytes.NewBufferString(input) s := make.NewScanner(buf, file) - ok := s.Scan() + pos, tok, _ := s.Scan() + Expect(tok).To(Equal(expected)) + Expect(pos).To(Equal(token.Pos(1))) - Expect(s.Token()).To(Equal(expected)) - Expect(ok).To(BeTrueBecause("scanned a token")) + pos, tok, _ = s.Scan() + Expect(tok).To(Equal(token.EOF)) }, ) @@ -129,185 +170,160 @@ var _ = Describe("Scanner", func() { buf := bytes.NewBufferString(input) s := make.NewScanner(buf, file) - ok := s.Scan() + pos, tok, _ := s.Scan() - Expect(s.Token()).To(Equal(expected)) - Expect(ok).To(BeTrueBecause("scanned a token")) + Expect(tok).To(Equal(expected)) + Expect(pos).To(Equal(token.Pos(1))) }, ) - It("should scan newline followed by token", func() { - buf := bytes.NewBufferString("\n ident") - s := make.NewScanner(buf, file) + DescribeTable("should scan newline followed by token", + Entry(nil, "\nident"), + Entry(nil, "\n,"), + Entry(nil, "\n$"), + Entry(nil, "\n;"), + Entry(nil, "\n:"), + Entry(nil, "\n:="), + Entry(nil, "\n::="), + Entry(nil, "\n:::="), + Entry(nil, "\n="), + Entry(nil, "\n?="), + Entry(nil, "\n!="), + Entry(nil, "\n|"), + Entry(nil, "\n\t"), + Entry(nil, "\n{"), + Entry(nil, "\n}"), + Entry(nil, "\n("), + Entry(nil, "\n)"), + func(input string) { + buf := bytes.NewBufferString(input) + s := make.NewScanner(buf, file) - Expect(s.Scan()).To(BeTrue()) - Expect(s.Token()).To(Equal(token.NEWLINE)) - }) + pos, tok, _ := s.Scan() + Expect(tok).To(Equal(token.NEWLINE)) + Expect(pos).To(Equal(token.Pos(1))) + Expect(s.Position(pos)).To(Equal(token.Position{ + Filename: file.Name(), + Offset: 0, + Line: 1, + Column: 1, + })) - Describe("Pos", func() { - DescribeTable("Starting token", - Entry(nil, "$", 2), - Entry(nil, ":", 2), - Entry(nil, ";", 2), - Entry(nil, "|", 2), - Entry(nil, "=", 2), - Entry(nil, ":=", 3), - Entry(nil, "::=", 4), - Entry(nil, ":::=", 5), - Entry(nil, "?=", 3), - Entry(nil, "!=", 3), - Entry(nil, "(", 2), - Entry(nil, ")", 2), - Entry(nil, "{", 2), - Entry(nil, "}", 2), - Entry(nil, ",", 2), - Entry(nil, "\t", 2), - Entry(nil, "identifier", 11), - Entry(nil, "$ foo", 2), - Entry(nil, ": foo", 2), - Entry(nil, "; foo", 2), - Entry(nil, "| foo", 2), - Entry(nil, "= foo", 2), - Entry(nil, ":= foo", 3), - Entry(nil, "::= foo", 4), - Entry(nil, ":::= foo", 5), - Entry(nil, "?= foo", 3), - Entry(nil, "!= foo", 3), - Entry(nil, "( foo", 2), - Entry(nil, ") foo", 2), - Entry(nil, "{ foo", 2), - Entry(nil, "} foo", 2), - Entry(nil, ", foo", 2), - Entry(nil, "\t foo", 2), - Entry(nil, "identifier foo", 11), - func(input string, expected int) { - buf := bytes.NewBufferString(input) - s := make.NewScanner(buf, file) - - Expect(s.Scan()).To(BeTrueBecause("scanned a token")) - Expect(s.Pos()).To(Equal(token.Pos(expected))) - }, - ) - - DescribeTable("Second token", - Entry(nil, "$ foo", 6), - Entry(nil, ": foo", 6), - Entry(nil, "; foo", 6), - Entry(nil, "| foo", 6), - Entry(nil, "= foo", 6), - Entry(nil, ":= foo", 7), - Entry(nil, "::= foo", 8), - Entry(nil, ":::= foo", 9), - Entry(nil, "?= foo", 7), - Entry(nil, "!= foo", 7), - Entry(nil, "( foo", 6), - Entry(nil, ") foo", 6), - Entry(nil, "{ foo", 6), - Entry(nil, "} foo", 6), - Entry(nil, ", foo", 6), - Entry(nil, "\t foo", 6), - Entry(nil, "identifier foo", 15), - Entry(nil, "$ foo bar", 6), - Entry(nil, ": foo bar", 6), - Entry(nil, "; foo bar", 6), - Entry(nil, "| foo bar", 6), - Entry(nil, "= foo bar", 6), - Entry(nil, ":= foo bar", 7), - Entry(nil, "::= foo bar", 8), - Entry(nil, ":::= foo bar", 9), - Entry(nil, "?= foo bar", 7), - Entry(nil, "!= foo bar", 7), - Entry(nil, "( foo bar", 6), - Entry(nil, ") foo bar", 6), - Entry(nil, "{ foo bar", 6), - Entry(nil, "} foo bar", 6), - Entry(nil, ", foo bar", 6), - Entry(nil, "\t foo bar", 6), - Entry(nil, "identifier foo bar", 15), - func(input string, expected int) { - buf := bytes.NewBufferString(input) - s := make.NewScanner(buf, file) - - Expect(s.Scan()).To(BeTrueBecause("scanned a token")) - Expect(s.Scan()).To(BeTrueBecause("scanned another token")) - Expect(s.Pos()).To(Equal(token.Pos(expected))) - }, - ) - - DescribeTable("Scan newline", - Entry(nil, "$\nfoo", 3), - Entry(nil, ":\nfoo", 3), - Entry(nil, ";\nfoo", 3), - Entry(nil, "|\nfoo", 3), - Entry(nil, "=\nfoo", 3), - Entry(nil, ":=\nfoo", 4), - Entry(nil, "::=\nfoo", 5), - Entry(nil, ":::=\nfoo", 6), - Entry(nil, "?=\nfoo", 4), - Entry(nil, "!=\nfoo", 4), - Entry(nil, "(\nfoo", 3), - Entry(nil, ")\nfoo", 3), - Entry(nil, "{\nfoo", 3), - Entry(nil, "}\nfoo", 3), - Entry(nil, ",\nfoo", 3), - Entry(nil, "\t\nfoo", 3), - Entry(nil, "identifier\nfoo", 12), - func(input string, expected int) { - buf := bytes.NewBufferString(input) - s := make.NewScanner(buf, file) - - Expect(s.Scan()).To(BeTrueBecause("scanned first token")) - Expect(s.Scan()).To(BeTrueBecause("scanned newline token")) - Expect(s.Pos()).To(Equal(token.Pos(expected))) - Expect(file.PositionFor(s.Pos(), false)).To(Equal(token.Position{ - Filename: file.Name(), - Offset: expected - file.Base(), - Line: 2, - Column: 2, - })) - }, - ) - - DescribeTable("Scan final newline", - Entry(nil, "$\n", 3), - Entry(nil, ":\n", 3), - Entry(nil, ";\n", 3), - Entry(nil, "|\n", 3), - Entry(nil, "=\n", 3), - Entry(nil, ":=\n", 4), - Entry(nil, "::=\n", 5), - Entry(nil, ":::=\n", 6), - Entry(nil, "?=\n", 4), - Entry(nil, "!=\n", 4), - Entry(nil, "(\n", 3), - Entry(nil, ")\n", 3), - Entry(nil, "{\n", 3), - Entry(nil, "}\n", 3), - Entry(nil, ",\n", 3), - Entry(nil, "\t\n", 3), - Entry(nil, "identifier\n", 12), - func(input string, expected int) { - buf := bytes.NewBufferString(input) - s := make.NewScanner(buf, file) - - Expect(s.Scan()).To(BeTrueBecause("scanned a token")) - Expect(s.Scan()).To(BeFalseBecause("scanned final newline")) - Expect(s.Pos()).To(Equal(token.Pos(expected))) - Expect(file.PositionFor(s.Pos(), false)).To(Equal(token.Position{ - Filename: file.Name(), - Offset: expected - file.Base(), - Line: 2, - Column: 2, - })) - }, - ) - }) + pos, tok, _ = s.Scan() + Expect(pos).To(Equal(token.Pos(2))) + Expect(s.Position(pos)).To(Equal(token.Position{ + Filename: file.Name(), + Offset: 1, + Line: 2, + Column: 1, + })) + }, + ) + + DescribeTable("space separated tokens", + Entry(nil, "$ foo", 3), + Entry(nil, ": foo", 3), + Entry(nil, "; foo", 3), + Entry(nil, "| foo", 3), + Entry(nil, "= foo", 3), + Entry(nil, ":= foo", 4), + Entry(nil, "::= foo", 5), + Entry(nil, ":::= foo", 6), + Entry(nil, "?= foo", 4), + Entry(nil, "!= foo", 4), + Entry(nil, "( foo", 3), + Entry(nil, ") foo", 3), + Entry(nil, "{ foo", 3), + Entry(nil, "} foo", 3), + Entry(nil, ", foo", 3), + Entry(nil, "\t foo", 3), + Entry(nil, "identifier foo", 12), + Entry(nil, "$ foo bar", 3), + Entry(nil, ": foo bar", 3), + Entry(nil, "; foo bar", 3), + Entry(nil, "| foo bar", 3), + Entry(nil, "= foo bar", 3), + Entry(nil, ":= foo bar", 4), + Entry(nil, "::= foo bar", 5), + Entry(nil, ":::= foo bar", 6), + Entry(nil, "?= foo bar", 4), + Entry(nil, "!= foo bar", 4), + Entry(nil, "( foo bar", 3), + Entry(nil, ") foo bar", 3), + Entry(nil, "{ foo bar", 3), + Entry(nil, "} foo bar", 3), + Entry(nil, ", foo bar", 3), + Entry(nil, "\t foo bar", 3), + Entry(nil, "identifier foo bar", 12), + func(input string, expected int) { + buf := bytes.NewBufferString(input) + s := make.NewScanner(buf, file) + + _, _, _ = s.Scan() + pos, tok, lit := s.Scan() + Expect(tok).To(Equal(token.IDENT)) + Expect(pos).To(Equal(token.Pos(expected))) + Expect(lit).To(Equal("foo")) + Expect(s.Position(pos)).To(Equal(token.Position{ + Filename: file.Name(), + Offset: expected - file.Base(), + Line: 1, + Column: expected, + })) + }, + ) + + DescribeTable("newline separated tokens", + Entry(nil, "$\nfoo", 2), + Entry(nil, ":\nfoo", 2), + Entry(nil, ";\nfoo", 2), + Entry(nil, "|\nfoo", 2), + Entry(nil, "=\nfoo", 2), + Entry(nil, ":=\nfoo", 3), + Entry(nil, "::=\nfoo", 4), + Entry(nil, ":::=\nfoo", 5), + Entry(nil, "?=\nfoo", 3), + Entry(nil, "!=\nfoo", 3), + Entry(nil, "(\nfoo", 2), + Entry(nil, ")\nfoo", 2), + Entry(nil, "{\nfoo", 2), + Entry(nil, "}\nfoo", 2), + Entry(nil, ",\nfoo", 2), + Entry(nil, "\t\nfoo", 2), + Entry(nil, "identifier\nfoo", 11), + func(input string, nlPos int) { + buf := bytes.NewBufferString(input) + s := make.NewScanner(buf, file) + + _, _, _ = s.Scan() + pos, tok, _ := s.Scan() + Expect(tok).To(Equal(token.NEWLINE)) + Expect(pos).To(Equal(token.Pos(nlPos))) + Expect(s.Position(pos)).To(Equal(token.Position{ + Filename: file.Name(), + Offset: nlPos - file.Base(), + Line: 1, + Column: nlPos, + })) + + pos, tok, lit := s.Scan() + Expect(tok).To(Equal(token.IDENT)) + Expect(pos).To(Equal(token.Pos(nlPos + 1))) + Expect(lit).To(Equal("foo")) + Expect(s.Position(pos)).To(Equal(token.Position{ + Filename: file.Name(), + Offset: nlPos, + Line: 2, + Column: 1, + })) + }, + ) It("should return IO errors", func() { r := testing.ErrReader("io error") s := make.NewScanner(r, file) - Expect(s.Scan()).To(BeFalse()) + _, _, _ = s.Scan() Expect(s.Err()).To(MatchError("io error")) }) }) diff --git a/token/position.go b/token/position.go index 0400c29..b2273ee 100644 --- a/token/position.go +++ b/token/position.go @@ -10,3 +10,11 @@ type ( ) const NoPos = token.NoPos + +// PositionFor returns the Position value for the given file position p. +// If p is out of bounds, it is adjusted to match the File.Offset behavior. +// p must be a Pos value in file or NoPos. Calling token.PositionFor(file, p) +// is equivalent to calling file.PositionFor(p, false). +func PositionFor(file *File, p Pos) Position { + return file.PositionFor(p, false) +} diff --git a/token/position_test.go b/token/position_test.go new file mode 100644 index 0000000..283ca38 --- /dev/null +++ b/token/position_test.go @@ -0,0 +1,35 @@ +package token_test + +import ( + gotoken "go/token" + "math" + "testing/quick" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/unmango/go-make/token" +) + +var _ = Describe("Position", func() { + Describe("PositionFor", func() { + var file *token.File + + BeforeEach(func() { + file = gotoken.NewFileSet().AddFile("test", 1, math.MaxInt-2) + }) + + It("should be equivalent to calling file.PositionFor(p, false)", func() { + err := quick.Check(func(p int) bool { + pos := token.Pos(p) + + expected := file.PositionFor(pos, false) + actual := token.PositionFor(file, pos) + + return actual == expected + }, nil) + + Expect(err).NotTo(HaveOccurred()) + }) + }) +})