Flutter令牌解析插件token_parser的使用
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;
使用 &
操作符将令牌以“与”操作符结合,使用 |
操作符将令牌以“或”操作符结合。我们可以定义一个表达式,它可以接受任何组合的词元 abc
和 def
。
词元可以通过不同的属性进行扩展。
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
更多关于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和用法可能会随着版本的更新而有所变化,因此请参考插件的官方文档以获取最新和最准确的信息。