
Golang scanners are used to read input from various sources, such as files, networks, or user input. They are a fundamental part of any Golang program that involves reading data.
The bufio.Scanner type is the most commonly used scanner in Golang, providing a convenient way to read input line by line or in chunks. It's a great tool for parsing configuration files or reading log data.
To use a bufio.Scanner effectively, you need to create a new instance of the type and specify the source of the input data. This can be a file, a string, or even a network connection. For example, you can create a scanner that reads from a file using the Open function.
Scanners can be used to read input in various modes, including ScanLines, which reads input line by line, and ScanWords, which reads input word by word. The mode you choose depends on the structure of your input data.
Recommended read: Golang Types
What Is Lexical Scanning?
Lexical scanning, also known as tokenization, is the process of converting a sequence of characters into a sequence of tokens.
These tokens are the smallest units of a programming language that have meaning, representing keywords, identifiers, literals, and punctuations.
It's an essential step in the process of compiling or interpreting source code, helping to break down the code into manageable pieces that can be processed by a parser.
In the context of Go, lexical scanning is a crucial part of the language's design, allowing developers to create robust and efficient parsers for programming languages and other structured text formats.
The Go `text/scanner` package provides a basic scanner for UTF-8 encoded text, designed to be used in the implementation of parsers.
This package can tokenize input text into a sequence of tokens, which can then be processed by a parser.
By understanding lexical scanning, developers can create more effective and efficient parsers, leading to better performance and reliability in their code.
In the next section, we'll take a closer look at how the Go `text/scanner` package can be used to implement lexical scanning in a Go program.
Scanner Basics
The `text/scanner` package in Go provides a basic scanner for UTF-8 encoded text. It's designed to be used in the implementation of parsers for programming languages and other structured text formats.
The `Scanner` type is the primary type provided by the `text/scanner` package, and it implements reading of Unicode characters and tokens from an `io.Reader`. You can create an instance of the `Scanner` type and configure it with your input text.
The `Scan` method is the core method of the `Scanner` type, and it reads the next token or Unicode character from the source and returns it. It only recognizes tokens for which the respective `Scanner.Mode` bit is set, and it returns `EOF` at the end of the source.
Go's Text Package
The Go language has a built-in `text/scanner` package that provides a basic scanner for UTF-8 encoded text.
This package is designed to be used in the implementation of parsers for programming languages and other structured text formats.

The `text/scanner` package can tokenize input text into a sequence of tokens, which can then be processed by a parser.
A Scanner can be used to scan and tokenize text data, and it provides methods to read tokens from the input text.
To use the `text/scanner` package, you need to create an instance of the `Scanner` type and configure it with your input text.
The `Scan` method of the `Scanner` type reads the next token or Unicode character from the source and returns it.
The `Scan` method only recognizes tokens for which the respective `Scanner.Mode` bit is set.
The `text/scanner` package skips white space and Go comments by default and recognizes all literals as defined by the Go language specification.
A Scanner can be customized to recognize only a subset of those literals and to recognize different identifier and white space characters.
The `Scan` method returns the token position, the token, and its literal string if applicable.
The token position is a type that holds the line, column, and probably also the offset.
The literal string needs some explanation, as certain tokens have values that need to be extracted.
Source Files

The source file is the foundation of a scanner's functionality. It's the raw material that gets processed.
You can feed the scanner a []byte, which is essentially a chunk of memory that stores the source text. This is the input that gets tokenized through repeated calls to the Scan method.
The scanner doesn't care where the source text comes from, as long as it's a valid []byte. This makes it a flexible tool for working with Go source text.
Scanner Initialization
Initializing a Scanner in Go is a crucial step before tokenizing text. You can reuse the same file when re-scanning the same file, and the scanner will ignore any line information that's already present.
The Init function is used to prepare the scanner for tokenization. It sets the scanner at the beginning of the source text and uses the file for position information.
When re-scanning the same file, the scanner will not re-add line information, which makes it efficient. However, if the file size does not match the source size, the Init function will cause a panic.
The mode parameter determines how comments are handled during tokenization. The Init function will call the error handler if there is an error in the first character of the file.
The Init function also resets the scanner's fields, including Error, ErrorCount, Mode, and Whitespace, to their default values.
Here's an interesting read: Golang Mode
Scanning Process
The scanning process in Go's text/scanner package is quite fascinating. It starts by scanning the next token and returning the token position, the token itself, and its literal string if applicable.
The token position is relative to the file added to the file set with Init, and token positions are relative to that file. This means that the scanning process is tied to the specific file being scanned.
A token can be one of several types, including literals, keywords, and semicolons. Literals include identifiers, integers, floats, imaginary numbers, characters, and strings, as well as comments. Keywords are returned as is, while semicolons have a corresponding literal string that indicates whether they were present in the source or inserted due to a newline or EOF.
The scanning process will return a valid token even if a syntax error was encountered, making it a more tolerant parser. However, this means that a client cannot assume that no error occurred and should instead check the scanner's ErrorCount or the number of calls to the error handler, if one was installed.
If the returned token is token.ILLEGAL, the literal string is the offending character. In all other cases, Scan returns an empty literal string.
Preventing Issues
To prevent issues with token handling, it's essential to use a switch statement to handle different token types. This allows you to specifically address each type of token, such as identifiers, integers, floats, characters, strings, and raw strings.
Using a switch statement helps you avoid potential errors that could occur when handling unexpected token types. For example, you can use the `scanner.Ident`, `scanner.Int`, `scanner.Float`, `scanner.Char`, `scanner.String`, and `scanner.RawString` constants to identify the token type.
To get the text representation of a token, you can use the `TokenText` method. This is particularly useful when you need to access the actual text of the token, rather than just its type.
Discover more: Golang Strings Package
Scanner Functions
The Scan function is the core of the Go scanner, responsible for reading the next token or Unicode character from the source. It returns the token position, the token, and its literal string if applicable.
The Scan function only recognizes tokens for which the respective Scanner.Mode bit is set. It returns EOF at the end of the source and reports scanner errors by calling s.Error if not nil.
In tolerant parsing mode, Scan will return a valid token even if a syntax error was encountered, but this doesn't guarantee that no error occurred. You should still check the scanner's ErrorCount or the number of calls to the error handler.
Type Mode

