基于java的脚本语言hair的开发思路1-词法分析

申明:本脚本语言前面的开发思路基本是《两周自制脚本语言》中Stone语言的模型,后期将逐渐分离,添加更多特征。这本书作为入门读物确实不错,但是其设计规范还是有待商榷,一是没有给出一个确定的项目架构、二是部分的结构的成分不够规范(比如token的定义,运算优先级的处理等)。

词法分析

一个程序片段被编译器解析的时候是一个很长的字符串。我们的目标是通过一定的手段分析将这个字符串分开来。

而这个被分割后的子字符串我们一般称为Token,Token有很多类型。在V8中定义的部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#define BINARY_OP_TOKEN_LIST(T, E) \
E(T, NULLISH, "??", 3) \
E(T, OR, "||", 4) \
E(T, AND, "&&", 5) \
E(T, BIT_OR, "|", 6) \
E(T, BIT_XOR, "^", 7) \
E(T, BIT_AND, "&", 8) \
E(T, SHL, "<<", 11) \
E(T, SAR, ">>", 11) \
E(T, SHR, ">>>", 11) \
E(T, MUL, "*", 13) \
E(T, DIV, "/", 13) \
E(T, MOD, "%", 13) \
E(T, EXP, "**", 14) \
E(T, ADD, "+", 12) \
E(T, SUB, "-", 12)

