Flutter令牌解析插件token_parser的使用

发布于 1周前 作者 eggper 来自 Flutter

Flutter令牌解析插件token_parser的使用

Token Parser

一个直观的令牌解析器,包括语法/词法规则定义、分词和解析。

实现基于词法分析。 了解更多请参阅 维基百科,或查看 基本图解

特性

  • 语法/词法规则定义
  • 分词
  • 解析
  • 引用和自引用
  • 词法语法错误
  • 调试

开始使用

pubspec.yaml 文件中添加依赖:

dart pub add token_parser

并在 Dart 文件中导入该包:

import 'package:token_parser/token_parser.dart';

使用

这个包基于语法/词法规则定义,这是一个定义语法规则的词元列表。以下是一个简短的例子:

final letter = '[a-zA-Z]'.regex;
final digit = '[0-9]'.regex;

final number = digit.multiple & ( '.' & digit.multiple ).optional;
final identifier = letter & ( letter | digit ).multiple.optional;

final grammar = Grammar(
  main: identifier + '=' + number,
  rules: {
    'letter': letter,
    'digit': digit,

    'number': number,
    'identifier': identifier,
  }
);

void main() {
  final result = grammar.parse('myNumber = 12.3');

  print('Identifier: ${ result.get(lexeme: identifier).first.value }');
  print('Number: ${ result.get(lexeme: number).first.value }');
  // 输出
  // Identifier: myNumber
  // Number: 12.3
}

词元

词元 是一个用于对输入进行分词的语法定义。它是必须匹配的模式,本质上是一个语法规则。

通过定义每个令牌应包含什么来完成语法/词法规则定义,使用词法分析。

这种词元组合定义了语法。词元可以包含其他词元以形成更复杂的词法语法。

final abc = 'a' | 'b' | 'c';
final def = 'd' | 'e' | 'f';

final expression = abc & def;

使用 & 操作符将令牌以“与”操作符结合,使用 | 操作符将令牌以“或”操作符结合。我们可以定义一个表达式,它可以接受任何组合的词元 abcdef

词元可以通过不同的属性进行扩展。

final abc = ('a' | 'b' | 'c').multiple;

final expression = abc & 'd'.optional;

为了方便起见,词元可以用正则表达式定义。

可用的词元修改方法包括:

  • .not
  • .multiple / .multipleOrNone
  • .full
  • .optional
  • .regex
  • .character
  • .spaced
  • .optionalSpaced
  • .repeat(int min, [int max])
  • .until(Pattern pattern)
  • .pad(Pattern pattern)
final digit = '[0-9]'.regex;
final number = digit.multiple & ('.' & digit.multiple).optional;

final letter = '[a-zA-Z]'.regex;
final word = letter.multiple;
final phrase = word & (' ' & word).multiple.optional;

运算符

模式(如字符串、正则表达式和词元)可以使用运算符进行组合或修改。

某些运算符只能用于组合模式,而其他运算符只能用于修改模式。修改运算符必须放在目标模式之前。

可用的运算符有:

运算符 描述 动作 分词
& 组合 ab
| 组合 a / b
+ 与,空格 组合 a b
* 与,可选空格 组合 ab / a b
- 修改 c
~ 可选空格周围 修改 "a" / " a "

负词元

负词元的行为可能与预期不同。否定不会消耗输入,而是确保前面的模式不匹配目标词元。

这意味着否定一个词元并不意味着“不是任何字符”。要消耗不匹配词元的任何字符,使用 .not.character 组合。

此外,注意否定运算符与其他修饰符使用的区别:

final wrongLexeme = -'a'.multiple.optional;
final lexeme = (-'a').multiple.optional;

虽然 -'a' 会消耗任何不是“a”的字符,但多重和可选项是在否定之前添加的。wrongLexeme 的否定应用于可选项。

要确保字符的否定应用于多重和可选项,可以使用 .not.character.multiple.optional

引用和自引用

引用词元是占位符,当请求对其进行输入分词时,它会在绑定到它的语法中查找关联的名称的词元。

词元可以通过 reference(String name)self() 函数,或者简写为 ref(String name) 来引用。

final abc = 'a' | 'b' | 'c' | reference('def');
final def = ('d' | 'e' | 'f') & self().optional;

为了使引用生效,它必须绑定到语法,并且引用的词元必须在同一语法中存在。如果引用的词元不存在,则在分词时会抛出错误。

语法

语法是一组将用于解析输入的词元,本质上是一组定义语言的规则。

语法有一个入口点,称为 主词元。这个词元用于解析输入,并且是唯一返回的词元。

语法可以通过构造函数定义:

