本文档总结了在编写编译器定义文件时使用的处理技巧和设计原则,包括递归处理、运算符优先级处理、模块化和可扩展性、空白符和注释处理以及代码块处理。
1. 递归和组合
1.1 递归规则
递归规则用于处理可以重复出现的语法结构。通过递归,可以轻松处理多个相同类型的语法结构。
示例:
statements -> statement {% ([statement1]) => statement1 %}
| statements _ statement {% ([statements1, _2, statement3]) => {
const result = []
if (Array.isArray(statements1.statements)) {
result.push(...statements1.statements)
} else {
result.push(statements1)
}
result.push(statement3)
return {
type: "statements",
statements: result
}
} %}
- 递归组合:
statements
规则通过递归组合多个statement
,形成一个语句序列。 - 数组组合:通过递归组合,将多个
statement
组合成一个数组。
1.2 递归组合模块导入和定义
import_defineMod -> importMod {% id %}
| defineMod {% id %}
| import_defineMod _ importMod {% ([import_defineMod1, _2, importMod3]) => {
const result = []
if(Array.isArray(import_defineMod1)) {
result.push(...import_defineMod1)
} else {
result.push(import_defineMod1)
}
result.push(importMod3)
return result
} %}
| import_defineMod _ defineMod {% ([import_defineMod1, _2, defineMod3]) => {
const result = []
if(Array.isArray(import_defineMod1)) {
result.push(...import_defineMod1)
} else {
result.push(import_defineMod1)
}
result.push(defineMod3)
return result
} %}
- 递归组合:
import_defineMod
规则通过递归组合多个importMod
或defineMod
,形成一个模块导入和定义的序列。 - 数组组合:通过递归组合,将多个
importMod
或defineMod
组合成一个数组。
2. 运算符优先级
通过定义不同的非终结符来处理运算符的优先级,确保运算符的优先级和结合性正确。
示例:
expression -> term (_ ("+" | "-") _ term):* {% ([term1, optional2]) => {
optional2 = [...optional2];
return optional2.reduce((acc, [_1, opkw2, _3, term4])=> {
return {
type: 'op',
opkw: opkw2,
left: acc,
right: term4
}
}, term1)
} %}
term -> factor (_ ("*" | "/") _ factor):* {% ([factor1, optional2]) => {
optional2 = [...optional2];
return optional2.reduce((acc,[_1, opkw2, _3, factor4])=> {
return {
type: 'op',
opkw: opkw2,
left: acc,
right: factor4
}
}, factor1)
} %}
- 分层处理:通过定义不同的非终结符(
expression
和term
)来处理运算符的优先级。 - 递归组合:通过递归组合,将多个运算符和操作数组合成一个表达式。
3. 模块化和可扩展性
3.1 模块化规则
将不同的语法结构(如 importMod
、defineMod
、assignStatement
等)定义为独立的规则,便于管理和扩展。
示例:
importMod -> "import" _ "{" _ ident _ "}"
{% ([import1, _2, lbrace3, _4, ident5, _6, rbrace7])=>{
return {
type: "importStatement",
modName: ident5.value,
line: ident5.line,
col: ident5.col
}
} %}
defineMod -> "mod" _ ident
{% ([mod1, _2, ident3]) => {
return {
type: "defineModStatement",
modName: ident3.value,
line: ident3.line,
col: ident3.col
}
} %}
- 独立规则:
importMod
和defineMod
规则是独立的,便于管理和扩展。
3.2 组合规则
通过组合规则(如 program
和 import_defineMod
)来处理复杂的语法结构。
示例:
program -> _ statements _ {% ([_1, statements2, _3]) => statements2 %}
program -> _ codeblock _ {% ([_1, codeblock2, _3]) => codeblock2 %}
program -> _ import_defineMod _ {% ([_1, codeblock2, _3]) => codeblock2 %}
program -> _ import_defineMod _ codeblock _ {% ([_1, import_defineMod2, _3, codeblock4]) => [import_defineMod2, codeblock4] %}
program -> _ import_defineMod _ statements _ {% ([_1, import_defineMod2, _3, statements4]) => [import_defineMod2, statements4] %}
- 组合规则:
program
规则通过组合statements
、codeblock
和import_defineMod
来处理复杂的语法结构。
4. 空白符和注释处理
使用 _
规则来处理空白符和注释,避免在语法规则中显式处理空白符。
示例:
nearley
复制
_ -> WS {% () => null %}
_ -> NL {% () => null %}
_ -> Comment {% () => null %}
__ -> WS WS {% () => null %}
- 空白符处理:使用
_
规则来处理空白符和注释,避免在语法规则中显式处理空白符。
5. 代码块
定义代码块规则,用于处理由大括号包围的语句序列。
示例:
codeblock -> "{" _ statements _ "}" {% ([lbrace1, _2, statements3, _4, rbrace5]) => {
return {
type: "codeblock",
statements: statements3
}
} %}
- 代码块:
codeblock
规则匹配一个代码块,即由大括号包围的statements
。
6. 关键字和符号声明
在 moo
中声明关键字和符号,确保在 nearley
和 moo
配合处理时能够正确识别 token。
示例:
const moo = require('moo')
const lexer = moo.compile({
eq: '=',
lb: '{', rb: '}',
lp: '(', rp: ')',
plus: '+', sub: '-', multi: '*', divide: '/',
Number: /-?[0-9]+(?:.[0-9]+)?/,
Identifier: {
match: /[a-zA-Z_][a-zA-Z0-9_]*/,
type: moo.keywords({
KW: ['mod', 'import', 'const'],
})},
WS: /[ \t]/,
SPACE: {match: /\s+/, lineBreaks: true},
NL: { match: /\n/, lineBreaks: true },
})
- 关键字声明:使用
moo.keywords
来声明关键字,如mod
,import
,const
。 - 符号声明:声明符号如
{
,}
,+
,-
,*
,/
等。
总结
在你的编译器定义文件中,展示了多种语法规则的处理技巧:
- 递归和组合:通过递归规则和数组组合,处理多个相同类型的语法结构。
- 运算符优先级:通过定义不同的非终结符来处理运算符的优先级。
- 模块化和可扩展性:将不同的语法结构定义为独立的规则,并通过组合规则处理复杂的语法结构。
- 空白符和注释处理:使用
_
规则来处理空白符和注释,避免在语法规则中显式处理空白符。 - 代码块:定义代码块规则,用于处理由大括号包围的语句序列。
- 关键字和符号声明:在
moo
中声明关键字和符号,确保在nearley
和moo
配合处理时能够正确识别 token。
这些技巧使得你的编译器定义文件结构清晰、易于扩展和维护。