Type Mode is a crucial aspect of scanner functions. A mode value is a set of flags, or simply 0, that controls scanner behavior.
These flags determine how the scanner operates, and they can be adjusted to suit specific needs. For example, a mode value can be set to control scanner behavior.
A mode value is a binary representation of the flags, with each flag being a single bit. This binary representation can be thought of as a series of 0s and 1s that correspond to the flags.
A unique perspective: Golang Build Flags
Maximum Token Size Error
The Maximum Token Size Error is a common issue that can arise when using scanner functions. By default, the maximum length of a token is 64 * 1024 bytes.
This limit can be a problem if you're working with large amounts of data, as it may cause the scanner to stop abruptly. The error is specifically known as ErrTooLong.
Fortunately, you can adjust this limit by using the Buffer method, which allows you to pass a custom buffer. This can be a useful workaround if you need to handle longer tokens.
Iterating with Channels

Iterating with channels can be a fun approach, but it's not recommended for real use. This method involves creating a new method called scanner.Tokens() that returns a channel of tokens.
Calling scanner.Scan() inside a for loop can lead to a race condition, resulting in tokens being out of order. This can cause issues with the program's logic and accuracy.
Breaking out of the for loop can cause the goroutine filling the tokens channel to block forever, eventually leaving garbage in memory.
Here are some potential problems with iterating with channels:
- Race condition: tokens may be out of order
- Memory leak: goroutine may block forever
These issues can make the codebase complex and hard to maintain.
Current APIs
The current APIs for scanner functions are provided by the text/scanner and go/scanner packages.
text/scanner has no constructor, and it's meant to be generic, working with Go-like source text. It returns the token literal, the first rune, and the position.
You have to call different methods to get the position or the literal in text/scanner, unlike go/scanner where it's part of the Scan() signature.
Here are the key differences in the API of text/scanner:
- There is no constructor.
- Scan() returns a token, while Next() returns the next unicode character, both of type rune.
- If an error happens, scanner.Pos() should be used to update the position and advance the internal scanner to the error position.
- The actual position is always stored in scanner.Position.
go/scanner, on the other hand, is used by the official Go tools and has a different constructor. It uses a token.File to handle positions of tokens internally.
The scanner.Scan() method in go/scanner returns the position, token, and literal all at once.
Token Handling
Token handling is a crucial part of working with Go's scanner package. The `Scan` method returns a `rune`, which represents the token type, and can be used with a switch statement to handle different token types.
The `text/scanner` package defines constants for various token types, such as `scanner.Ident`, `scanner.Int`, `scanner.Float`, `scanner.Char`, `scanner.String`, and `scanner.RawString`. You can use these constants to determine the type of token encountered.
To get the text representation of the token, you can use the `TokenText` method, which returns the string corresponding to the most recently scanned token. This is valid after calling `Scanner.Scan` and in calls of `[Scanner.Error]`.
Tokens, Positions, Text

Tokens are just enumerations based on what you think should be tokenized. For example, you could tokenize the literals true and false to be a token.BOOL token.
The `text/scanner` package in Go provides a basic scanner for UTF-8 encoded text, designed to be used in the implementation of parsers for programming languages and other structured text formats.
A Token can be defined as an enumeration based on what you think should be tokenized, and it can have a value that needs to be extracted, like in the case of a token.STRING.
The `text/scanner` package defines constants for various token types, such as `scanner.Ident`, `scanner.Int`, `scanner.Float`, `scanner.Char`, `scanner.String`, and `scanner.RawString`.
You can use a switch statement to handle different token types, and when you encounter a token, you can use the `TokenText` method to get the text representation of the token.
The `TokenText` method returns the string corresponding to the most recently scanned token, and it's valid after calling `Scanner.Scan` and in calls of `[Scanner.Error]`.
Constants
Constants play a crucial role in configuring the Scanner to recognize specific tokens. For instance, to configure a Scanner to only recognize Go identifiers, integers, and skip comments, you set the Scanner's Mode field to ScanIdents.
Unrecognized tokens are not ignored, but instead returned as individual characters or sub-tokens. This is because the scanner does not skip unrecognized tokens, unlike comments which are skipped if SkipComments is set.
The GoTokens mode allows the Scanner to accept all Go literal tokens, including identifiers, and comments will be skipped. This is the opposite of ScanIdents, which only recognizes identifiers.
GoWhitespace is the default value for the Scanner's Whitespace field, and it selects Go's white space characters. This means that the Scanner will recognize common whitespace characters by default.
Featured Images: pexels.com