final grammar = Grammar(
  main: phrase | number,
  rules: {
    'digit': digit,
    'number': number,

    'letter': letter,
    'word': word,
    'phrase': phrase,

    'abc': 'a' | 'b' | 'c',
    'def': 'd' | 'e' | 'f',
  },
);

或者通过 .add(String name, Pattern pattern) 方法定义:

final grammar = Grammar();

grammar.addMain(phrase | number);

grammar.add('digit', digit);
grammar.add(...);

词元可以单独地对输入进行分词,但通常将其分组在一个语法中会更一致。

这样允许使用 引用和主词元。将任何词元添加到语法中都会有效地将它们绑定在一起,连同名称,并解决任何 自引用

解析输入

语法用于解析任何输入,这将对其分词,考虑之前添加的所有词元。

使用 .parse(String input, { Lexeme? main }) 方法解析输入。

final grammar = Grammar(...);

grammar.parse('123');
grammar.parse('123.456');

grammar.parse('word');
grammar.parse('two words');

可以通过作为参数传递它来覆盖用于解析输入的主要词元。

解析输入时,它将返回一个结果令牌,可以使用它获取匹配的词元值和位置。也可以使用它获取子令牌。

删除不需要的模式

在解析输入时,语法可能不关心某些词元,例如注释。要删除这些模式,可以在构造函数中使用 remove 参数,或者使用 .addRemover(Lexeme lexeme) 方法。

令牌

令牌是将词元与输入匹配的结果。它包含匹配的词元值和令牌的位置。

生成此令牌的过程称为 分词

final grammar = Grammar(...);
final token = grammar.parse('123');

print('''
  Value: ${ token.value }
  Lexeme: ${ token.lexeme.name }

  Start: ${ token.start }
  End: ${ token.end }
  Length: ${ token.length }
''');

词法语法错误

在分词时,如果输入不匹配任何词元,它将抛出 LexicalSyntaxError 错误。

此错误显示错误的位置和预期匹配输入的词元。此外,它还会显示遍历的词元列表,作为到达错误的路径。

此错误将跳过任何未命名的词元。

分析令牌树

你可以使用这个令牌来分析结果树。使用 .get({ Lexeme? lexeme, String? name }) 方法可以获取所有匹配的令牌或名称。

搜索范围可以通过使用 bool shallow 参数进行限制,默认情况下,在有词元或名称时为 false,而在没有任何搜索参数时为 true

final result = grammar.parse('two words');

final tokens = result.get();
final words = result.get(lexeme: word);
final letters = result.get(name: 'letter');

print('Words: ${ words.map((token) => token.value) }');
print('Letters: ${ letters.get(letter).map((token) => token.value) }');

你也可以使用 .children.allChildren 进行更直接的方法。虽然子令牌不一定保证是令牌,它们也可能是基本匹配值,例如 Match 类型。

调试

了解语法如何对输入进行分词以及使用哪些词元非常重要。为此,提供了调试模式和语法错误。

调试模式

通过实例化 DebugGrammar 而不是 Grammar 来启用调试模式。

final grammar = DebugGrammar(...);

此外,你可以指定调试参数:

  • bool showAll: 包括无名称的词元,默认为 false
  • bool showPath: 显示词元路径,默认为 false
  • Duration delay: 每步之间的延迟,默认为 Duration.zero

信息输出如下:

