@desertnet/scanner

中文文档
1.1.1发布于 2018/08/02MITCommonJS零依赖近 30 天下载 8769

A regex-based string scanner/tokenizer

原始使用文档
文档目录8 节
  1. Installation
  2. Usage
  3. An Example
  4. Dialects
  5. Multi-Dialect Scanners
  6. API
  7. new Scanner(dialectDefinition)
  8. new Scanner.Token(type, value, index, line, column)

scanner

A regex-based string scanner/tokenizer for JavaScript

Installation

npm install --save @desertnet/scanner

Usage

An Example

const Scanner = require('@desertnet/scanner')

const body = `Hi! [img-1]
[img-2]
Bye[!]`

const embedTagScanner = new Scanner([
  {'tag': /\[\w+-\d+\]/},
  {'text': /[^\[]+/},
  {'bracket': /\[/}
])

let token
embedTagScanner.setSource(body)
while (token = embedTagScanner.nextToken()) {
  console.log(token)
}

Outputs:

Token { type: 'text',    value: 'Hi! ',    index: 0,  line: 1, column: 1 }
Token { type: 'tag',     value: '[img-1]', index: 4,  line: 1, column: 5 }
Token { type: 'text',    value: '\n',      index: 11, line: 2, column: 12 }
Token { type: 'tag',     value: '[img-2]', index: 12, line: 2, column: 1 }
Token { type: 'text',    value: '\nBye',   index: 19, line: 3, column: 8 }
Token { type: 'bracket', value: '[',       index: 23, line: 3, column: 4 }
Token { type: 'text',    value: '!]',      index: 24, line: 3, column: 5 }

Dialects

In the context of this module, a dialect is an ordered mapping of a token-type to a regular expression that describes the token. In the above example you will see a Scanner object instantiated with an array of objects, with each object containing a single RegExp property. This pattern allows you to succinctly define a dialect for your scanner.

We call a dialect an ordered mapping because the order in which the token types are defined is important. The scanner will attempt to match the input string against the first regex in the dialect. If that fails, it will go on to the next one in the list. Once a match is made, a token with the matching type is generated. No attempts to match any remaining regexes in the dialect are undertaken.

It is generally a very good idea to ensure that your dialect accepts all inputs. If you do not, then an input string could cause the scanner to throw an "unexpected character" error. Notice in the example how the text token description matches everything that is not a [ character, and the last token description matches a lone [ character. This ensures that all inputs will generate a token. An alternate way of doing this would be to end your dialect with the following token description to match any single character:

{'catchall': /[^]/}   // This is the same as `qr/./s` in Perl

Multi-Dialect Scanners

Often a language is simply not describable using a single dialect. For example, HTML often contains inline CSS. Or JavaDoc annotations inside Java comments. Even something simple like HTML attribute values denoted by " and HTML attribute values denoted by ' may necessitate distinct dialect definitions.

You can create a multi-dialect scanner by passing an object to the Scanner constructor. This object should be a mapping of dialect names to dialect definitions. For example, here is the start of a rather stripped down HTML multi-dialect scanner:

const htmlScanner = new Scanner({
  // Starting dialect, for content "outside of a tag".
  "content": [
    {"text": /[^<>]+/},
    {"tagStart": /<[a-z][^\t\n\ \/\>]*/i},
    {"closeTagStart": /<\/[a-z][^\t\n\ \/\>]*/i},
    {"error": /[<>]/}
  ],

  // Dialect for the inside of tags.
  "tag": [
    {"tagEnd": />/},
    {"whitespace": /\s+/},
    {"selfClose": /\//},
    {"error": /['"<=]/},
    {"attributeStart": /[^>=\s\/]+/i}
  ],

  // Initial dialect for attributes.
  "attribute": [
    {"whitespace": /\s+/},
    {"attributeValueQuotedStart": /=['"]/},
    {"attributeValueStart": /=/},
    {"tagEnd": />/},
    {"selfClose": /\//},
    {"error": /['"<]/},
  ],

  // Dialect for closing tags.
  "closeTag": [
    {"tagEnd": />/},
    {"whitespace": /\s+/},
    {"error": /[^\s>]+/}
  ],

  // ...

})

Once you have a multi-dialect scanner you must call scanner.pushDialect(dialectName) to set the initial dialect. While you tokenize your string, you can call .pushDialect() and .popDialect() whenever your input changes contexts to a different dialect. (Alternately you may call .setDialect() directly, but do not mix this with using the scanner's dialect stack.)

API

new Scanner(dialectDefinition)

Constructs a new Scanner object with the given dialect definition(s). If dialectDefinition is an Array, then it is expected that the array contains a list of token descriptors, which is an object containing a single property that maps a token-type to a RegExp. If it is an object, then it is expected that each property maps a dialect name to an array of token descriptors. See the above definition of Dialects and Multi-Dialect scanners.

.setSource(input)

Sets the input string for the scanner. If you are reusing a scanner instance, calling this method will reset the scanner to the beginning of the new string.

.currentDialect()

Returns a string which is the name of the current dialect. For single-dialect scanners this will always be 'main'.

.setDialect(name)

Sets the current dialect to the dialect specified by the string name. You do not need to call this for single-dialect scanners. For multi-dialect scanners, you are probably better off using the methods for managing the scanner's dialect stack.

.pushDialect(name)

Pushes the dialect specified by name onto the scanner's dialect stack, and sets it as the current dialect.

.popDialect()

Pops the topmost dialect from the scanner's dialect stack, and sets the current dialect to the dialect that is now at the top of the stack.

.nextToken([expectedTokens])

Returns a Token object for the next matching token in the source string. To scan a string, you keep calling this method until it returns null, which indicates the entire string has been scanned. This method can throw an error if the dialect cannot match the next token, so be sure to define your dialects to cover all inputs.

The optional expectedTokens parameter is an array of token type names for the current dialect. Use this if it makes sense to only scan for a subset of token types. However, if you find yourself doing this, it may be better to switch to a multi-dialect scanner.

new Scanner.Token(type, value, index, line, column)

Generally, instances of the Token class should not be instantiated directly. However the constructor is publicly available as it can be convenient to instantiate your own in tests.

In typical usage, they are returned to you by scanner.nextToken(). They have the following readable properties:

  • type: The name of the token type. (A string.)
  • value: The matching substring from the input.
  • index: The zero-based index of the start of the token in the input string.
  • line: The one-based index of the line the token starts at in the input string.
  • column: The one-based index of the column the token starts at within the line.

版本与发布记录

@desertnet/scanner 共发布 10 个版本,首次发布于 2016/08/13;过去 12 个月发布了 0 个版本。

版本发布日期距今
1.1.1latest2018/08/028 年前
2.1.12018/08/028 年前
2.1.02018/07/318 年前
2.0.22018/07/288 年前
2.0.12018/07/278 年前
2.0.02018/07/278 年前

关于 @desertnet/scanner 的常见问题

@desertnet/scanner 是什么?

A regex-based string scanner/tokenizer

如何安装 @desertnet/scanner?

在项目目录运行 npm install @desertnet/scanner,或使用 pnpm add / yarn add / bun add。

@desertnet/scanner 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/desertnet__scanner 是否存在。

@desertnet/scanner 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@desertnet/scanner 可以商用吗?

@desertnet/scanner 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@desertnet/scanner 的下载量有多少?

根据 npm 官方下载统计,@desertnet/scanner 在 2026/10/12 – 2026/11/10 这 30 天内被下载 8,769 次,日均约 292 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@desertnet/scanner 还在维护吗?

最新版本 1.1.1 发布于 2018/08/02(8 年前);至今共发布 10 个版本,其中过去 12 个月发布了 0 个;首次发布于 2016/08/13。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。