#define EXPAND_BINOP_ASSIGN_TOKEN(T, name, string, precedence) \
T(ASSIGN_##name, string "=", 2)

#define EXPAND_BINOP_TOKEN(T, name, string, precedence) \
T(name, string, precedence)

#define TOKEN_LIST(T, K) \
\
/* BEGIN PropertyOrCall */ \
/* BEGIN Member */ \
/* BEGIN Template */ \
/* ES6 Template Literals */ \
T(TEMPLATE_SPAN, nullptr, 0) \
T(TEMPLATE_TAIL, nullptr, 0) \
/* END Template */ \
\
/* Punctuators (ECMA-262, section 7.7, page 15). */ \
/* BEGIN Property */ \
T(PERIOD, ".", 0) \
T(LBRACK, "[", 0) \
/* END Property */ \
/* END Member */ \
T(QUESTION_PERIOD, "?.", 0) \
T(LPAREN, "(", 0) \
/* END PropertyOrCall */ \
T(RPAREN, ")", 0) \
T(RBRACK, "]", 0) \
T(LBRACE, "{", 0) \
T(COLON, ":", 0) \
T(ELLIPSIS, "...", 0) \
T(CONDITIONAL, "?", 3) \
/* BEGIN AutoSemicolon */ \
T(SEMICOLON, ";", 0) \
T(RBRACE, "}", 0) \
/* End of source indicator. */ \
T(EOS, "EOS", 0) \
/* END AutoSemicolon */ \
\
/* BEGIN ArrowOrAssignmentOp */ \
T(ARROW, "=>", 0) \
/* BEGIN AssignmentOp */ \
/* IsAssignmentOp() relies on this block of enum values being */ \
/* contiguous and sorted in the same order! */ \
T(INIT, "=init", 2) /* AST-use only. */ \
T(ASSIGN, "=", 2) \
BINARY_OP_TOKEN_LIST(T, EXPAND_BINOP_ASSIGN_TOKEN) \
/* END AssignmentOp */ \
/* END ArrowOrAssignmentOp */ \
\
/* Binary operators sorted by precedence. */ \
/* IsBinaryOp() relies on this block of enum values */ \
/* being contiguous and sorted in the same order! */ \
T(COMMA, ",", 1) \
\
/* Unary operators, starting at ADD in BINARY_OP_TOKEN_LIST */ \
/* IsUnaryOp() relies on this block of enum values */ \
/* being contiguous and sorted in the same order! */ \
BINARY_OP_TOKEN_LIST(T, EXPAND_BINOP_TOKEN) \
\
T(NOT, "!", 0) \
T(BIT_NOT, "~", 0) \
K(DELETE, "delete", 0) \
K(TYPEOF, "typeof", 0) \
K(VOID, "void", 0) \
\
/* BEGIN IsCountOp */ \
T(INC, "++", 0) \
T(DEC, "--", 0) \
/* END IsCountOp */ \
/* END IsUnaryOrCountOp */ \
\
/* Compare operators sorted by precedence. */ \
/* IsCompareOp() relies on this block of enum values */ \
/* being contiguous and sorted in the same order! */ \
T(EQ, "==", 9) \
T(EQ_STRICT, "===", 9) \
T(NE, "!=", 9) \
T(NE_STRICT, "!==", 9) \
T(LT, "<", 10) \
T(GT, ">", 10) \
T(LTE, "<=", 10) \
T(GTE, ">=", 10) \
K(INSTANCEOF, "instanceof", 10) \
K(IN, "in", 10) \
\
/* Keywords (ECMA-262, section 7.5.2, page 13). */ \
K(BREAK, "break", 0) \
K(CASE, "case", 0) \
K(CATCH, "catch", 0) \
K(CONTINUE, "continue", 0) \
K(DEBUGGER, "debugger", 0) \
K(DEFAULT, "default", 0) \
/* DELETE */ \
K(DO, "do", 0) \
K(ELSE, "else", 0) \
K(FINALLY, "finally", 0) \
K(FOR, "for", 0) \
K(FUNCTION, "function", 0) \
K(IF, "if", 0) \
/* IN */ \
/* INSTANCEOF */ \
K(NEW, "new", 0) \
K(RETURN, "return", 0) \
K(SWITCH, "switch", 0) \
K(THROW, "throw", 0) \
K(TRY, "try", 0) \
/* TYPEOF */ \
K(VAR, "var", 0) \
/* VOID */ \
K(WHILE, "while", 0) \
K(WITH, "with", 0) \
K(THIS, "this", 0) \
\
/* Literals (ECMA-262, section 7.8, page 16). */ \
K(NULL_LITERAL, "null", 0) \
K(TRUE_LITERAL, "true", 0) \
K(FALSE_LITERAL, "false", 0) \
T(NUMBER, nullptr, 0) \
T(SMI, nullptr, 0) \
T(BIGINT, nullptr, 0) \
T(STRING, nullptr, 0) \
\
/* BEGIN Callable */ \
K(SUPER, "super", 0) \
/* BEGIN AnyIdentifier */ \
/* Identifiers (not keywords or future reserved words). */ \
T(IDENTIFIER, nullptr, 0) \
K(GET, "get", 0) \
K(SET, "set", 0) \
K(ASYNC, "async", 0) \
/* `await` is a reserved word in module code only */ \
K(AWAIT, "await", 0) \
K(YIELD, "yield", 0) \
K(LET, "let", 0) \
K(STATIC, "static", 0) \
/* Future reserved words (ECMA-262, section 7.6.1.2). */ \
T(FUTURE_STRICT_RESERVED_WORD, nullptr, 0) \
T(ESCAPED_STRICT_RESERVED_WORD, nullptr, 0) \
/* END AnyIdentifier */ \
/* END Callable */ \
K(ENUM, "enum", 0) \
K(CLASS, "class", 0) \
K(CONST, "const", 0) \
K(EXPORT, "export", 0) \
K(EXTENDS, "extends", 0) \
K(IMPORT, "import", 0) \
T(PRIVATE_NAME, nullptr, 0) \
\
/* Illegal token - not able to scan. */ \
T(ILLEGAL, "ILLEGAL", 0) \
T(ESCAPED_KEYWORD, nullptr, 0) \
\
/* Scanner-internal use only. */ \
T(WHITESPACE, nullptr, 0) \
T(UNINITIALIZED, nullptr, 0) \
T(REGEXP_LITERAL, nullptr, 0)

可以看到V8定义了很多token类型,符号、关键字都被分开来定义。

但是作为一门简单的脚本语言,书中简单的将Token分为了3大类:

  1. Number Token:整形Token。
  2. String Token:字符串Token。
  3. Identifier Token: 标识符、关键字、变量名、运算符等。(除Number, String以外的单词)。

每种Token我们定义了一个类,其存储的主要信息有行号和实值(数字,字符串,标识符)。

最主要对于代码的解析我们用到了Java的正则匹配。而在V8中是自己写了匹配规则。

我们简单介绍一下:

java的正则表达式

Pattern类

pattern 对象是一个正则表达式的编译表示。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。该方法接受一个正则表达式作为它的第一个参数。下面是他的所有成员:

名称 解释 返回值
compile(String regex, ?int flags) Pattern类没有构造函数,都是通过该函数来获得匹配实体。其中regex是正则表达式,flags是模式,比如是否匹配换行等,一般使用Patter提供的静态变量来传递。 Pattern:由传入正则表达式编译形成的patter实体。
match(String regex, CharSequence input) 通过传入的regex创建匹配实体并且尝试匹配input。 boolean:是否成功匹配。
quote(String s) 将返回一个仅匹配s的正则表达式。(实际上是在s前后分别\Q和\E)。 String:一个仅可匹配s的一个字符串。
CANON_EQ: int 表示当两个字符严格相等时才匹配。主要是对于Unicode字符而言。(不重要,不再赘述)。 /
CASE_INSENSITIVE:int 是否大小写敏感,默认是,传入该变量则表示不敏感(也可在regex中插入?i表示此意)。 /
COMMENTS:int 是否忽略regex中的空格,不是//s,而是空格或者tab等(也可在regex中插入?x表示此意)。 /
DOTALL:int 表示点(.)是否可以匹配所有字符,包括结束符。(也可在regex中插入?s表示此意)。 /
LITERAL:int 表示启用字面值解析模式。 指定此标志后,指定模式的输入字符串就会作为字面值字符序列来对待。输入序列中的元字符或转义序列不具有任何特殊意义。 /
MULTILINE:int 默认情况下,输入的字符串被看作是一行,即便是这一行中包好了换行符也被看作一行。当匹配“^”到“$”之间的内容的时候,整个输入被看成一个一行。启用多行模式之后,包含换行符的输入将被自动转换成多行,然后进行匹配。 /
UNIX_LINES:int 表示仅以\n为换行,\r\n将不会被识别为换行。(也可在regex中插入?d表示此意)。 /

所以基本只会使用compile这个函数和以下的几个静态变量。

示例

1
2
private String regexPat = "\\abc"
private Pattern pattern = Pattern.compile(regexPat);

Matcher 类

Matcher 对象是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。

下面是其成员:

方法 说明
public MatchResult toMatchResult() 将匹配结果以MatchResult的形式返回
public Matcher usePattern(Pattern newPattern) 修改Matcher对象的Pattern,用以进行新的模式匹配。
public Matcher reset() 重置匹配器的状态。
public Matcher reset(CharSequence input) 重置匹配器的状态,重置目标字符串的值为input。
public int start() 返回当前匹配到的字符串在原目标字符串中的起始索引位置
public int start(int group) 返回当前匹配到的字符串中group组在目标字符串的起始索引位置
public int end() 返回当前匹配的字符串的最后一个字符在原目标字符串中的offset(偏移量),这个需要大家注意一下。
public int end(int group) 返回当前匹配的字符串中group组的最后一个字符在原目标字符串中的offset(偏移量),这个需要大家注意一下。
public String group() 返回匹配到的所有字符串,结合find函数使用。
public String group(int group) 返回匹配到的字符串中的对应index的group组的字符串。
public String group(String name) 返回被named-capturing组捕获的字符串,关于named-capturing group(命名捕获组)是JDK1.7新增的功能,可以将正则表达式中的组进行命名。
public int groupCount() 返回当前Matcher对象捕获的组的个数。
public boolean matches() 将整个目标字符串与正则表达式进行匹配,只有完全匹配才能返回true,否则false。
public boolean find() 对目标字符串进行正则匹配,通过while可以多次执行find方法,获取多次的匹配结果,代码编写方式类似于iterator.next()。
public boolean find(int start) 在指定的索引位置对目标字符串进行正则匹配。
public boolean lookingAt() 目标字符串的起始字符串与正则表达式匹配返回true,否则返回false。
public static String quoteReplacement(String s) 返回字符串s字面意义的替代字符串。
public Matcher appendReplacement(StringBuffer sb, String replacement) 向sb中追加replacement字符串,replacement字符串中可以包含匹配器中的分组参数,如1,2。
public StringBuffer appendTail(StringBuffer sb) 将Matcher匹配后的尾部字符串追加至sb中。
public String replaceAll(String replacement) 将目标字符串中所有满足正则匹配的字符串替换为replacement。
public String replaceFirst(String replacement) 将目标字符串中第一个满足正则匹配的字符串替换为replacement。
public Matcher region(int start, int end) 设置目标字符串的匹配范围。
public int regionStart() 返回匹配器区域的起始点索引位置。
public int regionEnd() 返回匹配器区域的结束点索引位置。
public boolean hasTransparentBounds() TransparentBounds标志位:查询TransparentBounds标志位true
public Matcher useTransparentBounds(boolean b) 设置TransparentBounds标志位的值true
public boolean hasAnchoringBounds() AnchoringBounds标志位:查询AnchoringBounds标志位的值,此标志位默认为true。在应用正则表达式的时候,我们可以指定目标字符串的检索范围,也就是说在目标字符串的子字符串中应用正则表达式。但此时会有一个问题,那就是 ^ 和 $ 应该匹配整个字符串的开头和结尾呢? 还是检索范围的起始和结束位置呢?Java 为我们提供了足够的灵活性,我们可以通过下面的方法来查看和设置,默认值是匹配检索范围的起始和结束位置。
public Matcher useAnchoringBounds(boolean b) 设置AnchoringBounds标志位的值true
public boolean hitEnd()
public boolean requireEnd()
boolean match(int from, int anchor)
int getTextLength() 返回目标字符串的长度。
CharSequence getSubSequence(int beginIndex, int endIndex) 获取目标字符串的子字符串。
char charAt(int i) 返回目标字符串中索引为i的字符

PatternSyntaxException

PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

捕获组

捕获组是把多个字符当一个单独单元进行处理的方法,它通过对括号内的字符分组来创建。

捕获组是通过从左至右计算其开括号来编号。例如,在表达式((A)(B(C))),有四个这样的组:

  • ((A)(B(C)))
  • (A)
  • (B(C))
  • (C)

可以通过调用 matcher 对象的 groupCount 方法来查看表达式有多少个分组。groupCount 方法返回一个 int 值,表示matcher对象当前有多个捕获组。

还有一个特殊的组(group(0)),它总是代表整个表达式。该组不包括在 groupCount 的返回值中。

正则表达式语法

有一个需要注意的点:

Java中的转移字符为两个反斜杠\,这与其他语言不同。即当我们需要对一个字符进行转义的时候,我们需要在前面写两个\。

如:一般的我们需要匹配换行符\n这个符号的时候,我们只需要这么写:\n。

而在Java中,我们需要这么写:\\n。

下面是匹配的字符:

字符 说明
\ 将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如, n匹配字符 n。\n 匹配换行符。序列 \\\\ 匹配 \\ ,\\( 匹配 (。
^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与”\n”或”\r”之后的位置匹配。
$ 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与”\n”或”\r”之前的位置匹配。
* 零次或多次匹配前面的字符或子表达式。例如,zo* 匹配”z”和”zoo”。* 等效于 {0,}。
+ 一次或多次匹配前面的字符或子表达式。例如,”zo+”与”zo”和”zoo”匹配,但与”z”不匹配。+ 等效于 {1,}。
? 零次或一次匹配前面的字符或子表达式。例如,”do(es)?”匹配”do”或”does”中的”do”。? 等效于 {0,1}。
{n} n 是非负整数。正好匹配 n 次。例如,”o{2}”与”Bob”中的”o”不匹配,但与”food”中的两个”o”匹配。
{n,} n 是非负整数。至少匹配 n 次。例如,”o{2,}”不匹配”Bob”中的”o”,而匹配”foooood”中的所有 o。”o{1,}”等效于”o+”。”o{0,}”等效于”o*”。
{n,m} m 和 n 是非负整数,其中 n <= m。匹配至少 n 次,至多 m 次。例如,”o{1,3}”匹配”fooooood”中的头三个 o。’o{0,1}’ 等效于 ‘o?’。注意:您不能将空格插入逗号和数字之间。
? 当此字符紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是”非贪心的”。”非贪心的”模式匹配搜索到的、尽可能短的字符串,而默认的”贪心的”模式匹配搜索到的、尽可能长的字符串。例如,在字符串”oooo”中,”o+?”只匹配单个”o”,而”o+”匹配所有”o”。
. 匹配除”\r\n”之外的任何单个字符。若要匹配包括”\r\n”在内的任意字符,请使用诸如”[\s\S]”之类的模式。
(pattern) 匹配 pattern 并捕获该匹配的子表达式。可以使用 $0…$9 属性从结果”匹配”集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用”\(“或者”\)”。
(?:pattern) 匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用”or”字符 (|) 组合模式部件的情况很有用。例如,’industr(?:y|ies) 是比 ‘industry|industries’ 更经济的表达式。
(?=pattern) 执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,’Windows (?=95|98|NT|2000)’ 匹配”Windows 2000”中的”Windows”,但不匹配”Windows 3.1”中的”Windows”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。
(?!pattern) 执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,’Windows (?!95|98|NT|2000)’ 匹配”Windows 3.1”中的 “Windows”,但不匹配”Windows 2000”中的”Windows”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。
x|y 匹配 xy。例如,’z|food’ 匹配”z”或”food”。’(z|f)ood’ 匹配”zood”或”food”。
[xyz] 字符集。匹配包含的任一字符。例如,”[abc]”匹配”plain”中的”a”。
[^xyz] 反向字符集。匹配未包含的任何字符。例如,”[^abc]”匹配”plain”中”p”,”l”,”i”,”n”。
[a-z] 字符范围。匹配指定范围内的任何字符。例如,”[a-z]”匹配”a”到”z”范围内的任何小写字母。
[^a-z] 反向范围字符。匹配不在指定的范围内的任何字符。例如,”[^a-z]”匹配任何不在”a”到”z”范围内的任何字符。
\b 匹配一个字边界,即字与空格间的位置。例如,”er\b”匹配”never”中的”er”,但不匹配”verb”中的”er”。
\B 非字边界匹配。”er\B”匹配”verb”中的”er”,但不匹配”never”中的”er”。
\cx 匹配 x 指示的控制字符。例如,\cM 匹配 Control-M 或回车符。x 的值必须在 A-Z 或 a-z 之间。如果不是这样,则假定 c 就是”c”字符本身。
\d 数字字符匹配。等效于 [0-9]。
\D 非数字字符匹配。等效于 [^0-9]。
\f 换页符匹配。等效于 \x0c 和 \cL。
\n 换行符匹配。等效于 \x0a 和 \cJ。
\r 匹配一个回车符。等效于 \x0d 和 \cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等。与 [ \f\n\r \v] 等效。
\S 匹配任何非空白字符。与 [^ \f\n\r\t\v] 等效。
\t 制表符匹配。与 \x09 和 \cI 等效。
\v 垂直制表符匹配。与 \x0b 和 \cK 等效。
\w 匹配任何字类字符,包括下划线。与”[A-Za-z0-9_]”等效。
\W 与任何非单词字符匹配。与”[^A-Za-z0-9_]”等效。
\xn 匹配 n,此处的 n 是一个十六进制转义码。十六进制转义码必须正好是两位数长。例如,”\x41”匹配”A”。”\x041”与”\x04”&”1”等效。允许在正则表达式中使用 ASCII 代码。
\num 匹配 num,此处的 num 是一个正整数。到捕获匹配的反向引用。例如,”(.)\1”匹配两个连续的相同字符。
\n 标识一个八进制转义码或反向引用。如果 \n 前面至少有 n 个捕获子表达式,那么 n 是反向引用。否则,如果 n 是八进制数 (0-7),那么 n 是八进制转义码。
\nm 标识一个八进制转义码或反向引用。如果 \nm 前面至少有 nm 个捕获子表达式,那么 nm 是反向引用。如果 \nm 前面至少有 n 个捕获,则 n 是反向引用,后面跟有字符 m。如果两种前面的情况都不存在,则 \nm 匹配八进制值 nm,其中 n 和 m 是八进制数字 (0-7)。
\nml 当 n 是八进制数 (0-3),m 和 l 是八进制数 (0-7) 时,匹配八进制转义码 nml。
\un 匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\u00A9 匹配版权符号 (©)。
\p{Punct} 匹配任何标点字符。

下面我们看一下主要的regexPat:

1
2
3
public static String regexPat = 
"\\s*((//.*)|([0-9]+)|(\"(\\\\\"|\\\\\\\\|\\\\n|[^\"])*\")"
+"|[A-Z_a-z][A-Z_a-z0-9]*|==|<=|>=|&&|\\|\\||\\p{Punct})?";

接下来我们按上面的规则来分析这个解析表达式:

1
"\\s*((//.*)|([0-9]+)|(\"(\\\\\"|\\\\\\\\|\\\\n|[^\"])*\")|[A-Z_a-z][A-Z_a-z0-9]*|==|<=|>=|&&|\\|\\||\\p{Punct})?";

我们经过格式化。可以得到以下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
\\s*                                            匹配空格
( group(1)
(
//.* 匹配注释-group(2)
)
|
(
[0-9]+ 匹配数字-group(3)
)
|
(
\"(\\\\\"|\\\\\\\\|\\\\n|[^\"])*\" 匹配字符串-group(4)
)
|
[A-Z_a-z][A-Z_a-z0-9]* 匹配标识符,identifier
|
== 匹配==
|
<= 匹配<=
|
>= 匹配>=
|
&& 匹配&&
|
\\ 匹配\
|
\\| 匹配|
|
\\p{Punct} 匹配任何标点字符
)? 匹配0次或1次

可以看出,我们将其分割开来,还是比较容易看懂的。所以此时我们再看匹配的Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void addToken(int lineNumber, Matcher matcher) {
String m = matcher.group(1); //最外层匹配项
// System.out.println(m);
if(m != null) { //not a space
if(matcher.group(2) == null) { //not a comment
Token token;
if(matcher.group(3) != null) { //数字Token,例如123
token = new NumToken(lineNumber, Integer.parseInt(m));
}else if(matcher.group(4) != null) { //字符串匹配,例如 "123"
token = new StrToken(lineNumber, toStringLiteral(m));
}else { //id匹配,例如while
token = new IdToken(lineNumber, m);
}
queue.add(token);
}
}
}

即可以完全和Java代码匹配出来。

总体思路

所以词法分析的主要流程很简答:

  1. 将代码读取出来为stream,按行读取。
  2. 使用正则表达式匹配每一行的代码,将其识别为对应的单词(token)。
  3. 将识别出的token加入到Lexer的ArrayList中。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :