- 开发工具
- 构建工具
【免费下载链接】buck
A fast build system that encourages the creation of small, reusable modules over a variety of platforms and languages.
本文以 Buck 仓库 tools/psi-grammar/docs/Python2Grammar.md 为骨架,完整解读 CPython 2.7 的正式文法(BNF 产生式全集):从三个起始符号、语句与复合语句结构,到表达式的 12 级优先级层级、推导式与testlist_safe的历史细节;同时结合仓库中 NextGenIdeabuckGrammar.bnf 与 Buck.bnf 的逐行注释,说明 Buck 的 ideabuck 插件是如何把这份 Python 文法裁剪成 Starlark 子集、并经 JFlex + Grammar-Kit 两遍生成变成可运行的 IntelliJ PSI 解析器的。读完后你既能独立读懂任意一份 BNF 文法,也能理解一个 IDE 语法插件从文法文件到词法/语法分析器的完整工程链路。
文档来龙去脉:为什么 Buck 仓库里放着一份 Python 2.7 文法
Python2Grammar.md 的第一行注明来源:它取自 CPython 2.7 源码树中的Grammar/Grammar文件,即 Python 2.7 解释器解析器所依据的正式文法。文件开头的注释还保留了 CPython 的原始告诫:
Changing the grammar specified in this file will most likely require corresponding changes in the parser module (../Modules/parsermodule.c) … You should also follow all the steps listed in PEP 306, "How to Change Python's Grammar"
也就是说,这份文档不是孤立的笔记,而是一份完整的语言规范级产物——它由 C 解析器直接驱动,任何产生式的改动都必须同步修改解析模块。
它被收录在 Buck 的tools/psi-grammar模块下,是因为该模块正是 ideabuck(Buck 的 IntelliJ 插件)的词法/语法分析器所在。同目录下的 StarlarkLanguageOverview.md 与 StarlarkLanguageSpecification.md 分别摘录了 Starlark(Bazel 构建语言,Buck 的 BUCK/DEPS 文件语言与其同源)的概述与形式规范,而 Python2Grammar.md 则是这套文档体系中的基准参照系:Buck 文件本质上是 Starlark,Starlark 在语法上又是 Python 2/3 的子集,所以 Python 2.7 的完整文法就是裁剪出 Starlark 文法的"全量母版"。
最能体现这一关系的,是 NextGenIdeabuckGrammar.bnf 文件头的说明(L17-L32):
// This file is not in use by Buck, and it is not the actual Buck grammar. // Instead, it is a prototype for a Buck grammar that more closely matches // the Python2 grammar, with specific exceptions for the Starlark language … // For reference, the full grammar of … appears below in comments, with // Buck's grammar interspersed throughout.即:该文件把 Python2Grammar.md 的每一条产生式原文都以// [Python2]:注释形式逐行引在正文中,再在其间穿插插值(interspersed)Buck 自己的对应产生式,并在两者不一致处给出理由。下文第 2~8 节按原文档结构完整解读这份 Python 文法,第 9 节再做逐条对照。
文法记法约定:如何阅读这份 BNF
在逐条讲解之前,先明确这份文法的记法(BNF 的变体,也是 Grammar-Kit.bnf文件的通用记法):
- 形如
nonterminal: rhs的每一行是一条产生式,左部是小写非终结符,右部是终结符与非终结符的序列; - 带单引号的串(如
'def'、'+')是字面量终结符;不带引号的大写标识符(NAME、NUMBER、STRING、NEWLINE、INDENT、DEDENT、ENDMARKER)是词法记号(token),由词法器产生; [ x ]表示 x 可选(0 或 1 次);x*表示 0 次或多次;x+表示 1 次或多次;( a | b )是选择;- 词法层面还有一个关键约定:
INDENT/DEDENT是逻辑记号,由缩进推导出来,这与 Python "以缩进定块" 的语法直接对应。
Python2Grammar.md 全文约 144 行,覆盖了 Python 2.7 的全部语法。下面按原文档的行文顺序完整给出各段产生式并逐段讲解。
起始符号:single_input、file_input 与 eval_input
文法开篇定义了三个起始符号(entry points),对应三种输入场景:
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE file_input: (NEWLINE | stmt)* ENDMARKER eval_input: testlist NEWLINE* ENDMARKERsingle_input:交互式单行输入(python 解释器的 REPL 模式)。它要么是空行(NEWLINE),要么是一条简单语句,要么是一条复合语句后跟换行——原文注释特意强调 "compound_stmt in single_input is followed by extra NEWLINE",因为交互式环境下复合语句需要用户敲一个空行来结束缩进块;file_input:从文件读入的模块,即buck解析 BUCK 文件、Python 编译.py文件所走的路径——任意多条空行或语句,以ENDMARKER(文件结束记号)收尾;eval_input:eval()/input()的输入,只允许一个测试列表(testlist),因为 eval 的对象是表达式而非语句。
值得对照的一点是:Buck 的实际文法 Buck.bnf 中,对应的顶层产生式简化为buckFile ::= statement*(L130),不再区分交互/文件两种模式——这与 BUCK 文件"永远按文件编译"的定位一致。
函数定义、装饰器与参数列表
decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE decorators: decorator+ decorated: decorators (classdef | funcdef) funcdef: 'def' NAME parameters ':' suite parameters: '(' [varargslist] ')' varargslist: ((fpdef ['=' test] ',')* ('*' NAME [',' '**' NAME] | '**' NAME) | fpdef ['=' test] (',' fpdef ['=' test])* [',']) fpdef: NAME | '(' fplist ')' fplist: fpdef (',' fpdef)* [',']这段产生式刻画了def与装饰器的完整形态:
funcdef要求'def'关键字、一个NAME、参数表、冒号,以及一个语句块suite;varargslist的三分支结构精确对应 Python 2.7 的参数规则:- 前置若干具名参数(
fpdef ['=' test] ','),每个可带任意表达式默认值; - 或者
'*' NAME [',' '**' NAME](*args,可再跟**kwargs); - 或者
**' NAME(仅关键字展开);
- 前置若干具名参数(
fpdef: NAME | '(' fplist ')'允许在参数表中写括号分组(如def f((a, b)): ...),这一特性在 Python 3 中已被移除——它本身就是 Python 2 的语法遗产。
在 Starlark 侧的对照中,这条产生式被大幅简化。Buck.bnf 中的对应写法是:
parameter_list ::= [parameter (',' parameter)* ','?] parameter ::= '**' identifier | '*' identifier | identifier ['=' simple_expression]NextGenIdeabuckGrammar.bnf 对此的注释引了 Starlark 规范(StarSpec:107):"The grammar does not enforce the legal order of params and args"——Starlark 有意不强制*args/**kwargs必须排最后,于是文法上直接用更宽松的产生式。而 Python 2.7 原版的decorator/decorators/decorated三条产生式在 Buck 文法中则被整条删除,注释为 "No evidence that Starlark or Buck support decorators"(NextGenIdeabuckGrammar.bnf#L331)。
语句层级:simple_stmt 与 small_stmt
stmt: simple_stmt | compound_stmt simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE small_stmt: (expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt | import_stmt | global_stmt | exec_stmt | assert_stmt)stmt是二选一:简单语句或复合语句。simple_stmt允许用分号把多条small_stmt串在一行,行尾还可再跟一个分号([';']),以NEWLINE结束。
small_stmt的 8 个分支覆盖了 Python 2 的全部简单语句,逐条看:
赋值、增广赋值与 print
expr_stmt: testlist (augassign (yield_expr|testlist) | ('=' (yield_expr|testlist))*) augassign: ('+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=') print_stmt: 'print' ( [ test (',' test)* [','] ] | '>>' test [ (',' test)+ [','] ] )expr_stmt的testlist开头支持链式赋值(a = b = 1)与元组解包(x, y = y, x);目标位置可以是yield_expr|testlist,这为a = b = yield x这类写法留了语法空间(是否合法由解释器进一步约束,注释 "For normal assignments, additional restrictions enforced by the interpreter" 即指此);augassign的 12 个算子与//地板除一起,是 Python 2 的完整增广赋值集合(Starlark 只保留了+= -= *= /= //= %=,见 Buck.bnf#L171 的augment_assignment);print_stmt把print当语句而非函数,第二个分支'>>' test支持向文件对象重定向(print >> f, x)——这是 Python 2 与 3 之间最著名的差异之一。
del、pass 与流程控制
del_stmt: 'del' exprlist pass_stmt: 'pass' flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt break_stmt: 'break' continue_stmt: 'continue' return_stmt: 'return' [testlist] yield_stmt: yield_expr raise_stmt: 'raise' [test [',' test [',' test]]]注意raise_stmt的三元形式'raise' test ',' test ',' test:这是 Python 2 特有的raise Exc, value, tb三参异常抛出(Python 3 改为raise exc from cause)。yield_stmt独立成句,配合文末的yield_expr: 'yield' [testlist]。
import、global 与 exec
import_stmt: import_name | import_from import_name: 'import' dotted_as_names import_from: ('from' ('.'* dotted_name | '.'+) 'import' ('*' | '(' import_as_names ')' | import_as_names)) import_as_name: NAME ['as' NAME] dotted_as_name: dotted_name ['as' NAME] import_as_names: import_as_name (',' import_as_name)* [','] dotted_as_names: dotted_as_name (',' dotted_as_name)* dotted_name: NAME ('.' NAME)* global_stmt: 'global' NAME (',' NAME)* exec_stmt: 'exec' expr ['in' test [',' test]] assert_stmt: 'assert' test [',' test]import_from的第一个选择项'.'* dotted_name | '.'+精确刻画了相对导入:from .mod import x(点号串后跟模块名)与from . import x(纯点号串)两种形态;'import' '*'即from x import *;exec_stmt是 Python 2 的exec语句(Python 3 降格为函数),['in' test [',' test]]对应exec code in globals, locals的执行环境参数;assert_stmt的可选第二参数是断言失败信息:assert cond, "msg"。
Starlark 对照:上述分支在 Buck.bnf 中被收敛为 8 个 simple 分支之一的新增条目load_call,同时删除了print、del、import、global、exec、raise、yield。其中load是 Starlark 独有的模块加载语句,其产生式(Buck.bnf#L157-L167):
load_call ::= 'load' '(' load_target_argument (',' load_argument) (',' load_argument)* [','] ')' load_target_argument ::= string load_argument ::= [identifier '='] string比import_from严格得多:第一个参数必须是字符串字面量(目标标签),其余参数只能是"sym" = "orig_name"形式的字符串映射,不支持*、不支持*args/**kwargs。NextGenIdeabuckGrammar.bnf#L362-L367 的注释也明确:"the syntax of a load statement is much more restrictive than a normal argument_list"。
复合语句与 suite:缩进如何进入文法
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] while_stmt: 'while' test ':' suite ['else' ':' suite] for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite] try_stmt: ('try' ':' suite ((except_clause ':' suite)+ ['else' ':' suite] ['finally' ':' suite] | 'finally' ':' suite)) with_stmt: 'with' with_item (',' with_item)* ':' suite with_item: test ['as' expr] # NB compile.c makes sure that the default except clause is last except_clause: 'except' [test [('as' | ',') test]] suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT这段是 Python "块语法"的核心,逐点说明:
if_stmt的('elif' test ':' suite)*把任意多 elif 表达为零次或多次重复;for/while均支持可选的else块(循环未被break打断时执行);try_stmt是一个精巧的选择结构:要么是"至少一个except_clause,可再跟else、finally",要么是纯'finally' ':' suite。原文注释 "NB compile.c makes sure that the default except clause is last" 点出了一个重要事实:裸except:(不带异常类型的默认子句)必须放在最后,这一约束文法本身不表达,由编译模块 compile.c 负责检查——这是"文法 + 语义检查"分工的典型例子;with_item: test ['as' expr]让with open(f) as fh:中的绑定目标是一个通用表达式,支持with a, b as x, c as y:的元组绑定;suite是全文法中INDENT/DEDENT出现的地方:要么同一行的单条简单语句,要么是NEWLINE INDENT stmt+ DEDENT的缩进块。词法器把物理缩进翻译成这对逻辑记号,文法层面就只剩普通的记号匹配了。
Starlark 对照:Buck.bnf#L183-L189 中compound_statement ::= if_statement | for_statement | function_definition——while、try、with、classdef、decorated全部缺席;for_statement也去掉了['else' ':' suite]。NextGenIdeabuckGrammar.bnf#L434-L450 的注释逐条引用 Starlark 规范给出原因:Starlark "not supported: while, yield / try, raise, except, finally(用fail代替致命错误)",for循环不支持 else 子句,且"for 语句不允许出现在顶层,请折进函数"。
表达式层级:十二级优先级与 test 的两种形态
Python2Grammar.md 中最长的部分是表达式文法。它采用逐级升格的经典写法:每引入一个低优先级算子就新增一层非终结符,从条件表达式一路下沉到原子。全文如下:
test: or_test ['if' or_test 'else' test] | lambdef or_test: and_test ('or' and_test)* and_test: not_test ('and' not_test)* not_test: 'not' not_test | comparison comparison: expr (comp_op expr)* comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not' expr: xor_expr ('|' xor_expr)* xor_expr: and_expr ('^' and_expr)* and_expr: shift_expr ('&' shift_expr)* shift_expr: arith_expr (('<<'|'>>') arith_expr)* arith_expr: term (('+'|'-') term)* term: factor (('*'|'/'|'%'|'//') factor)* factor: ('+'|'-'|'~') factor | power power: atom trailer* ['**' factor]由此得到自低到高的完整优先级序(同级左结合,**右结合):
| 层级 | 非终结符 | 算子 / 形态 | 结合性 |
|---|---|---|---|
| 1 | lambdef | lambda [varargslist] ':' test | 最低 |
| 2 | test | 条件式X if C else Y | 右递归 |
| 3 | or_test | or | 左结合 |
| 4 | and_test | and | 左结合 |
| 5 | not_test | not | 右递归(一元) |
| 6 | comparison | < > == >= <= <> != in not in is is not,可链式 | 左结合 |
| 7 | expr | \|(位或) | 左结合 |
| 8 | xor_expr | ^(位异或) | 左结合 |
| 9 | and_expr | &(位与) | 左结合 |
| 10 | shift_expr | <<>> | 左结合 |
| 11 | arith_expr | +- | 左结合 |
| 12 | term | */%// | 左结合 |
| 13 | factor | 一元+-~ | 右递归 |
| 14 | power | ** | 右结合 |
| 15 | atom/trailer | 字面量、括号、调用、下标、属性 | — |
几个值得停下来细读的点:
comparison: expr (comp_op expr)*表达链式比较:1 < x < 5是合法的(语义由求值器处理为1 < x and x < 5)。这正是 Starlark 明确移除的特性——NextGenIdeabuckGrammar.bnf#L479-L485 将对照注释 "Starlark does not support chained comparisons",并把产生式改写成单次比较comparison_expression ::= simple_expression [comparison_op simple_expression],同时删除'is'算子(Starlark 要求用==)。test的双重身份:test既可是一个条件三元表达式,也可是一个lambdef。而条件式的 else 分支递归回test(而非or_test),这使a if c else b if d else e这类嵌套条件式在语法上成立且按右结合解析。power: atom trailer* ['**' factor]的右结合:2 ** 3 ** 2解析为2 ** (3 ** 2) = 512;同时因为**直接挂在power层而一元算子在factor层,-x ** 2解析为-(x ** 2)而非(-x) ** 2。这是该文法里"层级顺序即语义"的最典型例证。
test以下的支撑产生式如下:
atom: ('(' [yield_expr|testlist_comp] ')' | '[' [listmaker] ']' | '{' [dictorsetmaker] '}' | '`' testlist1 '`' | NAME | NUMBER | STRING+) trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME subscriptlist: subscript (',' subscript)* [','] subscript: '.' '.' '.' | test | [test] ':' [test] [sliceop] sliceop: ':' [test] exprlist: expr (',' expr)* [','] testlist: test (',' test)* [','] lambdef: 'lambda' [varargslist] ':' testatom的五个分支即五类原子:括号表达式(含生成器与推导式)、列表、字典/集合、反引号(Python 2 独有的`x`强制 repr 语法,Python 3 已移除)、标识符/数字/字符串;STRING+(注意是加号)意味着相邻字符串字面量隐式拼接("a" "b"即"ab")。Starlark 禁止这种隐式拼接,要求显式用+(见 NextGenIdeabuckGrammar.bnf#L68-L81 摘录的 Starlark 差异清单),因此 Buck.bnf#L278 的atomic_expression中字符串只出现一次,且 NextGenIdeabuckGrammar.bnf#L612-L619 为此单列了 8 个字符串记号(单/双引号 × 普通/原始 × 单行/三引号)的统一string产生式;subscript的三个分支覆盖...(Ellipsis)、普通索引与切片,[test] ':' [test] [sliceop]合起来支持a[:]、a[::2]、a[1:2:3]等全部切片形态;power的trailer*允许obj().method[0]这类调用、下标、属性访问的任意交错序列。
推导式、生成器与 testlist_safe 的历史细节
listmaker: test ( list_for | (',' test)* [','] ) testlist_comp: test ( comp_for | (',' test)* [','] ) dictorsetmaker: ( (test ':' test (comp_for | (',' test ':' test)* [','])) | (test (comp_for | (',' test)* [','])) ) arglist: (argument ',')* (argument [','] |'*' test (',' argument)* [',' '**' test] |'**' test) # The reason that keywords are test nodes instead of NAME is that using NAME # results in an ambiguity. ast.c makes sure it's a NAME. argument: test [comp_for] | test '=' test list_iter: list_for | list_if list_for: 'for' exprlist 'in' testlist_safe [list_iter] list_if: 'if' old_test [list_iter] comp_iter: comp_for | comp_if comp_for: 'for' exprlist 'in' or_test [comp_iter] comp_if: 'if' old_test [comp_iter] testlist1: test (',' test)* # not used in grammar, but may appear in "node" passed from Parser to Compiler encoding_decl: NAME yield_expr: 'yield' [testlist]listmaker与testlist_comp是同构的"列表体":开头一个test,后面要么接推导/生成子句,要么是普通逗号列表(尾逗号可选)。区别在于testlist_comp只出现在圆括号内((...)可以是生成器表达式,而[...]内的推导式走listmaker);dictorsetmaker用顶层选择区分字典推导(test ':' test comp_for)与集合推导(test comp_for),以及各自的普通字面量形态({'a': 1, 'b': 2}与{1, 2})。Starlark 没有 set 也没有 float,对应地 NextGenIdeabuckGrammar.bnf#L560-L564 将其裁剪为只保留字典形态的dictmaker;arglist的三分支允许f(*args)、f(*args, **kwargs)、f(**kw)等一切调用形态,且各分支都接受尾逗号;argument: test [comp_for] | test '=' test里comp_for的存在正是生成器表达式的语法入口(sum(x for x in it),圆括号可省);其上方注释解释了一个精妙的设计:关键字参数名在文法层面被写成test而非NAME,因为若用NAME会产生歧义(f(x=1)中x与f(x) = 1无法区分),真正"它必须是 NAME"的约束推迟到 ast.c 检查——又一次"文法宽松、语义收紧"的分工;encoding_decl: NAME单独注明 "not used in grammar, but may appear in node passed from Parser to Compiler":它是# -*- coding: xxx -*-编码声明的载体,词法层面产出但不出现在任何产生式里。
最难懂的是testlist_safe/old_test/old_lambdef三行(紧随suite之后、test之前):
# Backward compatibility cruft to support: # [ x for x in lambda: True, lambda: False if x() ] # even while also allowing: # lambda x: 5 if x else 2 # (But not a mix of the two) testlist_safe: old_test [(',' old_test)+ [',']] old_test: or_test | old_lambdef old_lambdef: 'lambda' [varargslist] ':' old_test原文注释自述其存在是向后兼容的遗留设计,目的是同时满足两个需求而不允许混搭:
[ x for x in lambda: True, lambda: False if x() ]——推导式的迭代序列里允许"旧式" lambda(其函数体是old_test,不含if-else 条件式);lambda x: 5 if x else 2——独立语句位置的 lambda 函数体可以是完整test(含条件式)。
若把两个 lambda 直接复用,[x for x in lambda: True if x else False, ...]会产生"for 的迭代序列边界在哪"的歧义。于是文法克隆了一套old_前缀的降级 lambda(函数体限old_test),专门供list_for: 'for' exprlist 'in' testlist_safe [list_iter]的迭代序列使用;而comp_for迭代的是or_test,comp_if的条件是old_test——推导式条件位置也因此不能写裸 lambda。这是整份文法中最能体现"文法是为了解析器实际行为而逐条打磨"的段落,理解它基本等于读懂了 BNF 处理歧义的手段。
Starlark 侧由于不支持 lambda("not supported: lambda and nested functions"),testlist_safe/old_lambdef整套机制在 NextGenIdeabuckGrammar.bnf#L587-L602 中被合并简化:list_for/list_if与comp_for/comp_if归并为一组comprehension_iter ::= comprehension_for | comprehension_if,迭代序列直接用or_expression。
类定义与其余零散产生式
classdef: 'class' NAME ['(' [testlist] ')'] ':' suiteclassdef允许任意表达式作为基类(class A(B(1+2), C): ...,基类本身可以是调用结果),基类列表可以是元组(class A(B, C))。Starlark 不支持 class("use struct function" 代替,见 NextGenIdeabuckGrammar.bnf#L566-L567),该产生式在 Buck 文法中整体删除。
至此,Python2Grammar.md 从single_input到yield_expr的全部产生式(约 60 个非终结符)即已完整覆盖:3 个起始符号、10 条函数定义相关、约 30 条语句相关、约 20 条表达式相关,外加encoding_decl与yield_expr两个"语法外"辅助记号。
从 Python 2.7 到 Starlark:Buck 如何裁剪这份文法
把上文各节中散落的对照集中起来,NextGenIdeabuckGrammar.bnf 文件头部(L51-L81)摘录 Starlark 规范后,可以整理出 Buck/Starlark 相对 Python 2.7 的完整删减清单:
| Python 2.7 产生式 / 特性 | Starlark / Buck 的处置 |
|---|---|
decorator/decorators/decorated | 删除(无证据 Starlark/Buck 支持装饰器) |
print_stmt | 删除(非关键字,用内置函数风格) |
del_stmt | 删除(改用dict.pop()等) |
import_stmt(含相对导入、*) | 替换为受限的load_call |
global_stmt/exec_stmt | 删除(Starlark 不允许重赋全局变量;无 exec) |
raise_stmt/yield_stmt/try_stmt/with_stmt | 删除(用fail表达致命错误) |
while_stmt、for/while的else子句 | 删除 |
classdef | 删除(用struct()代替类) |
lambdef、yield_expr、生成器表达式(argument中的comp_for入口) | 删除 |
链式比较、is/is not | 删除(跨类型比较未定义,与 Python 3 一致) |
反引号原子`x`、<>不等号、STRING+隐式拼接 | 删除(<>在 Buck 文法中仍保留为comparison_op的历史遗存,见 Buck.bnf#L220) |
testlist_safe/old_test/old_lambdef兼容机制 | 随 lambda 一起消失,推导子句统一 |
fpdef的括号分组参数、varargslist的顺序约束 | 简化为parameter_list,不强制*/**位置 |
同时保留并直接继承的部分(可逐条在 Buck.bnf 找到同形产生式):条件三元式、or/and/not三级、位运算四级、移位、四则、一元与**、trailer三分支、切片三形态、dict/列表推导、assert两参形式、缩进suite等。换言之,Python2Grammar.md 在这套文档里的角色就是"裁剪前的全量基准":读 Buck 文法时遇到任何一处删改,都能回到这份 Python 文法找到被删掉的原产生式。
文法如何变成解析器:JFlex 与 Grammar-Kit 的两遍生成
最后交代这份文法在 Buck 构建中的实际去向。tools/psi-grammar/README.md 说明该模块用 JFlex 生成词法器(输入*.flex)、用 JetBrains Grammar-Kit 生成解析器(输入*.bnf),开发时统一用 Buck 构建:
buck build //tools/psi-grammar:lexers //tools/psi-grammar:parsers对应 tools/psi-grammar/BUCK 中的目标:lexersgenrule(L4-L17)以 Buck.flex 和 Bcfg.flex 为源;parsers目标则实现了 README 所述的两遍生成以解决"mixin 鸡生蛋"问题——Grammar-Kit 解析.bnf里的mixin = "…BuckNamedElementImpl"这类指令时需要已编译的Java 类,而编译 mixin 又依赖生成的解析器:
- 第一遍(BUCK#L63-L70):直接跑 Grammar-Kit 生成"不认识 mixin 的不完整解析器",打包成
.src.zip; - 中间步骤(BUCK#L79-L95):用这份 zip 编译 MIXIN_SRCS 中列出的 18 个手写类(如 BuckPsiImplUtil.java),并打成
_custom_grammarkit可执行 JAR; - 第二遍(BUCK#L98-L105):用带 mixin 的 Grammar-Kit 重新生成解析器,此时
.bnf中function_definition、load_argument、identifier等产生式上声明的mixin/implements/methods(见 Buck.bnf#L132-L140)被正确解析为 PSI 方法(getName/setName等),最终由java_library目标grammar连同手写源码一起发布给//tools/ideabuck/...使用。
NextGenIdeabuckGrammar.bnf 自身不参与构建(文件头已声明 "This file is not in use by Buck"),它的用途是给开发者在 IntelliJ 的 Grammar-Kit + PsiViewer 实时预览窗里试验这套更贴近 Python 2 文法的原型,验证通过后再逐步迁移——这也解释了为什么 Python2Grammar.md 作为参考规范长期保留在docs/目录下。
小结
- tools/psi-grammar/docs/Python2Grammar.md 是 CPython 2.7 解析器文法的完整副本:3 个起始符号、约 60 个非终结符,覆盖从
single_input到yield_expr的全部 Python 2.7 语法,其中suite: NEWLINE INDENT stmt+ DEDENT与 12 级表达式分层是理解"缩进如何进入文法"与"优先级如何由非终结符层级编码"的关键; - 它在 Buck 仓库中的定位是基准参照:NextGenIdeabuckGrammar.bnf 逐行引用它并在间隙给出 Starlark 的删改与理由,Buck.bnf 是实际参与构建的裁剪版文法;
- 从文法文件到可运行的 IDE 解析器,链路是 JFlex(词法)+ Grammar-Kit 两遍生成(语法,解决 mixin 编译依赖),构建入口见 tools/psi-grammar/BUCK 与 tools/psi-grammar/README.md。
- 开发工具
- 构建工具
【免费下载链接】buck
A fast build system that encourages the creation of small, reusable modules over a variety of platforms and languages.
相关推荐
Buck psi-grammar 深入解析:ideabuck 插件中 BUCK 与 .buckconfig 语法的两遍生成机制
Buck psi grammar 深入解析:ideabuck 插件中 BUCK 与 .buckconfig 语法的两遍生成机制 psi grammar 是 Bu
开发工具构建工具深入解析 .NET runtime 的 IL 语法提取器:从 Yacc 文法到 BNF 语法文件
深入解析 .NET runtime 的 IL 语法提取器:从 Yacc 文法到 BNF 语法文件 导读 在 .NET 开源仓库( runtime https:/
语言运行时标准库JIT编译编译器Slang 语法逆向工程参考:从 EBNF 文法到解析器实现的完整指南
Slang 语法逆向工程参考:从 EBNF 文法到解析器实现的完整指南 导读 本文基于 Slang 编译器中逆向工程得到的 EBNF 风格语法参考文档( doc
编译器图形学编程语言
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考