│
│  (#3)
├► Tokenizing named syntaxRule
│    at index 0, character "/"
│    on path: (main) → syntax → syntaxRule
│

词法语法错误

语法错误会在输入不匹配所需词元时抛出。错误将显示字符、索引、词元和路径。

LexicalSyntaxError: Unexpected character "/"
at index 0
with lexeme "syntax"
on path:
    → syntax
    ↑ (main)

示例

词元化

import 'package:token_parser/token_parser.dart';

final whitespace = ' ' | '\t';
final lineBreak = '\n' | '\r';
final space = (whitespace | lineBreak).multiple;

final letter = '[a-zA-Z]'.regex;
final digit = '[0-9]'.regex;

final identifier = letter & (letter | digit).multiple.optional;

final number = digit.multiple & ('.' & digit.multiple).optional;
final string = '"' & '[^"]*'.regex & '"' | "'" & "[^']*".regex & "'";

final variableDeclaration = 'var' &
    space &
    identifier &
    space.optional &
    '=' &
    space.optional &
    (number | string) &
    space.optional &
    (';' | space);

final grammar = Grammar(
  main: (variableDeclaration | space).multiple,
  rules: {
    'whitespace': whitespace,
    'lineBreak': lineBreak,
    'space': space,
    'letter': letter,
    'digit': digit,
    'identifier': identifier,
    'number': number,
    'string': string,
    'variableDeclaration': variableDeclaration,
  },
);

void main() {
  final result = grammar.parse('''
    var hello = "world";
    var foo = 123;
    var bar = 123.456;
  ''');

  final numbers = result.get(lexeme: number).map((token) => token.value);
  final identifiers = result.get(lexeme: identifier).map((token) => '"${token.value}"');

  print('Numbers: $numbers');
  print('Identifiers: $identifiers');
}

引用

import 'package:token_parser/token_parser.dart';

final expression = 'a' & Lexeme.reference('characterB').optional;
final characterB = 'b'.lexeme();

final recursive = 'a' & Lexeme.self().optional;

final grammar = Grammar(
  main: expression,
  rules: {
    'expression': expression,
    'characterB': characterB,
    'recursive': recursive,
  }
);

void main() {
  print(grammar.parse('ab').get(lexeme: characterB));
  print(grammar.parse('aaa', recursive).get(lexeme: recursive));
}

RGB颜色解析器

import 'package:token_parser/token_parser.dart';

void main() {
  final result = grammar.parse('rgb(255, 100, 0)');
  print('Red: ${ result.get(lexeme: red).first.value }');
  print('Green: ${ result.get(lexeme: green).first.value }');
  print('Blue: ${ result.get(lexeme: blue).first.value }');

  // 输出
  // Red: 255
  // Green: 100
  // Blue: 0
}

final grammar = Grammar(
  main: rgb,
  rules: {
    'rgbNumber': rgbNumber,
    'red': red,
    'green': green,
    'blue': blue,
    'rgb': rgb,
  }
);

final rgbNumber = range(0, 255).lexeme();

final red = rgbNumber.copy();
final green = rgbNumber.copy();
final blue = rgbNumber.copy();

final rgb = 'rgb('.lexeme() * red * ',' * green * ',' * blue * ')';

更多关于Flutter令牌解析插件token_parser的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter令牌解析插件token_parser的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用token_parser插件来解析令牌的示例代码。假设你已经将token_parser插件添加到了你的pubspec.yaml文件中,并且已经运行了flutter pub get来安装它。

1. 添加依赖

首先,确保你的pubspec.yaml文件中包含token_parser依赖:

dependencies:
  flutter:
    sdk: flutter
  token_parser: ^x.y.z  # 请替换为实际的版本号

2. 导入插件

在你的Dart文件中导入token_parser插件:

import 'package:token_parser/token_parser.dart';

3. 使用TokenParser解析令牌

假设你有一个JWT令牌,你想解析它以获取其中的信息,你可以这样做:

void main() async {
  // 示例JWT令牌(请替换为你的实际令牌)
  String jwtToken = 'your.jwt.token.here';

  // 创建TokenParser实例
  TokenParser parser = TokenParser();

  try {
    // 解析令牌
    Map<String, dynamic> payload = await parser.parse(jwtToken);

    // 打印解析后的payload
    print('Parsed Payload: $payload');

    // 访问payload中的特定字段,例如用户ID
    String userId = payload['sub']; // 'sub'是JWT标准中的subject字段,通常用于存储用户ID
    print('User ID: $userId');
  } catch (e) {
    // 捕获并处理解析错误
    print('Error parsing token: $e');
  }
}

4. 在Flutter应用中使用

如果你在一个Flutter应用中使用这个代码,你可能想在一个按钮点击事件或者初始化状态中使用它。例如:

import 'package:flutter/material.dart';
import 'package:token_parser/token_parser.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: TokenParserExample(),
    );
  }
}

class TokenParserExample extends StatefulWidget {
  @override
  _TokenParserExampleState createState() => _TokenParserExampleState();
}

class _TokenParserExampleState extends State<TokenParserExample> {
  String _parsedData = '';

  void _parseToken() async {
    String jwtToken = 'your.jwt.token.here';
    TokenParser parser = TokenParser();

    try {
      Map<String, dynamic> payload = await parser.parse(jwtToken);
      setState(() {
        _parsedData = jsonEncode(payload); // 使用jsonEncode将Map转换为字符串以便显示
      });
    } catch (e) {
      setState(() {
        _parsedData = 'Error parsing token: $e';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Token Parser Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Parsed Data:'),
            Text(_parsedData, style: TextStyle(fontSize: 18)),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _parseToken,
              child: Text('Parse Token'),
            ),
          ],
        ),
      ),
    );
  }
}

在这个示例中,我们创建了一个简单的Flutter应用,其中包含一个按钮,点击按钮时会尝试解析JWT令牌,并在屏幕上显示解析后的数据。

请注意,token_parser插件的具体API和用法可能会随着版本的更新而有所变化,因此请参考插件的官方文档以获取最新和最准确的信息。

回到顶部